diff --git a/amd/build/data.min.js b/amd/build/data.min.js
index fe7797a..0391b99 100644
--- a/amd/build/data.min.js
+++ b/amd/build/data.min.js
@@ -1,3 +1,3 @@
-define("tiny_elements/data",["exports","core/str","tiny_elements/common","./variantslib","./helper","core/ajax","core/log"],(function(_exports,_str,_common,_variantslib,_helper,_ajax,_log){var obj;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_log=(obj=_log)&&obj.__esModule?obj:{default:obj};return _exports.default=class{constructor(contextid,userStudent,previewElements,canManage){_defineProperty(this,"categories",[]),_defineProperty(this,"components",[]),_defineProperty(this,"flavors",[]),_defineProperty(this,"variants",[]),_defineProperty(this,"langStrings",{}),_defineProperty(this,"userStudent",!1),_defineProperty(this,"canManage",!1),_defineProperty(this,"contextid",1),this.contextid=contextid,this.userStudent=userStudent,this.previewElements=previewElements,this.canManage=canManage,(0,_variantslib.setData)(this)}async loadData(){await this.loadElementsData(),this.langStrings=await this.getAllStrings()}getComponents(){return this.components}getFlavors(){return this.flavors}getVariants(){return this.variants}getComponentById(id){return(0,_helper.findById)(this.components,id)}getCategoryFlavors(categoryname){const categoryFlavors=[];return this.flavors.forEach((flavor=>{flavor.categoryname==categoryname&&categoryFlavors.push({id:flavor.id,name:flavor.name,displayname:flavor.displayname,displayorder:flavor.displayorder})})),categoryFlavors}getCategories(){const cats=[];return this.categories.forEach((category=>{let categoryFlavors=this.getCategoryFlavors(category.name);categoryFlavors.sort(((a,b)=>a.displayorder-b.displayorder));let hasFlavors=Array.isArray(categoryFlavors)&&categoryFlavors.length;cats.push({categoryid:category.id,name:category.displayname,categoryname:category.name,type:category.id,displayorder:category.displayorder,flavors:categoryFlavors,hasFlavors:hasFlavors,active:""})})),cats.sort(((a,b)=>a.displayorder-b.displayorder)),cats.length>0&&(cats[0].active="active",cats[0].flavors.length>0&&(cats[0].flavors[0].factive="active")),cats}getComponentVariants(component){const componentVariants=[];return component.variants.forEach((variant=>{let variantitem=(0,_helper.findByName)(this.variants,variant);if(void 0!==variantitem){let state=(0,_variantslib.variantExists)(component.name,variantitem.name)?"on":"off";componentVariants.push({id:variantitem.id,name:variantitem.name,displayname:variantitem.displayname,state:state,imageClass:variantitem.name+"-variant-"+state,variantclass:(variantitem.c4lcompatibility?"c4l":"elements")+"-"+variantitem.name+"-variant",title:this.langStrings.get(variantitem.name),content:variantitem.content})}})),componentVariants.sort(((a,b)=>a.name.localeCompare(b.name))),componentVariants}getCategoryById(id){return(0,_helper.findById)(this.categories,id)}getLangString(id){return this.langStrings.get(id)}getButtons(editor){const buttons=[];editor.selection.getContent();return Object.values(this.components).forEach((component=>{buttons.push({id:component.id,name:component.displayname,type:component.categoryname,imageClass:"elements-"+component.name+"-icon",htmlcode:component.code,variants:this.getComponentVariants(component),flavorlist:component.flavors.join(","),category:component.categoryname,displayorder:component.displayorder})})),buttons.sort(((a,b)=>a.displayorder-b.displayorder)),buttons}getTemplateContext(editor){return Object.assign({},{elementid:editor.id,buttons:this.getButtons(editor),categories:this.getCategories(),preview:this.previewElements,canmanage:this.canManage})}getPreviewElements(){return this.previewElements}async getAllStrings(){const keys=[],compRegex=/{{#([^}]*)}}/g;this.components.forEach((element=>{[...element.code.matchAll(compRegex)].forEach((strLang=>{-1===keys.indexOf(strLang[1])&&keys.push(strLang[1])})),[...element.text.matchAll(compRegex)].forEach((strLang=>{-1===keys.indexOf(strLang[1])&&keys.push(strLang[1])}))}));const stringValues=await(0,_str.get_strings)(keys.map((key=>({key:key,pluginname:_common.component}))));return new Map(keys.map(((key,index)=>[key,stringValues[index]])))}async loadElementsData(){const data=await(0,_ajax.call)([{methodname:"tiny_elements_get_elements_data",args:{isstudent:this.userStudent,contextid:this.contextid}}])[0].catch((err=>{_log.default.error(err.message)})),indexedComponents=[];data.components.forEach((component=>{indexedComponents[component.id]=component}));const indexedVariants=[];data.variants.forEach((variant=>{indexedVariants[variant.id]=variant}));const indexedCategories=[];data.categories.forEach((category=>{indexedCategories[category.id]=category})),this.components=indexedComponents,this.variants=indexedVariants,this.categories=indexedCategories,this.flavors=data.flavors}},_exports.default}));
+define("tiny_elements/data",["exports","core/str","tiny_elements/common","./variantslib","./helper","core/ajax","core/log"],(function(_exports,_str,_common,_variantslib,_helper,_ajax,_log){var obj;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_log=(obj=_log)&&obj.__esModule?obj:{default:obj};return _exports.default=class{constructor(contextid,userStudent,previewElements,canManage){_defineProperty(this,"categories",[]),_defineProperty(this,"components",[]),_defineProperty(this,"flavors",[]),_defineProperty(this,"variants",[]),_defineProperty(this,"langStrings",{}),_defineProperty(this,"userStudent",!1),_defineProperty(this,"canManage",!1),_defineProperty(this,"contextid",1),this.contextid=contextid,this.userStudent=userStudent,this.previewElements=previewElements,this.canManage=canManage,(0,_variantslib.setData)(this)}async loadData(){await this.loadElementsData(),this.langStrings=await this.getAllStrings()}getComponents(){return this.components}getFlavors(){return this.flavors}getVariants(){return this.variants}getComponentById(id){return(0,_helper.findById)(this.components,id)}getCategoryFlavors(categoryname){const categoryFlavors=[];return this.flavors.forEach((flavor=>{flavor.categoryname==categoryname&&categoryFlavors.push({id:flavor.id,name:flavor.name,displayname:flavor.displayname,displayorder:flavor.displayorder})})),categoryFlavors}getCategories(){const cats=[],componentCounts={};return this.components.forEach((component=>{void 0!==component&&(componentCounts[component.categoryname]=(componentCounts[component.categoryname]||0)+1)})),this.categories.forEach((category=>{let categoryFlavors=this.getCategoryFlavors(category.name);categoryFlavors.sort(((a,b)=>a.displayorder-b.displayorder));let hasFlavors=Array.isArray(categoryFlavors)&&categoryFlavors.length;cats.push({categoryid:category.id,name:category.displayname,categoryname:category.name,type:category.id,displayorder:category.displayorder,flavors:categoryFlavors,hasFlavors:hasFlavors,componentcount:componentCounts[category.name]||0,active:""})})),cats.sort(((a,b)=>a.displayorder-b.displayorder)),cats.length>0&&(cats[0].active="active",cats[0].flavors.length>0&&(cats[0].flavors[0].factive="active")),cats}getComponentVariants(component){const componentVariants=[];return component.variants.forEach((variant=>{let variantitem=(0,_helper.findByName)(this.variants,variant);if(void 0!==variantitem){let state=(0,_variantslib.variantExists)(component.name,variantitem.name)?"on":"off";componentVariants.push({id:variantitem.id,name:variantitem.name,displayname:variantitem.displayname,state:state,imageClass:variantitem.name+"-variant-"+state,variantclass:(variantitem.c4lcompatibility?"c4l":"elements")+"-"+variantitem.name+"-variant",title:this.langStrings.get(variantitem.name),content:variantitem.content})}})),componentVariants.sort(((a,b)=>a.name.localeCompare(b.name))),componentVariants}getCategoryById(id){return(0,_helper.findById)(this.categories,id)}getLangString(id){return this.langStrings.get(id)}getButtons(editor){const buttons=[];editor.selection.getContent();return Object.values(this.components).forEach((component=>{buttons.push({id:component.id,name:component.displayname,type:component.categoryname,imageClass:"elements-"+component.name+"-icon",htmlcode:component.code,variants:this.getComponentVariants(component),flavorlist:component.flavors.join(","),category:component.categoryname,displayorder:component.displayorder})})),buttons.sort(((a,b)=>a.displayorder-b.displayorder)),buttons}getTemplateContext(editor){return Object.assign({},{elementid:editor.id,buttons:this.getButtons(editor),categories:this.getCategories(),preview:this.previewElements,canmanage:this.canManage})}getPreviewElements(){return this.previewElements}async getAllStrings(){const keys=[],compRegex=/{{#([^}]*)}}/g;this.components.forEach((element=>{[...element.code.matchAll(compRegex)].forEach((strLang=>{-1===keys.indexOf(strLang[1])&&keys.push(strLang[1])})),[...element.text.matchAll(compRegex)].forEach((strLang=>{-1===keys.indexOf(strLang[1])&&keys.push(strLang[1])}))}));const stringValues=await(0,_str.get_strings)(keys.map((key=>({key:key,pluginname:_common.component}))));return new Map(keys.map(((key,index)=>[key,stringValues[index]])))}async loadElementsData(){const data=await(0,_ajax.call)([{methodname:"tiny_elements_get_elements_data",args:{isstudent:this.userStudent,contextid:this.contextid}}])[0].catch((err=>{_log.default.error(err.message)})),indexedComponents=[];data.components.forEach((component=>{indexedComponents[component.id]=component}));const indexedVariants=[];data.variants.forEach((variant=>{indexedVariants[variant.id]=variant}));const indexedCategories=[];data.categories.forEach((category=>{indexedCategories[category.id]=category})),this.components=indexedComponents,this.variants=indexedVariants,this.categories=indexedCategories,this.flavors=data.flavors}},_exports.default}));
//# sourceMappingURL=data.min.js.map
\ No newline at end of file
diff --git a/amd/build/data.min.js.map b/amd/build/data.min.js.map
index ae934f2..112420f 100644
--- a/amd/build/data.min.js.map
+++ b/amd/build/data.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"data.min.js","sources":["../src/data.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Container for tiny_elements data (categories, components, flavors, variants).\n *\n * @module tiny_elements/data\n * @copyright 2025 ISB Bayern\n * @author Stefan Hanauska \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {get_strings as getStrings} from 'core/str';\nimport {component as pluginname} from 'tiny_elements/common';\nimport {\n variantExists,\n setData as setVariantsData\n} from './variantslib';\nimport {\n findById,\n findByName\n} from './helper';\nimport {call as fetchMany} from 'core/ajax';\nimport Log from 'core/log';\n\nexport default class Data {\n categories = [];\n components = [];\n flavors = [];\n variants = [];\n langStrings = {};\n userStudent = false;\n canManage = false;\n contextid = 1;\n\n constructor(contextid, userStudent, previewElements, canManage) {\n this.contextid = contextid;\n this.userStudent = userStudent;\n this.previewElements = previewElements;\n this.canManage = canManage;\n setVariantsData(this);\n }\n\n async loadData() {\n await this.loadElementsData();\n this.langStrings = await this.getAllStrings();\n }\n\n getComponents() {\n return this.components;\n }\n\n getFlavors() {\n return this.flavors;\n }\n\n getVariants() {\n return this.variants;\n }\n\n getComponentById(id) {\n return findById(this.components, id);\n }\n\n getCategoryFlavors(categoryname) {\n const categoryFlavors = [];\n this.flavors.forEach(flavor => {\n if (flavor.categoryname == categoryname) {\n categoryFlavors.push({\n id: flavor.id,\n name: flavor.name,\n displayname: flavor.displayname,\n displayorder: flavor.displayorder,\n });\n }\n });\n return categoryFlavors;\n }\n\n /**\n * Get the Elements categories for the dialogue.\n *\n * @returns {object} data\n */\n getCategories() {\n const cats = [];\n // Iterate over contexts.\n this.categories.forEach((category) => {\n let categoryFlavors = this.getCategoryFlavors(category.name);\n categoryFlavors.sort((a, b) => a.displayorder - b.displayorder);\n let hasFlavors = Array.isArray(categoryFlavors) && categoryFlavors.length;\n cats.push({\n categoryid: category.id,\n name: category.displayname,\n categoryname: category.name,\n type: category.id,\n displayorder: category.displayorder,\n flavors: categoryFlavors,\n hasFlavors: hasFlavors,\n active: '',\n });\n });\n // Sort by displayorder and set first to active.\n cats.sort((a, b) => a.displayorder - b.displayorder);\n if (cats.length > 0) {\n cats[0].active = 'active';\n if (cats[0].flavors.length > 0) {\n cats[0].flavors[0].factive = 'active';\n }\n }\n\n return cats;\n }\n\n getComponentVariants(component) {\n const componentVariants = [];\n component.variants.forEach(variant => {\n let variantitem = findByName(this.variants, variant);\n if (variantitem !== undefined) {\n let state = variantExists(component.name, variantitem.name) ? 'on' : 'off';\n componentVariants.push({\n id: variantitem.id,\n name: variantitem.name,\n displayname: variantitem.displayname,\n state: state,\n imageClass: variantitem.name + '-variant-' + state,\n variantclass: (variantitem.c4lcompatibility ? 'c4l' : 'elements') + '-' + variantitem.name + '-variant',\n title: this.langStrings.get(variantitem.name),\n content: variantitem.content,\n });\n }\n });\n componentVariants.sort((a, b) => (a.name.localeCompare(b.name)));\n return componentVariants;\n }\n\n getCategoryById(id) {\n return findById(this.categories, id);\n }\n\n getLangString(id) {\n return this.langStrings.get(id);\n }\n\n /**\n * Get the Elements buttons for the dialogue.\n *\n * @param {Editor} editor\n * @returns {object} buttons\n */\n getButtons(editor) {\n const buttons = [];\n // Not used at the moment.\n // eslint-disable-next-line no-unused-vars\n const sel = editor.selection.getContent();\n Object.values(this.components).forEach(component => {\n buttons.push({\n id: component.id,\n name: component.displayname,\n type: component.categoryname,\n imageClass: 'elements-' + component.name + '-icon',\n htmlcode: component.code,\n variants: this.getComponentVariants(component),\n flavorlist: component.flavors.join(','),\n category: component.categoryname,\n displayorder: component.displayorder,\n });\n });\n buttons.sort((a, b) => a.displayorder - b.displayorder);\n\n return buttons;\n }\n\n /**\n * Get the template context for the dialogue.\n *\n * @param {Editor} editor\n * @returns {object} data\n */\n getTemplateContext(editor) {\n return Object.assign({}, {\n elementid: editor.id,\n buttons: this.getButtons(editor),\n categories: this.getCategories(),\n preview: this.previewElements,\n canmanage: this.canManage,\n });\n }\n\n getPreviewElements() {\n return this.previewElements;\n }\n\n /**\n * Get language strings.\n *\n * @return {object} Language strings\n */\n async getAllStrings() {\n const keys = [];\n const compRegex = /{{#([^}]*)}}/g;\n\n this.components.forEach(element => {\n // Get lang strings from components.\n [...element.code.matchAll(compRegex)].forEach(strLang => {\n if (keys.indexOf(strLang[1]) === -1) {\n keys.push(strLang[1]);\n }\n });\n\n // Get lang strings from text placeholders.\n [...element.text.matchAll(compRegex)].forEach(strLang => {\n if (keys.indexOf(strLang[1]) === -1) {\n keys.push(strLang[1]);\n }\n });\n });\n\n const stringValues = await getStrings(keys.map((key) => ({key, pluginname})));\n return new Map(keys.map((key, index) => ([key, stringValues[index]])));\n }\n\n async loadElementsData() {\n const data = await fetchMany([{\n methodname: 'tiny_elements_get_elements_data',\n args: {\n isstudent: this.userStudent,\n contextid: this.contextid\n },\n }])[0].catch(err => {\n Log.error(err.message);\n });\n\n const indexedComponents = [];\n data.components.forEach(component => {\n indexedComponents[component.id] = component;\n });\n\n const indexedVariants = [];\n data.variants.forEach(variant => {\n indexedVariants[variant.id] = variant;\n });\n\n const indexedCategories = [];\n data.categories.forEach(category => {\n indexedCategories[category.id] = category;\n });\n\n this.components = indexedComponents;\n this.variants = indexedVariants;\n this.categories = indexedCategories;\n this.flavors = data.flavors;\n }\n}\n"],"names":["constructor","contextid","userStudent","previewElements","canManage","this","loadElementsData","langStrings","getAllStrings","getComponents","components","getFlavors","flavors","getVariants","variants","getComponentById","id","getCategoryFlavors","categoryname","categoryFlavors","forEach","flavor","push","name","displayname","displayorder","getCategories","cats","categories","category","sort","a","b","hasFlavors","Array","isArray","length","categoryid","type","active","factive","getComponentVariants","component","componentVariants","variant","variantitem","undefined","state","imageClass","variantclass","c4lcompatibility","title","get","content","localeCompare","getCategoryById","getLangString","getButtons","editor","buttons","selection","getContent","Object","values","htmlcode","code","flavorlist","join","getTemplateContext","assign","elementid","preview","canmanage","getPreviewElements","keys","compRegex","element","matchAll","strLang","indexOf","text","stringValues","map","key","pluginname","Map","index","data","methodname","args","isstudent","catch","err","error","message","indexedComponents","indexedVariants","indexedCategories"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"data.min.js","sources":["../src/data.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Container for tiny_elements data (categories, components, flavors, variants).\n *\n * @module tiny_elements/data\n * @copyright 2025 ISB Bayern\n * @author Stefan Hanauska \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {get_strings as getStrings} from 'core/str';\nimport {component as pluginname} from 'tiny_elements/common';\nimport {\n variantExists,\n setData as setVariantsData\n} from './variantslib';\nimport {\n findById,\n findByName\n} from './helper';\nimport {call as fetchMany} from 'core/ajax';\nimport Log from 'core/log';\n\nexport default class Data {\n categories = [];\n components = [];\n flavors = [];\n variants = [];\n langStrings = {};\n userStudent = false;\n canManage = false;\n contextid = 1;\n\n constructor(contextid, userStudent, previewElements, canManage) {\n this.contextid = contextid;\n this.userStudent = userStudent;\n this.previewElements = previewElements;\n this.canManage = canManage;\n setVariantsData(this);\n }\n\n async loadData() {\n await this.loadElementsData();\n this.langStrings = await this.getAllStrings();\n }\n\n getComponents() {\n return this.components;\n }\n\n getFlavors() {\n return this.flavors;\n }\n\n getVariants() {\n return this.variants;\n }\n\n getComponentById(id) {\n return findById(this.components, id);\n }\n\n getCategoryFlavors(categoryname) {\n const categoryFlavors = [];\n this.flavors.forEach(flavor => {\n if (flavor.categoryname == categoryname) {\n categoryFlavors.push({\n id: flavor.id,\n name: flavor.name,\n displayname: flavor.displayname,\n displayorder: flavor.displayorder,\n });\n }\n });\n return categoryFlavors;\n }\n\n /**\n * Get the Elements categories for the dialogue.\n *\n * @returns {object} data\n */\n getCategories() {\n const cats = [];\n const componentCounts = {};\n this.components.forEach((component) => {\n if (component === undefined) {\n return;\n }\n componentCounts[component.categoryname] = (componentCounts[component.categoryname] || 0) + 1;\n });\n // Iterate over contexts.\n this.categories.forEach((category) => {\n let categoryFlavors = this.getCategoryFlavors(category.name);\n categoryFlavors.sort((a, b) => a.displayorder - b.displayorder);\n let hasFlavors = Array.isArray(categoryFlavors) && categoryFlavors.length;\n cats.push({\n categoryid: category.id,\n name: category.displayname,\n categoryname: category.name,\n type: category.id,\n displayorder: category.displayorder,\n flavors: categoryFlavors,\n hasFlavors: hasFlavors,\n componentcount: componentCounts[category.name] || 0,\n active: '',\n });\n });\n // Sort by displayorder and set first to active.\n cats.sort((a, b) => a.displayorder - b.displayorder);\n if (cats.length > 0) {\n cats[0].active = 'active';\n if (cats[0].flavors.length > 0) {\n cats[0].flavors[0].factive = 'active';\n }\n }\n\n return cats;\n }\n\n getComponentVariants(component) {\n const componentVariants = [];\n component.variants.forEach(variant => {\n let variantitem = findByName(this.variants, variant);\n if (variantitem !== undefined) {\n let state = variantExists(component.name, variantitem.name) ? 'on' : 'off';\n componentVariants.push({\n id: variantitem.id,\n name: variantitem.name,\n displayname: variantitem.displayname,\n state: state,\n imageClass: variantitem.name + '-variant-' + state,\n variantclass: (variantitem.c4lcompatibility ? 'c4l' : 'elements') + '-' + variantitem.name + '-variant',\n title: this.langStrings.get(variantitem.name),\n content: variantitem.content,\n });\n }\n });\n componentVariants.sort((a, b) => (a.name.localeCompare(b.name)));\n return componentVariants;\n }\n\n getCategoryById(id) {\n return findById(this.categories, id);\n }\n\n getLangString(id) {\n return this.langStrings.get(id);\n }\n\n /**\n * Get the Elements buttons for the dialogue.\n *\n * @param {Editor} editor\n * @returns {object} buttons\n */\n getButtons(editor) {\n const buttons = [];\n // Not used at the moment.\n // eslint-disable-next-line no-unused-vars\n const sel = editor.selection.getContent();\n Object.values(this.components).forEach(component => {\n buttons.push({\n id: component.id,\n name: component.displayname,\n type: component.categoryname,\n imageClass: 'elements-' + component.name + '-icon',\n htmlcode: component.code,\n variants: this.getComponentVariants(component),\n flavorlist: component.flavors.join(','),\n category: component.categoryname,\n displayorder: component.displayorder,\n });\n });\n buttons.sort((a, b) => a.displayorder - b.displayorder);\n\n return buttons;\n }\n\n /**\n * Get the template context for the dialogue.\n *\n * @param {Editor} editor\n * @returns {object} data\n */\n getTemplateContext(editor) {\n return Object.assign({}, {\n elementid: editor.id,\n buttons: this.getButtons(editor),\n categories: this.getCategories(),\n preview: this.previewElements,\n canmanage: this.canManage,\n });\n }\n\n getPreviewElements() {\n return this.previewElements;\n }\n\n /**\n * Get language strings.\n *\n * @return {object} Language strings\n */\n async getAllStrings() {\n const keys = [];\n const compRegex = /{{#([^}]*)}}/g;\n\n this.components.forEach(element => {\n // Get lang strings from components.\n [...element.code.matchAll(compRegex)].forEach(strLang => {\n if (keys.indexOf(strLang[1]) === -1) {\n keys.push(strLang[1]);\n }\n });\n\n // Get lang strings from text placeholders.\n [...element.text.matchAll(compRegex)].forEach(strLang => {\n if (keys.indexOf(strLang[1]) === -1) {\n keys.push(strLang[1]);\n }\n });\n });\n\n const stringValues = await getStrings(keys.map((key) => ({key, pluginname})));\n return new Map(keys.map((key, index) => ([key, stringValues[index]])));\n }\n\n async loadElementsData() {\n const data = await fetchMany([{\n methodname: 'tiny_elements_get_elements_data',\n args: {\n isstudent: this.userStudent,\n contextid: this.contextid\n },\n }])[0].catch(err => {\n Log.error(err.message);\n });\n\n const indexedComponents = [];\n data.components.forEach(component => {\n indexedComponents[component.id] = component;\n });\n\n const indexedVariants = [];\n data.variants.forEach(variant => {\n indexedVariants[variant.id] = variant;\n });\n\n const indexedCategories = [];\n data.categories.forEach(category => {\n indexedCategories[category.id] = category;\n });\n\n this.components = indexedComponents;\n this.variants = indexedVariants;\n this.categories = indexedCategories;\n this.flavors = data.flavors;\n }\n}\n"],"names":["constructor","contextid","userStudent","previewElements","canManage","this","loadElementsData","langStrings","getAllStrings","getComponents","components","getFlavors","flavors","getVariants","variants","getComponentById","id","getCategoryFlavors","categoryname","categoryFlavors","forEach","flavor","push","name","displayname","displayorder","getCategories","cats","componentCounts","component","undefined","categories","category","sort","a","b","hasFlavors","Array","isArray","length","categoryid","type","componentcount","active","factive","getComponentVariants","componentVariants","variant","variantitem","state","imageClass","variantclass","c4lcompatibility","title","get","content","localeCompare","getCategoryById","getLangString","getButtons","editor","buttons","selection","getContent","Object","values","htmlcode","code","flavorlist","join","getTemplateContext","assign","elementid","preview","canmanage","getPreviewElements","keys","compRegex","element","matchAll","strLang","indexOf","text","stringValues","map","key","pluginname","Map","index","data","methodname","args","isstudent","catch","err","error","message","indexedComponents","indexedVariants","indexedCategories"],"mappings":"ygBA+CIA,YAAYC,UAAWC,YAAaC,gBAAiBC,6CATxC,sCACA,mCACH,oCACC,uCACG,wCACA,qCACF,oCACA,QAGHH,UAAYA,eACZC,YAAcA,iBACdC,gBAAkBA,qBAClBC,UAAYA,mCACDC,6BAIVA,KAAKC,wBACNC,kBAAoBF,KAAKG,gBAGlCC,uBACWJ,KAAKK,WAGhBC,oBACWN,KAAKO,QAGhBC,qBACWR,KAAKS,SAGhBC,iBAAiBC,WACN,oBAASX,KAAKK,WAAYM,IAGrCC,mBAAmBC,oBACTC,gBAAkB,eACnBP,QAAQQ,SAAQC,SACbA,OAAOH,cAAgBA,cACvBC,gBAAgBG,KAAK,CACjBN,GAAIK,OAAOL,GACXO,KAAMF,OAAOE,KACbC,YAAaH,OAAOG,YACpBC,aAAcJ,OAAOI,kBAI1BN,gBAQXO,sBACUC,KAAO,GACPC,gBAAkB,eACnBlB,WAAWU,SAASS,iBACHC,IAAdD,YAGJD,gBAAgBC,UAAUX,eAAiBU,gBAAgBC,UAAUX,eAAiB,GAAK,WAG1Fa,WAAWX,SAASY,eACjBb,gBAAkBd,KAAKY,mBAAmBe,SAAST,MACvDJ,gBAAgBc,MAAK,CAACC,EAAGC,IAAMD,EAAET,aAAeU,EAAEV,mBAC9CW,WAAaC,MAAMC,QAAQnB,kBAAoBA,gBAAgBoB,OACnEZ,KAAKL,KAAK,CACNkB,WAAYR,SAAShB,GACrBO,KAAMS,SAASR,YACfN,aAAcc,SAAST,KACvBkB,KAAMT,SAAShB,GACfS,aAAcO,SAASP,aACvBb,QAASO,gBACTiB,WAAYA,WACZM,eAAgBd,gBAAgBI,SAAST,OAAS,EAClDoB,OAAQ,QAIhBhB,KAAKM,MAAK,CAACC,EAAGC,IAAMD,EAAET,aAAeU,EAAEV,eACnCE,KAAKY,OAAS,IACdZ,KAAK,GAAGgB,OAAS,SACbhB,KAAK,GAAGf,QAAQ2B,OAAS,IACzBZ,KAAK,GAAGf,QAAQ,GAAGgC,QAAU,WAI9BjB,KAGXkB,qBAAqBhB,iBACXiB,kBAAoB,UAC1BjB,UAAUf,SAASM,SAAQ2B,cACnBC,aAAc,sBAAW3C,KAAKS,SAAUiC,iBACxBjB,IAAhBkB,YAA2B,KACvBC,OAAQ,8BAAcpB,UAAUN,KAAMyB,YAAYzB,MAAQ,KAAO,MACrEuB,kBAAkBxB,KAAK,CACnBN,GAAIgC,YAAYhC,GAChBO,KAAMyB,YAAYzB,KAClBC,YAAawB,YAAYxB,YACzByB,MAAOA,MACPC,WAAYF,YAAYzB,KAAO,YAAc0B,MAC7CE,cAAeH,YAAYI,iBAAmB,MAAQ,YAAc,IAAMJ,YAAYzB,KAAO,WAC7F8B,MAAOhD,KAAKE,YAAY+C,IAAIN,YAAYzB,MACxCgC,QAASP,YAAYO,cAIjCT,kBAAkBb,MAAK,CAACC,EAAGC,IAAOD,EAAEX,KAAKiC,cAAcrB,EAAEZ,QAClDuB,kBAGXW,gBAAgBzC,WACL,oBAASX,KAAK0B,WAAYf,IAGrC0C,cAAc1C,WACHX,KAAKE,YAAY+C,IAAItC,IAShC2C,WAAWC,cACDC,QAAU,GAGJD,OAAOE,UAAUC,oBAC7BC,OAAOC,OAAO5D,KAAKK,YAAYU,SAAQS,YACnCgC,QAAQvC,KAAK,CACTN,GAAIa,UAAUb,GACdO,KAAMM,UAAUL,YAChBiB,KAAMZ,UAAUX,aAChBgC,WAAY,YAAcrB,UAAUN,KAAO,QAC3C2C,SAAUrC,UAAUsC,KACpBrD,SAAUT,KAAKwC,qBAAqBhB,WACpCuC,WAAYvC,UAAUjB,QAAQyD,KAAK,KACnCrC,SAAUH,UAAUX,aACpBO,aAAcI,UAAUJ,kBAGhCoC,QAAQ5B,MAAK,CAACC,EAAGC,IAAMD,EAAET,aAAeU,EAAEV,eAEnCoC,QASXS,mBAAmBV,eACRI,OAAOO,OAAO,GAAI,CACrBC,UAAWZ,OAAO5C,GAClB6C,QAASxD,KAAKsD,WAAWC,QACzB7B,WAAY1B,KAAKqB,gBACjB+C,QAASpE,KAAKF,gBACduE,UAAWrE,KAAKD,YAIxBuE,4BACWtE,KAAKF,4CASNyE,KAAO,GACPC,UAAY,qBAEbnE,WAAWU,SAAQ0D,cAEhBA,QAAQX,KAAKY,SAASF,YAAYzD,SAAQ4D,WACR,IAA9BJ,KAAKK,QAAQD,QAAQ,KACrBJ,KAAKtD,KAAK0D,QAAQ,WAKtBF,QAAQI,KAAKH,SAASF,YAAYzD,SAAQ4D,WACR,IAA9BJ,KAAKK,QAAQD,QAAQ,KACrBJ,KAAKtD,KAAK0D,QAAQ,gBAKxBG,mBAAqB,oBAAWP,KAAKQ,KAAKC,OAAUA,IAAAA,IAAKC,WAAAA,8BACxD,IAAIC,IAAIX,KAAKQ,KAAI,CAACC,IAAKG,QAAW,CAACH,IAAKF,aAAaK,0CAItDC,WAAa,cAAU,CAAC,CAC1BC,WAAY,kCACZC,KAAM,CACFC,UAAWvF,KAAKH,YAChBD,UAAWI,KAAKJ,cAEpB,GAAG4F,OAAMC,mBACLC,MAAMD,IAAIE,YAGZC,kBAAoB,GAC1BR,KAAK/E,WAAWU,SAAQS,YACpBoE,kBAAkBpE,UAAUb,IAAMa,mBAGhCqE,gBAAkB,GACxBT,KAAK3E,SAASM,SAAQ2B,UAClBmD,gBAAgBnD,QAAQ/B,IAAM+B,iBAG5BoD,kBAAoB,GAC1BV,KAAK1D,WAAWX,SAAQY,WACpBmE,kBAAkBnE,SAAShB,IAAMgB,iBAGhCtB,WAAauF,uBACbnF,SAAWoF,qBACXnE,WAAaoE,uBACbvF,QAAU6E,KAAK7E"}
\ No newline at end of file
diff --git a/amd/build/ui.min.js b/amd/build/ui.min.js
index 2710e92..4dc6359 100644
--- a/amd/build/ui.min.js
+++ b/amd/build/ui.min.js
@@ -6,6 +6,6 @@ define("tiny_elements/ui",["exports","tiny_elements/modal","tiny_elements/option
* @copyright 2025 ISB Bayern
* @copyright based on the work of Marc Català
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.handleAction=void 0,_modal_events=_interopRequireDefault(_modal_events),_data=_interopRequireDefault(_data);let currentFlavor="",currentFlavorId=0,currentCategoryId=1,currentCategoryName="",lastFlavor=[],selection="",data={};_exports.handleAction=async editor=>{selection=editor.selection.getContent(),data=new _data.default((0,_options2.getContextId)(editor),(0,_options.isStudent)(editor),(0,_options.showPreview)(editor),(0,_options.canManage)(editor)),await data.loadData(),(0,_variantslib.setData)(data),currentCategoryId=await(0,_preferencelib.loadPreferences)(_preferencelib.Preferences.category),lastFlavor=await(0,_preferencelib.loadPreferences)(_preferencelib.Preferences.category_flavors),null==lastFlavor&&(lastFlavor=[]);let componentVariants=await(0,_preferencelib.loadPreferences)(_preferencelib.Preferences.component_variants);null==componentVariants&&(componentVariants={}),(0,_variantslib.loadVariantPreferences)(componentVariants),await displayDialogue(editor)};const displayDialogue=async editor=>{const templateContext=data.getTemplateContext(editor),modal=await _modal.ElementsModal.create({type:_modal.ElementsModal.TYPE,templateContext:templateContext,large:!0}),modalClass=data.getPreviewElements()?"elements-modal":"elements-modal-no-preview";editor.targetElm.closest("body").classList.add(modalClass),modal.show(),modal.getRoot().on(_modal_events.default.hidden,(()=>{handleModalHidden(editor)}));const soleCategories=modal.getRoot()[0].querySelectorAll(".elements-category.no-flavors");soleCategories.forEach((node=>{node.addEventListener("click",(event=>{handleCategoryClick(event,modal)}))}));const selectCategories=modal.getRoot()[0].querySelectorAll(".elements-category-flavor");selectCategories.forEach((node=>{node.addEventListener("click",(event=>{handleCategoryFlavorClick(event,modal)}))}));modal.getRoot()[0].querySelectorAll(".nav-link.dropdown-toggle").forEach((node=>{node.addEventListener("click",(event=>{handleCategoryRemember(event,modal)}))}));modal.getRoot()[0].querySelectorAll(".elementst-dialog-button").forEach((node=>{node.addEventListener("click",(event=>{handleButtonClick(event,editor,modal)})),data.getPreviewElements()&&(node.addEventListener("mouseenter",(event=>{handleButtonMouseEvent(event,modal,!0)})),node.addEventListener("mouseleave",(event=>{handleButtonMouseEvent(event,modal,!1)})))}));if(modal.getRoot()[0].querySelectorAll(".elements-button-variant").forEach((node=>{node.addEventListener("click",(event=>{handleVariantClick(event,modal)}))})),soleCategories.length>0||selectCategories.length>0){const savedCategory=currentCategoryId,savedFlavor=lastFlavor[currentCategoryId];if(0==soleCategories.length||soleCategories[0].displayorder>selectCategories[0].displayorder?selectCategories[0].click():soleCategories[0].click(),0!=savedCategory&&(soleCategories.forEach((node=>{node.dataset.categoryid==savedCategory&&node.click()})),selectCategories.forEach((node=>{node.dataset.categoryid==savedCategory&&node.click()})),savedFlavor)){const flavorlink=modal.getRoot()[0].querySelector('.elements-category-flavor[data-id="'+savedFlavor+'"]');flavorlink&&flavorlink.click()}}},handleCategoryClick=(event,modal)=>{const link=event.target;currentCategoryId=link.dataset.categoryid,currentCategoryName=link.dataset.categoryname;modal.getRoot()[0].querySelectorAll(".nav-link, .dropdown-item").forEach((node=>node.classList.remove("active"))),link.classList.add("active"),showCategoryButtons(modal,currentCategoryName)},handleCategoryFlavorClick=(event,modal)=>{const link=event.target;currentFlavor=link.dataset.flavor,currentFlavorId=link.dataset.id,currentCategoryId=link.dataset.categoryid,currentCategoryName=link.dataset.categoryname,lastFlavor[currentCategoryId]=currentFlavorId;modal.getRoot()[0].querySelectorAll(".nav-link, .dropdown-item").forEach((node=>node.classList.remove("active"))),link.classList.add("active");modal.getRoot()[0].querySelector('.nav-link[data-categoryid="'+currentCategoryId+'"]').classList.add("active");modal.getRoot()[0].querySelectorAll(".elements-buttons-preview button").forEach((componentButton=>{if(componentButton.dataset.flavor=currentFlavor,""!=componentButton.dataset.flavorlist&&!componentButton.dataset.flavorlist.split(",").includes(currentFlavor)||componentButton.dataset.category!=currentCategoryName)componentButton.classList.add("elements-hidden");else if(componentButton.classList.remove("elements-hidden"),""!=componentButton.dataset.flavorlist){let variants=(0,_variantslib.getVariantsClass)(data.getComponentById(componentButton.dataset.id).name,currentFlavor);componentButton.querySelectorAll(".elements-button-variant").forEach((variant=>{updateVariantButtonState(variant,-1!=variants.indexOf(variant.dataset.variantclass))}))}}))},handleCategoryRemember=(event,modal)=>{const link=event.target;if(currentCategoryId=link.dataset.categoryid,currentCategoryName=link.dataset.categoryname,currentFlavorId=lastFlavor[currentCategoryId],null!=currentFlavorId){let e={target:modal.getRoot()[0].querySelector('.elements-category-flavor[data-id="'+currentFlavorId+'"]')};handleCategoryFlavorClick(e,modal)}},handleModalHidden=editor=>{editor.targetElm.closest("body").classList.remove("elements-modal-no-preview"),0!=currentCategoryId&&0!=currentFlavorId&&(0,_preferencelib.savePreferences)([{type:_preferencelib.Preferences.category,value:currentCategoryId},{type:_preferencelib.Preferences.category_flavors,value:JSON.stringify(lastFlavor)},{type:_preferencelib.Preferences.component_variants,value:JSON.stringify((0,_variantslib.getVariantPreferences)())}])},updateComponentCode=function(componentCode,selectedButton,placeholder){let flavor=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"";componentCode=componentCode.replace("{{PLACEHOLDER}}",placeholder);const comp=data.getComponentById(selectedButton),variants=(0,_variantslib.getVariantsClass)(comp.name,flavor);return componentCode=variants.length>0?(componentCode=componentCode.replace("{{VARIANTS}}",variants.join(" "))).replace("{{VARIANTSHTML}}",(0,_variantslib.getVariantsHtml)(comp.name,flavor)):(componentCode=componentCode.replace("{{VARIANTS}}","")).replace("{{VARIANTSHTML}}",""),componentCode=(componentCode=(componentCode=currentFlavor?componentCode.replace("{{FLAVOR}}","elements-"+currentFlavor+"-flavor"):componentCode.replace("{{FLAVOR}}","")).replace("{{COMPONENT}}","elements-"+comp.name)).replace("{{CATEGORY}}","elements-"+data.getCategoryById(currentCategoryId).name),componentCode=applyRandomID(componentCode),componentCode=applyLangStrings(componentCode)},handleButtonClick=async(event,editor,modal)=>{const selectedButton=event.target.closest("button").dataset.id,comp=data.getComponentById(selectedButton);if(comp){let componentCode=comp.code;const placeholder=selection.length>0?selection:comp.text;let flavor=comp.flavors.length>0?currentFlavor:"";const randomId=generateRandomID(),newNode=document.createElement("span");newNode.dataset.id=randomId,newNode.innerHTML=placeholder,componentCode=updateComponentCode(componentCode,selectedButton,newNode.outerHTML,flavor),editor.selection.setContent(componentCode);const nodeSel=editor.dom.select('span[data-id="'+randomId+'"]');null!=nodeSel&&nodeSel[0]&&editor.selection.select(nodeSel[0]),modal.destroy(),editor.focus(),editor.undoManager.add()}},handleButtonMouseEvent=(event,modal,show)=>{const selectedButton=event.target.closest("button").dataset.id,node=modal.getRoot()[0].querySelector('div[data-id="code-preview-'+selectedButton+'"]'),previewDefault=modal.getRoot()[0].querySelector('div[data-id="code-preview-default"]'),comp=data.getComponentById(selectedButton);let flavor=comp.flavors.length>0?currentFlavor:"";const placeholder=selection.length>0?selection:comp.text;node.innerHTML=updateComponentCode(comp.code,selectedButton,placeholder,flavor),node&&(show?(previewDefault.classList.toggle("elements-hidden"),node.classList.toggle("elements-hidden")):(node.classList.toggle("elements-hidden"),previewDefault.classList.toggle("elements-hidden")))},handleVariantClick=(event,modal)=>{event.stopPropagation();const variant=event.target.closest("span"),button=event.target.closest("button"),comp=data.getComponentById(button.dataset.id),flavor=comp.flavors.length>0?currentFlavor:"";updateVariantComponentState(variant,button,modal,!1,!0);modal.getRoot()[0].querySelector('div[data-id="code-preview-'+button.dataset.id+'"]').innerHTML=updateComponentCode(comp.code,button.dataset.id,comp.text,flavor)},updateVariantComponentState=(variant,button,modal,show,updateHtml)=>{const selectedVariant=variant.dataset.variantclass,selectedButton=button.dataset.id,componentClass=button.dataset.classcomponent,previewComponent=modal.getRoot()[0].querySelector('div[data-id="code-preview-'+button.dataset.id+'"] .'+componentClass),variantPreview=modal.getRoot()[0].querySelector('span[data-id="variantHTML-'+button.dataset.id+'"]'),comp=data.getComponentById(selectedButton);let variantsHtml="",hasflavors=comp.flavors.length>0;previewComponent?updateHtml?("on"==variant.dataset.state?((0,_variantslib.removeVariant)(comp.name,variant.dataset.variant,hasflavors?currentFlavor:""),updateVariantButtonState(variant,!1),previewComponent.classList.remove(selectedVariant)):((0,_variantslib.addVariant)(comp.name,variant.dataset.variant,hasflavors?currentFlavor:""),updateVariantButtonState(variant,!0),previewComponent.classList.add(selectedVariant)),variantPreview&&(variantPreview.innerHTML=(0,_variantslib.getVariantsHtml)(comp.name,currentFlavor))):(variantsHtml=(0,_variantslib.getVariantsHtml)(comp.name,currentFlavor),show?(previewComponent.classList.add(selectedVariant),variantsHtml+=(0,_variantslib.getVariantHtml)(variant.dataset.variant)):previewComponent.classList.remove(selectedVariant),variantPreview&&(variantPreview.innerHTML=variantsHtml)):"on"==variant.dataset.state?((0,_variantslib.removeVariant)(comp.name,variant.dataset.variant,hasflavors?currentFlavor:""),updateVariantButtonState(variant,!1)):((0,_variantslib.addVariant)(comp.name,variant.dataset.variant,hasflavors?currentFlavor:""),updateVariantButtonState(variant,!0))},updateVariantButtonState=(variant,activate)=>{activate?(variant.dataset.state="on",variant.classList.remove(variant.dataset.variant+"-variant-off"),variant.classList.add(variant.dataset.variant+"-variant-on"),variant.classList.add("on")):(variant.dataset.state="off",variant.classList.remove(variant.dataset.variant+"-variant-on"),variant.classList.add(variant.dataset.variant+"-variant-off"),variant.classList.remove("on"))},showCategoryButtons=(modal,context)=>{const showNodes=modal.getRoot()[0].querySelectorAll('button[data-type="'+context+'"]'),hideNodes=modal.getRoot()[0].querySelectorAll('button[data-type]:not([data-type="'+context+'"])');showNodes.forEach((node=>node.classList.remove("elements-hidden"))),hideNodes.forEach((node=>node.classList.add("elements-hidden")))},applyLangStrings=text=>([...text.matchAll(/{{#([^}]*)}}/g)].forEach((strLang=>{text=text.replace("{{#"+strLang[1]+"}}",data.getLangString(strLang[1]))})),text),generateRandomID=()=>{const timestamp=(new Date).getTime();return"R"+Math.round(1e5*Math.random())+"-"+timestamp},applyRandomID=text=>{const compRegex=/{{@ID}}/g;return text.match(compRegex)&&(text=text.replace(compRegex,generateRandomID())),text}}));
+ */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.handleAction=void 0,_modal_events=_interopRequireDefault(_modal_events),_data=_interopRequireDefault(_data);let currentFlavor="",currentFlavorId=0,currentCategoryId=1,currentCategoryName="",selectedComponentId=null,previewComponentId=null,stripComponentId=null,stripHideTimer=null,lastFlavor=[],selection="",data={};_exports.handleAction=async editor=>{selection=editor.selection.getContent(),data=new _data.default((0,_options2.getContextId)(editor),(0,_options.isStudent)(editor),(0,_options.showPreview)(editor),(0,_options.canManage)(editor)),await data.loadData(),(0,_variantslib.setData)(data),currentCategoryId=await(0,_preferencelib.loadPreferences)(_preferencelib.Preferences.category),lastFlavor=await(0,_preferencelib.loadPreferences)(_preferencelib.Preferences.category_flavors),null==lastFlavor&&(lastFlavor=[]);let componentVariants=await(0,_preferencelib.loadPreferences)(_preferencelib.Preferences.component_variants);null==componentVariants&&(componentVariants={}),(0,_variantslib.loadVariantPreferences)(componentVariants),await displayDialogue(editor)};const displayDialogue=async editor=>{const templateContext=data.getTemplateContext(editor),modal=await _modal.ElementsModal.create({type:_modal.ElementsModal.TYPE,templateContext:templateContext,large:!0}),modalClass=data.getPreviewElements()?"elements-modal":"elements-modal-no-preview";editor.targetElm.closest("body").classList.add(modalClass),modal.show();const root=modal.getRoot()[0];modal.getRoot().on(_modal_events.default.hidden,(()=>{handleModalHidden(editor)})),root.querySelectorAll(".elements-rail-item").forEach((node=>{node.addEventListener("click",(event=>handleRailClick(event,modal)))})),root.querySelectorAll(".elements-flavor-chip").forEach((node=>{node.addEventListener("click",(event=>handleFlavorChipClick(event,modal)))})),root.querySelectorAll(".elementst-dialog-button").forEach((node=>{node.addEventListener("click",(event=>handleCardClick(event,modal))),node.addEventListener("dblclick",(event=>handleCardDblClick(event,editor,modal))),data.getPreviewElements()&&(node.addEventListener("mouseenter",(event=>handleCardMouseEvent(event,modal,!0))),node.addEventListener("mouseleave",(event=>handleCardMouseEvent(event,modal,!1))));const dotsContainer=node.querySelector(".elements-v-dots");dotsContainer&&(dotsContainer.addEventListener("mouseenter",(()=>showVariantStripFor(modal,node.dataset.id))),dotsContainer.addEventListener("mouseleave",(()=>scheduleHideVariantStrip(modal)))),node.querySelectorAll(".elements-v-dot").forEach((dot=>{dot.addEventListener("click",(event=>handleDotClick(event,modal)))}))}));const strip=root.querySelector('[data-region="variant-strip"]');strip&&(strip.addEventListener("mouseenter",(()=>cancelHideVariantStrip())),strip.addEventListener("mouseleave",(()=>scheduleHideVariantStrip(modal))));const insertBtn=root.querySelector(".elements-insert-btn");insertBtn&&insertBtn.addEventListener("click",(()=>handleInsertClick(editor,modal))),restoreInitialSelection(modal)},restoreInitialSelection=modal=>{const root=modal.getRoot()[0],railItems=root.querySelectorAll(".elements-rail-item");if(0===railItems.length)return;let target=null;currentCategoryId&&(target=root.querySelector('.elements-rail-item[data-categoryid="'.concat(currentCategoryId,'"]'))),target||(target=railItems[0]),target.click();const savedFlavorId=lastFlavor[target.dataset.categoryid];if(savedFlavorId){const flavorChip=root.querySelector('.elements-flavor-chip[data-id="'.concat(savedFlavorId,'"][data-categoryid="').concat(target.dataset.categoryid,'"]'));flavorChip&&flavorChip.click()}},handleRailClick=(event,modal)=>{const item=event.currentTarget;currentCategoryId=item.dataset.categoryid,currentCategoryName=item.dataset.categoryname,currentFlavor="",currentFlavorId=0;const root=modal.getRoot()[0];root.querySelectorAll(".elements-rail-item").forEach((node=>{const isActive=node===item;node.classList.toggle("active",isActive),node.setAttribute("aria-selected",isActive?"true":"false")})),root.querySelectorAll(".elements-flavor-list").forEach((set=>{set.toggleAttribute("hidden",set.dataset.categoryid!==currentCategoryId)})),root.querySelectorAll(".elements-flavor-chip").forEach((chip=>chip.classList.remove("active")));const noneChip=root.querySelector('.elements-flavor-list[data-categoryid="'.concat(currentCategoryId,'"] .elements-flavor-none'));noneChip&&noneChip.classList.add("active"),showCategoryButtons(modal,currentCategoryName),clearSelection(modal)},handleFlavorChipClick=(event,modal)=>{const chip=event.currentTarget;currentFlavor=chip.dataset.flavor||"",currentFlavorId=chip.dataset.id?parseInt(chip.dataset.id,10):0,currentCategoryId=chip.dataset.categoryid,currentCategoryName=chip.dataset.categoryname,currentFlavorId?lastFlavor[currentCategoryId]=currentFlavorId:delete lastFlavor[currentCategoryId];const root=modal.getRoot()[0];root.querySelectorAll('.elements-flavor-list[data-categoryid="'.concat(currentCategoryId,'"] .elements-flavor-chip')).forEach((c=>c.classList.remove("active"))),chip.classList.add("active");if(root.querySelectorAll(".elementst-dialog-button").forEach((button=>{const matchesCategory=button.dataset.category===currentCategoryName,flavorlist=button.dataset.flavorlist||"",matchesFlavor=!currentFlavor||""===flavorlist||flavorlist.split(",").includes(currentFlavor),visible=matchesCategory&&matchesFlavor;button.classList.toggle("elements-hidden",!visible),visible&&(button.dataset.flavor=currentFlavor,refreshCardDots(button))})),selectedComponentId){root.querySelector('.elementst-dialog-button[data-id="'.concat(selectedComponentId,'"]:not(.elements-hidden)'))?(updatePreviewSummary(modal),refreshSelectedPreview(modal)):clearSelection(modal)}hideVariantStrip(modal)},handleCardClick=(event,modal)=>{if(event.target.closest(".elements-v-dot"))return;const button=event.currentTarget,root=modal.getRoot()[0];selectedComponentId=button.dataset.id,root.querySelectorAll(".elementst-dialog-button.selected").forEach((node=>{node.classList.remove("selected"),node.setAttribute("aria-selected","false")})),button.classList.add("selected"),button.setAttribute("aria-selected","true"),refreshSelectedPreview(modal),updatePreviewSummary(modal);const insertBtn=root.querySelector(".elements-insert-btn");insertBtn&&(insertBtn.disabled=!1)},handleCardDblClick=(event,editor,modal)=>{event.target.closest(".elements-v-dot")||(selectedComponentId=event.currentTarget.dataset.id,handleInsertClick(editor,modal))},handleCardMouseEvent=(event,modal,show)=>{const button=event.currentTarget;showPreviewFor(modal,show?button.dataset.id:selectedComponentId||null)},showPreviewFor=(modal,componentId)=>{const previewBody=modal.getRoot()[0].querySelector(".elements-preview-body");if(!previewBody)return void(previewComponentId=null);if(previewBody.querySelectorAll(".elements-component-code, .elements-preview-default").forEach((node=>{node.classList.add("elements-hidden")})),!componentId){const defaultHint=previewBody.querySelector(".elements-preview-default");return defaultHint&&defaultHint.classList.remove("elements-hidden"),void(previewComponentId=null)}const comp=data.getComponentById(componentId);if(!comp)return void(previewComponentId=null);const node=previewBody.querySelector('div[data-id="code-preview-'.concat(componentId,'"]'));if(!node)return void(previewComponentId=null);const flavor=comp.flavors.length>0?currentFlavor:"",placeholder=selection.length>0?selection:comp.text;node.innerHTML=updateComponentCode(comp.code,componentId,placeholder,flavor),node.classList.remove("elements-hidden"),previewComponentId=componentId},refreshSelectedPreview=modal=>{showPreviewFor(modal,selectedComponentId||null)},showVariantStripFor=(modal,componentId)=>{cancelHideVariantStrip();const root=modal.getRoot()[0],strip=root.querySelector('[data-region="variant-strip"]'),target=root.querySelector('[data-region="variant-target"]'),chips=root.querySelector('[data-region="variant-chips"]');if(!strip||!chips)return;const comp=data.getComponentById(componentId);if(!comp)return;const variants=data.getComponentVariants(comp);if(0===variants.length)return void strip.setAttribute("hidden","");if(stripComponentId===componentId&&!strip.hasAttribute("hidden"))return;stripComponentId=componentId;const flavor=comp.flavors.length>0?currentFlavor:"",activeClasses=(0,_variantslib.getVariantsClass)(comp.name,flavor);chips.innerHTML="",target&&(target.textContent=comp.displayname),variants.forEach((variant=>{const isOn=activeClasses.includes(variant.variantclass),chip=document.createElement("button");chip.type="button",chip.className="elements-variant-chip "+(isOn?"active":""),chip.dataset.variant=variant.name,chip.dataset.variantclass=variant.variantclass,chip.dataset.state=isOn?"on":"off",chip.title=variant.displayname||variant.name,chip.textContent=variant.displayname||variant.name,chip.addEventListener("click",(()=>handleVariantChipClick(chip,modal))),chips.appendChild(chip)})),strip.removeAttribute("hidden")},scheduleHideVariantStrip=modal=>{cancelHideVariantStrip(),stripHideTimer=setTimeout((()=>hideVariantStrip(modal)),150)},cancelHideVariantStrip=()=>{stripHideTimer&&(clearTimeout(stripHideTimer),stripHideTimer=null)},hideVariantStrip=modal=>{cancelHideVariantStrip();const strip=modal.getRoot()[0].querySelector('[data-region="variant-strip"]');strip&&strip.setAttribute("hidden",""),stripComponentId=null},handleDotClick=(event,modal)=>{event.stopPropagation(),event.preventDefault();const dot=event.currentTarget,card=dot.closest(".elementst-dialog-button");card&&toggleVariantForComponent(modal,card.dataset.id,dot.dataset.variant,dot.dataset.variantclass)},handleVariantChipClick=(chip,modal)=>{stripComponentId&&toggleVariantForComponent(modal,stripComponentId,chip.dataset.variant,chip.dataset.variantclass)},toggleVariantForComponent=(modal,componentId,variantName,variantClass)=>{const comp=data.getComponentById(componentId);if(!comp)return;const flavor=comp.flavors.length>0?currentFlavor:"",turningOn=!(0,_variantslib.getVariantsClass)(comp.name,flavor).includes(variantClass);turningOn?(0,_variantslib.addVariant)(comp.name,variantName,flavor):(0,_variantslib.removeVariant)(comp.name,variantName,flavor);const card=modal.getRoot()[0].querySelector('.elementst-dialog-button[data-id="'.concat(componentId,'"]'));if(card){const dot=card.querySelector('.elements-v-dot[data-variantclass="'.concat(variantClass,'"]'));dot&&dot.classList.toggle("on",turningOn)}if(stripComponentId===componentId){const chip=modal.getRoot()[0].querySelector('.elements-variant-chip[data-variantclass="'.concat(variantClass,'"]'));chip&&(chip.dataset.state=turningOn?"on":"off",chip.classList.toggle("active",turningOn))}previewComponentId===componentId&&showPreviewFor(modal,componentId),updatePreviewSummary(modal)},refreshCardDots=card=>{const comp=data.getComponentById(card.dataset.id);if(!comp)return;const flavor=comp.flavors.length>0?currentFlavor:"",activeClasses=(0,_variantslib.getVariantsClass)(comp.name,flavor);card.querySelectorAll(".elements-v-dot").forEach((dot=>{dot.classList.toggle("on",activeClasses.includes(dot.dataset.variantclass))}))},updatePreviewSummary=modal=>{const root=modal.getRoot()[0],summary=root.querySelector('[data-region="preview-summary"]');if(!summary)return;if(!selectedComponentId)return void(summary.textContent="");const comp=data.getComponentById(selectedComponentId);if(!comp)return void(summary.textContent="");const parts=[comp.displayname],cat=data.getCategoryById(currentCategoryId);if(cat&&parts.push(cat.displayname),currentFlavor){const chip=root.querySelector('.elements-flavor-chip[data-flavor="'.concat(currentFlavor,'"][data-categoryid="').concat(currentCategoryId,'"]'));chip&&parts.push(chip.textContent.trim())}summary.textContent=parts.join(" · ")},clearSelection=modal=>{selectedComponentId=null;const root=modal.getRoot()[0];root.querySelectorAll(".elementst-dialog-button.selected").forEach((node=>{node.classList.remove("selected"),node.setAttribute("aria-selected","false")}));const strip=root.querySelector('[data-region="variant-strip"]');strip&&strip.setAttribute("hidden","");const chips=root.querySelector('[data-region="variant-chips"]');chips&&(chips.innerHTML="");const insertBtn=root.querySelector(".elements-insert-btn");insertBtn&&(insertBtn.disabled=!0),showPreviewFor(modal,null),updatePreviewSummary(modal)},handleInsertClick=(editor,modal)=>{if(!selectedComponentId)return;const comp=data.getComponentById(selectedComponentId);if(!comp)return;const flavor=comp.flavors.length>0?currentFlavor:"",placeholder=selection.length>0?selection:comp.text,randomId=generateRandomID(),newNode=document.createElement("span");newNode.dataset.id=randomId,newNode.innerHTML=placeholder;const componentCode=updateComponentCode(comp.code,selectedComponentId,newNode.outerHTML,flavor);editor.selection.setContent(componentCode);const nodeSel=editor.dom.select('span[data-id="'+randomId+'"]');null!=nodeSel&&nodeSel[0]&&editor.selection.select(nodeSel[0]),modal.destroy(),editor.focus(),editor.undoManager.add()},handleModalHidden=editor=>{editor.targetElm.closest("body").classList.remove("elements-modal-no-preview"),currentCategoryId&&(0,_preferencelib.savePreferences)([{type:_preferencelib.Preferences.category,value:currentCategoryId},{type:_preferencelib.Preferences.category_flavors,value:JSON.stringify(lastFlavor)},{type:_preferencelib.Preferences.component_variants,value:JSON.stringify((0,_variantslib.getVariantPreferences)())}])},updateComponentCode=function(componentCode,selectedButton,placeholder){let flavor=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"";componentCode=componentCode.replace("{{PLACEHOLDER}}",placeholder);const comp=data.getComponentById(selectedButton),variants=(0,_variantslib.getVariantsClass)(comp.name,flavor);return componentCode=variants.length>0?(componentCode=componentCode.replace("{{VARIANTS}}",variants.join(" "))).replace("{{VARIANTSHTML}}",(0,_variantslib.getVariantsHtml)(comp.name,flavor)):(componentCode=componentCode.replace("{{VARIANTS}}","")).replace("{{VARIANTSHTML}}",""),componentCode=(componentCode=(componentCode=currentFlavor?componentCode.replace("{{FLAVOR}}","elements-"+currentFlavor+"-flavor"):componentCode.replace("{{FLAVOR}}","")).replace("{{COMPONENT}}","elements-"+comp.name)).replace("{{CATEGORY}}","elements-"+data.getCategoryById(currentCategoryId).name),componentCode=applyRandomID(componentCode),componentCode=applyLangStrings(componentCode)},showCategoryButtons=(modal,context)=>{const showNodes=modal.getRoot()[0].querySelectorAll('button[data-type="'.concat(context,'"]')),hideNodes=modal.getRoot()[0].querySelectorAll('button[data-type]:not([data-type="'.concat(context,'"])'));showNodes.forEach((node=>{node.classList.remove("elements-hidden"),refreshCardDots(node)})),hideNodes.forEach((node=>node.classList.add("elements-hidden")))},applyLangStrings=text=>([...text.matchAll(/{{#([^}]*)}}/g)].forEach((strLang=>{text=text.replace("{{#"+strLang[1]+"}}",data.getLangString(strLang[1]))})),text),generateRandomID=()=>{const timestamp=(new Date).getTime();return"R"+Math.round(1e5*Math.random())+"-"+timestamp},applyRandomID=text=>{const compRegex=/{{@ID}}/g;return text.match(compRegex)&&(text=text.replace(compRegex,generateRandomID())),text}}));
//# sourceMappingURL=ui.min.js.map
\ No newline at end of file
diff --git a/amd/build/ui.min.js.map b/amd/build/ui.min.js.map
index bb9411b..c0182be 100644
--- a/amd/build/ui.min.js.map
+++ b/amd/build/ui.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"ui.min.js","sources":["../src/ui.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny Elements UI.\n *\n * @module tiny_elements/ui\n * @copyright 2025 ISB Bayern\n * @copyright based on the work of Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {ElementsModal} from 'tiny_elements/modal';\nimport {\n isStudent,\n showPreview,\n canManage\n} from 'tiny_elements/options';\nimport ModalEvents from 'core/modal_events';\nimport {\n addVariant,\n getVariantsClass,\n getVariantHtml,\n getVariantPreferences,\n getVariantsHtml,\n loadVariantPreferences,\n removeVariant,\n setData as setVariantsData\n} from 'tiny_elements/variantslib';\nimport {\n savePreferences,\n loadPreferences,\n Preferences\n} from 'tiny_elements/preferencelib';\nimport {getContextId} from 'editor_tiny/options';\nimport Data from 'tiny_elements/data';\n\nlet currentFlavor = '';\nlet currentFlavorId = 0;\nlet currentCategoryId = 1;\nlet currentCategoryName = '';\nlet lastFlavor = [];\nlet selection = '';\nlet data = {};\n\n/**\n * Handle action\n *\n * @param {TinyMCE} editor\n */\nexport const handleAction = async(editor) => {\n selection = editor.selection.getContent();\n data = new Data(\n getContextId(editor),\n isStudent(editor),\n showPreview(editor),\n canManage(editor)\n );\n await data.loadData();\n setVariantsData(data);\n\n currentCategoryId = await loadPreferences(Preferences.category);\n\n lastFlavor = await loadPreferences(Preferences.category_flavors);\n if (lastFlavor === null || lastFlavor === undefined) {\n lastFlavor = [];\n }\n\n let componentVariants = await loadPreferences(Preferences.component_variants);\n if (componentVariants === null || componentVariants === undefined) {\n componentVariants = {};\n }\n loadVariantPreferences(componentVariants);\n await displayDialogue(editor);\n};\n\n/**\n * Display modal\n *\n * @param {TinyMCE} editor\n */\nconst displayDialogue = async(editor) => {\n const templateContext = data.getTemplateContext(editor);\n // Show modal with buttons.\n const modal = await ElementsModal.create({\n type: ElementsModal.TYPE,\n templateContext: templateContext,\n large: true,\n });\n\n // Choose class to modal.\n const modalClass = data.getPreviewElements() ? 'elements-modal' : 'elements-modal-no-preview';\n\n // Set class to modal.\n editor.targetElm.closest('body').classList.add(modalClass);\n\n modal.show();\n\n // Event modal listener.\n modal.getRoot().on(ModalEvents.hidden, () => {\n handleModalHidden(editor);\n });\n\n // Event listener for categories without flavors.\n const soleCategories = modal.getRoot()[0].querySelectorAll('.elements-category.no-flavors');\n soleCategories.forEach(node => {\n node.addEventListener('click', (event) => {\n handleCategoryClick(event, modal);\n });\n });\n\n // Event listener for categories with flavors.\n const selectCategories = modal.getRoot()[0].querySelectorAll('.elements-category-flavor');\n selectCategories.forEach(node => {\n node.addEventListener('click', (event) => {\n handleCategoryFlavorClick(event, modal);\n });\n });\n\n // Event listener for category dropdown, triggering to switch to last used flavor.\n const selectCategoriesRemember = modal.getRoot()[0].querySelectorAll('.nav-link.dropdown-toggle');\n selectCategoriesRemember.forEach(node => {\n node.addEventListener('click', (event) => {\n handleCategoryRemember(event, modal);\n });\n });\n\n // Event buttons listeners.\n const buttons = modal.getRoot()[0].querySelectorAll('.elementst-dialog-button');\n buttons.forEach(node => {\n node.addEventListener('click', (event) => {\n handleButtonClick(event, editor, modal);\n });\n if (data.getPreviewElements()) {\n node.addEventListener('mouseenter', (event) => {\n handleButtonMouseEvent(event, modal, true);\n });\n node.addEventListener('mouseleave', (event) => {\n handleButtonMouseEvent(event, modal, false);\n });\n }\n });\n\n // Event variants listeners.\n const variants = modal.getRoot()[0].querySelectorAll('.elements-button-variant');\n variants.forEach(node => {\n node.addEventListener('click', (event) => {\n handleVariantClick(event, modal);\n });\n });\n\n // Select first or saved category.\n if (soleCategories.length > 0 || selectCategories.length > 0) {\n const savedCategory = currentCategoryId;\n const savedFlavor = lastFlavor[currentCategoryId];\n if (soleCategories.length == 0 || soleCategories[0].displayorder > selectCategories[0].displayorder) {\n selectCategories[0].click();\n } else {\n soleCategories[0].click();\n }\n if (savedCategory != 0) {\n soleCategories.forEach((node) => {\n if (node.dataset.categoryid == savedCategory) {\n node.click();\n }\n });\n selectCategories.forEach((node) => {\n if (node.dataset.categoryid == savedCategory) {\n node.click();\n }\n });\n if (savedFlavor) {\n const flavorlink = modal.getRoot()[0].querySelector(\n '.elements-category-flavor[data-id=\"' + savedFlavor + '\"]'\n );\n if (flavorlink) {\n flavorlink.click();\n }\n }\n }\n }\n};\n\n/**\n * Handle a click within filter button.\n *\n * @param {MouseEvent} event The change event\n * @param {obj} modal\n */\nconst handleCategoryClick = (event, modal) => {\n const link = event.target;\n currentCategoryId = link.dataset.categoryid;\n currentCategoryName = link.dataset.categoryname;\n\n // Remove active from all and set to selected.\n const links = modal.getRoot()[0].querySelectorAll('.nav-link, .dropdown-item');\n links.forEach(node => node.classList.remove('active'));\n link.classList.add('active');\n\n // Show/hide component buttons.\n showCategoryButtons(modal, currentCategoryName);\n};\n\n/**\n * Handle a click on a flavor in the category dropdown.\n *\n * @param {MouseEvent} event The change event\n * @param {obj} modal\n */\nconst handleCategoryFlavorClick = (event, modal) => {\n const link = event.target;\n currentFlavor = link.dataset.flavor;\n currentFlavorId = link.dataset.id;\n currentCategoryId = link.dataset.categoryid;\n currentCategoryName = link.dataset.categoryname;\n lastFlavor[currentCategoryId] = currentFlavorId;\n\n // Remove active from all and set to selected.\n const links = modal.getRoot()[0].querySelectorAll('.nav-link, .dropdown-item');\n links.forEach(node => node.classList.remove('active'));\n link.classList.add('active');\n const category = modal.getRoot()[0].querySelector('.nav-link[data-categoryid=\"' + currentCategoryId + '\"]');\n category.classList.add('active');\n\n const componentButtons = modal.getRoot()[0].querySelectorAll('.elements-buttons-preview button');\n componentButtons.forEach(componentButton => {\n componentButton.dataset.flavor = currentFlavor;\n if (\n (componentButton.dataset.flavorlist == '' || componentButton.dataset.flavorlist.split(',').includes(currentFlavor)) &&\n componentButton.dataset.category == currentCategoryName\n ) {\n componentButton.classList.remove('elements-hidden');\n if (componentButton.dataset.flavorlist != '') {\n let variants = getVariantsClass(data.getComponentById(componentButton.dataset.id).name, currentFlavor);\n let availableVariants = componentButton.querySelectorAll('.elements-button-variant');\n availableVariants.forEach((variant) => {\n updateVariantButtonState(variant, variants.indexOf(variant.dataset.variantclass) != -1);\n });\n }\n } else {\n componentButton.classList.add('elements-hidden');\n }\n });\n\n};\n\n/**\n * When opening the category dropdown, try to load remembered flavor.\n *\n * @param {MouseEvent} event The change event\n * @param {obj} modal\n */\nconst handleCategoryRemember = (event, modal) => {\n const link = event.target;\n currentCategoryId = link.dataset.categoryid;\n currentCategoryName = link.dataset.categoryname;\n currentFlavorId = lastFlavor[currentCategoryId];\n\n if (currentFlavorId != undefined) {\n // Call handleCategoryFlavorClick with tampered data.\n let e = {target: modal.getRoot()[0].querySelector('.elements-category-flavor[data-id=\"' + currentFlavorId + '\"]')};\n handleCategoryFlavorClick(e, modal);\n }\n};\n\n/**\n * Handle when closing the Modal.\n *\n * @param {obj} editor\n */\nconst handleModalHidden = (editor) => {\n editor.targetElm.closest('body').classList.remove('elements-modal-no-preview');\n if (currentCategoryId != 0 && currentFlavorId != 0) {\n savePreferences([\n {type: Preferences.category, value: currentCategoryId},\n {type: Preferences.category_flavors, value: JSON.stringify(lastFlavor)},\n {type: Preferences.component_variants, value: JSON.stringify(getVariantPreferences())}\n ]);\n }\n};\n\nconst updateComponentCode = (componentCode, selectedButton, placeholder, flavor = '') => {\n componentCode = componentCode.replace('{{PLACEHOLDER}}', placeholder);\n const comp = data.getComponentById(selectedButton);\n // Return active variants for current component.\n const variants = getVariantsClass(comp.name, flavor);\n\n // Apply variants to html component.\n if (variants.length > 0) {\n componentCode = componentCode.replace('{{VARIANTS}}', variants.join(' '));\n componentCode = componentCode.replace('{{VARIANTSHTML}}', getVariantsHtml(comp.name, flavor));\n } else {\n componentCode = componentCode.replace('{{VARIANTS}}', '');\n componentCode = componentCode.replace('{{VARIANTSHTML}}', '');\n }\n\n if (currentFlavor) {\n componentCode = componentCode.replace('{{FLAVOR}}', 'elements-' + currentFlavor + '-flavor');\n } else {\n componentCode = componentCode.replace('{{FLAVOR}}', '');\n }\n\n componentCode = componentCode.replace('{{COMPONENT}}', 'elements-' + comp.name);\n componentCode = componentCode.replace('{{CATEGORY}}', 'elements-' + data.getCategoryById(currentCategoryId).name);\n\n // Apply random IDs.\n componentCode = applyRandomID(componentCode);\n\n // Apply lang strings.\n componentCode = applyLangStrings(componentCode);\n\n return componentCode;\n};\n\n/**\n * Handle a click in a component button.\n *\n * @param {MouseEvent} event The click event\n * @param {obj} editor\n * @param {obj} modal\n */\nconst handleButtonClick = async(event, editor, modal) => {\n const selectedButton = event.target.closest('button').dataset.id;\n\n const comp = data.getComponentById(selectedButton);\n\n // Component button.\n if (comp) {\n let componentCode = comp.code;\n const placeholder = (selection.length > 0 ? selection : comp.text);\n\n let flavor = comp.flavors.length > 0 ? currentFlavor : '';\n\n // Create a new node to replace the placeholder.\n const randomId = generateRandomID();\n const newNode = document.createElement('span');\n newNode.dataset.id = randomId;\n newNode.innerHTML = placeholder;\n componentCode = updateComponentCode(componentCode, selectedButton, newNode.outerHTML, flavor);\n // Sets new content.\n editor.selection.setContent(componentCode);\n\n // Select text.\n const nodeSel = editor.dom.select('span[data-id=\"' + randomId + '\"]');\n if (nodeSel?.[0]) {\n editor.selection.select(nodeSel[0]);\n }\n\n modal.destroy();\n editor.focus();\n editor.undoManager.add();\n }\n};\n\n/**\n * Handle a mouse events mouseenter/mouseleave in a component button.\n *\n * @param {MouseEvent} event The click event\n * @param {obj} modal\n * @param {bool} show\n */\nconst handleButtonMouseEvent = (event, modal, show) => {\n const selectedButton = event.target.closest('button').dataset.id;\n const node = modal.getRoot()[0].querySelector('div[data-id=\"code-preview-' + selectedButton + '\"]');\n const previewDefault = modal.getRoot()[0].querySelector('div[data-id=\"code-preview-default\"]');\n const comp = data.getComponentById(selectedButton);\n\n let flavor = comp.flavors.length > 0 ? currentFlavor : '';\n\n const placeholder = (selection.length > 0 ? selection : comp.text);\n\n node.innerHTML = updateComponentCode(comp.code, selectedButton, placeholder, flavor);\n\n if (node) {\n if (show) {\n previewDefault.classList.toggle('elements-hidden');\n node.classList.toggle('elements-hidden');\n } else {\n node.classList.toggle('elements-hidden');\n previewDefault.classList.toggle('elements-hidden');\n }\n }\n};\n\n/**\n * Handle a mouse event within the variant buttons.\n *\n * @param {MouseEvent} event The mouseenter/mouseleave event\n * @param {obj} modal\n */\nconst handleVariantClick = (event, modal) => {\n event.stopPropagation();\n const variant = event.target.closest('span');\n const button = event.target.closest('button');\n const comp = data.getComponentById(button.dataset.id);\n const flavor = comp.flavors.length > 0 ? currentFlavor : '';\n\n updateVariantComponentState(variant, button, modal, false, true);\n\n const node = modal.getRoot()[0].querySelector('div[data-id=\"code-preview-' + button.dataset.id + '\"]');\n node.innerHTML = updateComponentCode(\n comp.code,\n button.dataset.id,\n comp.text,\n flavor\n );\n};\n\n/**\n * Update a variant component UI.\n *\n * @param {obj} variant\n * @param {obj} button\n * @param {obj} modal\n * @param {bool} show\n * @param {bool} updateHtml\n */\nconst updateVariantComponentState = (variant, button, modal, show, updateHtml) => {\n const selectedVariant = variant.dataset.variantclass;\n const selectedButton = button.dataset.id;\n const componentClass = button.dataset.classcomponent;\n const previewComponent = modal.getRoot()[0]\n .querySelector('div[data-id=\"code-preview-' + button.dataset.id + '\"] .' + componentClass);\n const variantPreview = modal.getRoot()[0]\n .querySelector('span[data-id=\"variantHTML-' + button.dataset.id + '\"]');\n const comp = data.getComponentById(selectedButton);\n let variantsHtml = '';\n let hasflavors = comp.flavors.length > 0;\n\n if (previewComponent) {\n if (updateHtml) {\n if (variant.dataset.state == 'on') {\n removeVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : '');\n updateVariantButtonState(variant, false);\n previewComponent.classList.remove(selectedVariant);\n } else {\n addVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : '');\n updateVariantButtonState(variant, true);\n previewComponent.classList.add(selectedVariant);\n }\n\n // Update variant preview HTML.\n if (variantPreview) {\n variantPreview.innerHTML = getVariantsHtml(comp.name, currentFlavor);\n }\n } else {\n variantsHtml = getVariantsHtml(comp.name, currentFlavor);\n if (show) {\n previewComponent.classList.add(selectedVariant);\n variantsHtml += getVariantHtml(variant.dataset.variant);\n } else {\n previewComponent.classList.remove(selectedVariant);\n }\n\n // Update variant preview HTML.\n if (variantPreview) {\n variantPreview.innerHTML = variantsHtml;\n }\n }\n } else {\n // Update variants preferences.\n if (variant.dataset.state == 'on') {\n removeVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : '');\n updateVariantButtonState(variant, false);\n } else {\n addVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : '');\n updateVariantButtonState(variant, true);\n }\n }\n};\n\n/**\n * Update a variant button UI.\n *\n * @param {obj} variant\n * @param {bool} activate\n */\nconst updateVariantButtonState = (variant, activate) => {\n if (activate) {\n variant.dataset.state = 'on';\n variant.classList.remove(variant.dataset.variant + '-variant-off');\n variant.classList.add(variant.dataset.variant + '-variant-on');\n variant.classList.add('on');\n } else {\n variant.dataset.state = 'off';\n variant.classList.remove(variant.dataset.variant + '-variant-on');\n variant.classList.add(variant.dataset.variant + '-variant-off');\n variant.classList.remove('on');\n }\n};\n\n/**\n * Show/hide buttons depend on selected context.\n *\n * @param {object} modal\n * @param {String} context\n */\nconst showCategoryButtons = (modal, context) => {\n const showNodes = modal.getRoot()[0].querySelectorAll('button[data-type=\"' + context + '\"]');\n const hideNodes = modal.getRoot()[0].querySelectorAll('button[data-type]:not([data-type=\"' + context + '\"])');\n\n showNodes.forEach(node => node.classList.remove('elements-hidden'));\n hideNodes.forEach(node => node.classList.add('elements-hidden'));\n};\n\n/**\n * Replace all localized strings.\n *\n * @param {String} text\n * @return {String} String with lang tags replaced with a localized string.\n */\nconst applyLangStrings = (text) => {\n const compRegex = /{{#([^}]*)}}/g;\n\n [...text.matchAll(compRegex)].forEach(strLang => {\n text = text.replace('{{#' + strLang[1] + '}}', data.getLangString(strLang[1]));\n });\n\n return text;\n};\n\n/**\n * Generates a random string.\n * @return {string} A random string\n */\nconst generateRandomID = () => {\n const timestamp = new Date().getTime();\n return 'R' + Math.round(Math.random() * 100000) + '-' + timestamp;\n};\n\n/**\n * Replace all ID tags with a random string.\n * @param {String} text\n * @return {String} String with all ID tags replaced with a random string.\n */\nconst applyRandomID = (text) => {\n const compRegex = /{{@ID}}/g;\n\n if (text.match(compRegex)) {\n text = text.replace(compRegex, generateRandomID());\n }\n\n return text;\n};\n"],"names":["currentFlavor","currentFlavorId","currentCategoryId","currentCategoryName","lastFlavor","selection","data","async","editor","getContent","Data","loadData","Preferences","category","category_flavors","componentVariants","component_variants","displayDialogue","templateContext","getTemplateContext","modal","ElementsModal","create","type","TYPE","large","modalClass","getPreviewElements","targetElm","closest","classList","add","show","getRoot","on","ModalEvents","hidden","handleModalHidden","soleCategories","querySelectorAll","forEach","node","addEventListener","event","handleCategoryClick","selectCategories","handleCategoryFlavorClick","handleCategoryRemember","handleButtonClick","handleButtonMouseEvent","handleVariantClick","length","savedCategory","savedFlavor","displayorder","click","dataset","categoryid","flavorlink","querySelector","link","target","categoryname","remove","showCategoryButtons","flavor","id","componentButton","flavorlist","split","includes","variants","getComponentById","name","variant","updateVariantButtonState","indexOf","variantclass","undefined","e","value","JSON","stringify","updateComponentCode","componentCode","selectedButton","placeholder","replace","comp","join","getCategoryById","applyRandomID","applyLangStrings","code","text","flavors","randomId","generateRandomID","newNode","document","createElement","innerHTML","outerHTML","setContent","nodeSel","dom","select","destroy","focus","undoManager","previewDefault","toggle","stopPropagation","button","updateVariantComponentState","updateHtml","selectedVariant","componentClass","classcomponent","previewComponent","variantPreview","variantsHtml","hasflavors","state","activate","context","showNodes","hideNodes","matchAll","strLang","getLangString","timestamp","Date","getTime","Math","round","random","compRegex","match"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"ui.min.js","sources":["../src/ui.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny Elements UI.\n *\n * @module tiny_elements/ui\n * @copyright 2025 ISB Bayern\n * @copyright based on the work of Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {ElementsModal} from 'tiny_elements/modal';\nimport {\n isStudent,\n showPreview,\n canManage\n} from 'tiny_elements/options';\nimport ModalEvents from 'core/modal_events';\nimport {\n addVariant,\n getVariantsClass,\n getVariantPreferences,\n getVariantsHtml,\n loadVariantPreferences,\n removeVariant,\n setData as setVariantsData\n} from 'tiny_elements/variantslib';\nimport {\n savePreferences,\n loadPreferences,\n Preferences\n} from 'tiny_elements/preferencelib';\nimport {getContextId} from 'editor_tiny/options';\nimport Data from 'tiny_elements/data';\n\nlet currentFlavor = '';\nlet currentFlavorId = 0;\nlet currentCategoryId = 1;\nlet currentCategoryName = '';\nlet selectedComponentId = null;\nlet previewComponentId = null;\nlet stripComponentId = null;\nlet stripHideTimer = null;\nlet lastFlavor = [];\nlet selection = '';\nlet data = {};\n\n/**\n * Handle action.\n *\n * @param {TinyMCE} editor\n */\nexport const handleAction = async(editor) => {\n selection = editor.selection.getContent();\n data = new Data(\n getContextId(editor),\n isStudent(editor),\n showPreview(editor),\n canManage(editor)\n );\n await data.loadData();\n setVariantsData(data);\n\n currentCategoryId = await loadPreferences(Preferences.category);\n\n lastFlavor = await loadPreferences(Preferences.category_flavors);\n if (lastFlavor === null || lastFlavor === undefined) {\n lastFlavor = [];\n }\n\n let componentVariants = await loadPreferences(Preferences.component_variants);\n if (componentVariants === null || componentVariants === undefined) {\n componentVariants = {};\n }\n loadVariantPreferences(componentVariants);\n await displayDialogue(editor);\n};\n\n/**\n * Display modal.\n *\n * @param {TinyMCE} editor\n */\nconst displayDialogue = async(editor) => {\n const templateContext = data.getTemplateContext(editor);\n const modal = await ElementsModal.create({\n type: ElementsModal.TYPE,\n templateContext: templateContext,\n large: true,\n });\n\n const modalClass = data.getPreviewElements() ? 'elements-modal' : 'elements-modal-no-preview';\n editor.targetElm.closest('body').classList.add(modalClass);\n\n modal.show();\n\n const root = modal.getRoot()[0];\n\n modal.getRoot().on(ModalEvents.hidden, () => {\n handleModalHidden(editor);\n });\n\n root.querySelectorAll('.elements-rail-item').forEach(node => {\n node.addEventListener('click', (event) => handleRailClick(event, modal));\n });\n\n root.querySelectorAll('.elements-flavor-chip').forEach(node => {\n node.addEventListener('click', (event) => handleFlavorChipClick(event, modal));\n });\n\n root.querySelectorAll('.elementst-dialog-button').forEach(node => {\n node.addEventListener('click', (event) => handleCardClick(event, modal));\n node.addEventListener('dblclick', (event) => handleCardDblClick(event, editor, modal));\n if (data.getPreviewElements()) {\n node.addEventListener('mouseenter', (event) => handleCardMouseEvent(event, modal, true));\n node.addEventListener('mouseleave', (event) => handleCardMouseEvent(event, modal, false));\n }\n const dotsContainer = node.querySelector('.elements-v-dots');\n if (dotsContainer) {\n dotsContainer.addEventListener('mouseenter', () => showVariantStripFor(modal, node.dataset.id));\n dotsContainer.addEventListener('mouseleave', () => scheduleHideVariantStrip(modal));\n }\n node.querySelectorAll('.elements-v-dot').forEach(dot => {\n dot.addEventListener('click', (event) => handleDotClick(event, modal));\n });\n });\n\n const strip = root.querySelector('[data-region=\"variant-strip\"]');\n if (strip) {\n strip.addEventListener('mouseenter', () => cancelHideVariantStrip());\n strip.addEventListener('mouseleave', () => scheduleHideVariantStrip(modal));\n }\n\n const insertBtn = root.querySelector('.elements-insert-btn');\n if (insertBtn) {\n insertBtn.addEventListener('click', () => handleInsertClick(editor, modal));\n }\n\n restoreInitialSelection(modal);\n};\n\n/**\n * Restore the previously chosen category + flavor on modal open.\n *\n * @param {obj} modal\n */\nconst restoreInitialSelection = (modal) => {\n const root = modal.getRoot()[0];\n const railItems = root.querySelectorAll('.elements-rail-item');\n if (railItems.length === 0) {\n return;\n }\n let target = null;\n if (currentCategoryId) {\n target = root.querySelector(`.elements-rail-item[data-categoryid=\"${currentCategoryId}\"]`);\n }\n if (!target) {\n target = railItems[0];\n }\n target.click();\n\n const savedFlavorId = lastFlavor[target.dataset.categoryid];\n if (savedFlavorId) {\n const flavorChip = root.querySelector(\n `.elements-flavor-chip[data-id=\"${savedFlavorId}\"][data-categoryid=\"${target.dataset.categoryid}\"]`\n );\n if (flavorChip) {\n flavorChip.click();\n }\n }\n};\n\n/**\n * Activate a category — switches the rail, the flavor bar set, and filters the grid.\n *\n * @param {MouseEvent} event\n * @param {obj} modal\n */\nconst handleRailClick = (event, modal) => {\n const item = event.currentTarget;\n currentCategoryId = item.dataset.categoryid;\n currentCategoryName = item.dataset.categoryname;\n currentFlavor = '';\n currentFlavorId = 0;\n\n const root = modal.getRoot()[0];\n\n root.querySelectorAll('.elements-rail-item').forEach(node => {\n const isActive = node === item;\n node.classList.toggle('active', isActive);\n node.setAttribute('aria-selected', isActive ? 'true' : 'false');\n });\n\n root.querySelectorAll('.elements-flavor-list').forEach(set => {\n set.toggleAttribute('hidden', set.dataset.categoryid !== currentCategoryId);\n });\n root.querySelectorAll('.elements-flavor-chip').forEach(chip => chip.classList.remove('active'));\n const noneChip = root.querySelector(\n `.elements-flavor-list[data-categoryid=\"${currentCategoryId}\"] .elements-flavor-none`\n );\n if (noneChip) {\n noneChip.classList.add('active');\n }\n\n showCategoryButtons(modal, currentCategoryName);\n clearSelection(modal);\n};\n\n/**\n * Handle clicks on a flavor chip (incl. the \"Kein Flavor\" chip).\n *\n * @param {MouseEvent} event\n * @param {obj} modal\n */\nconst handleFlavorChipClick = (event, modal) => {\n const chip = event.currentTarget;\n currentFlavor = chip.dataset.flavor || '';\n currentFlavorId = chip.dataset.id ? parseInt(chip.dataset.id, 10) : 0;\n currentCategoryId = chip.dataset.categoryid;\n currentCategoryName = chip.dataset.categoryname;\n\n if (currentFlavorId) {\n lastFlavor[currentCategoryId] = currentFlavorId;\n } else {\n delete lastFlavor[currentCategoryId];\n }\n\n const root = modal.getRoot()[0];\n root.querySelectorAll(\n `.elements-flavor-list[data-categoryid=\"${currentCategoryId}\"] .elements-flavor-chip`\n ).forEach(c => c.classList.remove('active'));\n chip.classList.add('active');\n\n const buttons = root.querySelectorAll('.elementst-dialog-button');\n buttons.forEach(button => {\n const matchesCategory = button.dataset.category === currentCategoryName;\n const flavorlist = button.dataset.flavorlist || '';\n const matchesFlavor = !currentFlavor || flavorlist === '' || flavorlist.split(',').includes(currentFlavor);\n const visible = matchesCategory && matchesFlavor;\n button.classList.toggle('elements-hidden', !visible);\n\n if (visible) {\n button.dataset.flavor = currentFlavor;\n refreshCardDots(button);\n }\n });\n\n if (selectedComponentId) {\n const stillVisible = root.querySelector(\n `.elementst-dialog-button[data-id=\"${selectedComponentId}\"]:not(.elements-hidden)`\n );\n if (!stillVisible) {\n clearSelection(modal);\n } else {\n updatePreviewSummary(modal);\n refreshSelectedPreview(modal);\n }\n }\n hideVariantStrip(modal);\n};\n\n/**\n * Handle a click on a component card — sets selection, builds the variant strip, enables Insert.\n *\n * @param {MouseEvent} event\n * @param {obj} modal\n */\nconst handleCardClick = (event, modal) => {\n if (event.target.closest('.elements-v-dot')) {\n return;\n }\n const button = event.currentTarget;\n const root = modal.getRoot()[0];\n\n selectedComponentId = button.dataset.id;\n\n root.querySelectorAll('.elementst-dialog-button.selected').forEach(node => {\n node.classList.remove('selected');\n node.setAttribute('aria-selected', 'false');\n });\n button.classList.add('selected');\n button.setAttribute('aria-selected', 'true');\n\n refreshSelectedPreview(modal);\n updatePreviewSummary(modal);\n\n const insertBtn = root.querySelector('.elements-insert-btn');\n if (insertBtn) {\n insertBtn.disabled = false;\n }\n};\n\n/**\n * Double-click on a card — same as a click + immediate insert.\n *\n * @param {MouseEvent} event\n * @param {obj} editor\n * @param {obj} modal\n */\nconst handleCardDblClick = (event, editor, modal) => {\n if (event.target.closest('.elements-v-dot')) {\n return;\n }\n selectedComponentId = event.currentTarget.dataset.id;\n handleInsertClick(editor, modal);\n};\n\n/**\n * Hover over a card — show/hide preview (selection takes over on mouseleave).\n *\n * @param {MouseEvent} event\n * @param {obj} modal\n * @param {bool} show\n */\nconst handleCardMouseEvent = (event, modal, show) => {\n const button = event.currentTarget;\n if (show) {\n showPreviewFor(modal, button.dataset.id);\n } else if (selectedComponentId) {\n showPreviewFor(modal, selectedComponentId);\n } else {\n showPreviewFor(modal, null);\n }\n};\n\n/**\n * Render preview for the given component id (or the default hint when null).\n *\n * @param {obj} modal\n * @param {?string} componentId\n */\nconst showPreviewFor = (modal, componentId) => {\n const root = modal.getRoot()[0];\n const previewBody = root.querySelector('.elements-preview-body');\n if (!previewBody) {\n previewComponentId = null;\n return;\n }\n previewBody.querySelectorAll('.elements-component-code, .elements-preview-default').forEach(node => {\n node.classList.add('elements-hidden');\n });\n\n if (!componentId) {\n const defaultHint = previewBody.querySelector('.elements-preview-default');\n if (defaultHint) {\n defaultHint.classList.remove('elements-hidden');\n }\n previewComponentId = null;\n return;\n }\n\n const comp = data.getComponentById(componentId);\n if (!comp) {\n previewComponentId = null;\n return;\n }\n const node = previewBody.querySelector(`div[data-id=\"code-preview-${componentId}\"]`);\n if (!node) {\n previewComponentId = null;\n return;\n }\n const flavor = comp.flavors.length > 0 ? currentFlavor : '';\n const placeholder = (selection.length > 0 ? selection : comp.text);\n node.innerHTML = updateComponentCode(comp.code, componentId, placeholder, flavor);\n node.classList.remove('elements-hidden');\n previewComponentId = componentId;\n};\n\n/**\n * Re-render the preview for the currently selected component.\n *\n * @param {obj} modal\n */\nconst refreshSelectedPreview = (modal) => {\n if (selectedComponentId) {\n showPreviewFor(modal, selectedComponentId);\n } else {\n showPreviewFor(modal, null);\n }\n};\n\n/**\n * Reveal the variant strip below the grid, populated for the given component.\n *\n * @param {obj} modal\n * @param {string} componentId\n */\nconst showVariantStripFor = (modal, componentId) => {\n cancelHideVariantStrip();\n const root = modal.getRoot()[0];\n const strip = root.querySelector('[data-region=\"variant-strip\"]');\n const target = root.querySelector('[data-region=\"variant-target\"]');\n const chips = root.querySelector('[data-region=\"variant-chips\"]');\n if (!strip || !chips) {\n return;\n }\n\n const comp = data.getComponentById(componentId);\n if (!comp) {\n return;\n }\n const variants = data.getComponentVariants(comp);\n if (variants.length === 0) {\n strip.setAttribute('hidden', '');\n return;\n }\n\n if (stripComponentId === componentId && !strip.hasAttribute('hidden')) {\n return;\n }\n stripComponentId = componentId;\n\n const flavor = comp.flavors.length > 0 ? currentFlavor : '';\n const activeClasses = getVariantsClass(comp.name, flavor);\n\n chips.innerHTML = '';\n if (target) {\n target.textContent = comp.displayname;\n }\n\n variants.forEach(variant => {\n const isOn = activeClasses.includes(variant.variantclass);\n const chip = document.createElement('button');\n chip.type = 'button';\n chip.className = 'elements-variant-chip ' + (isOn ? 'active' : '');\n chip.dataset.variant = variant.name;\n chip.dataset.variantclass = variant.variantclass;\n chip.dataset.state = isOn ? 'on' : 'off';\n chip.title = variant.displayname || variant.name;\n chip.textContent = variant.displayname || variant.name;\n chip.addEventListener('click', () => handleVariantChipClick(chip, modal));\n chips.appendChild(chip);\n });\n\n strip.removeAttribute('hidden');\n};\n\nconst scheduleHideVariantStrip = (modal) => {\n cancelHideVariantStrip();\n stripHideTimer = setTimeout(() => hideVariantStrip(modal), 150);\n};\n\nconst cancelHideVariantStrip = () => {\n if (stripHideTimer) {\n clearTimeout(stripHideTimer);\n stripHideTimer = null;\n }\n};\n\nconst hideVariantStrip = (modal) => {\n cancelHideVariantStrip();\n const strip = modal.getRoot()[0].querySelector('[data-region=\"variant-strip\"]');\n if (strip) {\n strip.setAttribute('hidden', '');\n }\n stripComponentId = null;\n};\n\n/**\n * Toggle a variant on a card via dot click.\n *\n * @param {MouseEvent} event\n * @param {obj} modal\n */\nconst handleDotClick = (event, modal) => {\n event.stopPropagation();\n event.preventDefault();\n const dot = event.currentTarget;\n const card = dot.closest('.elementst-dialog-button');\n if (!card) {\n return;\n }\n toggleVariantForComponent(modal, card.dataset.id, dot.dataset.variant, dot.dataset.variantclass);\n};\n\n/**\n * Toggle a variant via chip click in the strip.\n *\n * @param {HTMLElement} chip\n * @param {obj} modal\n */\nconst handleVariantChipClick = (chip, modal) => {\n if (!stripComponentId) {\n return;\n }\n toggleVariantForComponent(modal, stripComponentId, chip.dataset.variant, chip.dataset.variantclass);\n};\n\n/**\n * Core toggle: flip a variant on a component, sync dot, chip, and preview.\n *\n * @param {obj} modal\n * @param {string} componentId\n * @param {string} variantName\n * @param {string} variantClass\n */\nconst toggleVariantForComponent = (modal, componentId, variantName, variantClass) => {\n const comp = data.getComponentById(componentId);\n if (!comp) {\n return;\n }\n const flavor = comp.flavors.length > 0 ? currentFlavor : '';\n const activeBefore = getVariantsClass(comp.name, flavor).includes(variantClass);\n const turningOn = !activeBefore;\n\n if (turningOn) {\n addVariant(comp.name, variantName, flavor);\n } else {\n removeVariant(comp.name, variantName, flavor);\n }\n\n const card = modal.getRoot()[0].querySelector(`.elementst-dialog-button[data-id=\"${componentId}\"]`);\n if (card) {\n const dot = card.querySelector(`.elements-v-dot[data-variantclass=\"${variantClass}\"]`);\n if (dot) {\n dot.classList.toggle('on', turningOn);\n }\n }\n\n if (stripComponentId === componentId) {\n const chip = modal.getRoot()[0].querySelector(\n `.elements-variant-chip[data-variantclass=\"${variantClass}\"]`\n );\n if (chip) {\n chip.dataset.state = turningOn ? 'on' : 'off';\n chip.classList.toggle('active', turningOn);\n }\n }\n\n if (previewComponentId === componentId) {\n showPreviewFor(modal, componentId);\n }\n updatePreviewSummary(modal);\n};\n\n/**\n * Sync the dot indicators on a single card to current preferences.\n *\n * @param {HTMLElement} card\n */\nconst refreshCardDots = (card) => {\n const comp = data.getComponentById(card.dataset.id);\n if (!comp) {\n return;\n }\n const flavor = comp.flavors.length > 0 ? currentFlavor : '';\n const activeClasses = getVariantsClass(comp.name, flavor);\n card.querySelectorAll('.elements-v-dot').forEach(dot => {\n dot.classList.toggle('on', activeClasses.includes(dot.dataset.variantclass));\n });\n};\n\n/**\n * Update the summary line in the preview footer.\n *\n * @param {obj} modal\n */\nconst updatePreviewSummary = (modal) => {\n const root = modal.getRoot()[0];\n const summary = root.querySelector('[data-region=\"preview-summary\"]');\n if (!summary) {\n return;\n }\n if (!selectedComponentId) {\n summary.textContent = '';\n return;\n }\n const comp = data.getComponentById(selectedComponentId);\n if (!comp) {\n summary.textContent = '';\n return;\n }\n const parts = [comp.displayname];\n const cat = data.getCategoryById(currentCategoryId);\n if (cat) {\n parts.push(cat.displayname);\n }\n if (currentFlavor) {\n const chip = root.querySelector(\n `.elements-flavor-chip[data-flavor=\"${currentFlavor}\"][data-categoryid=\"${currentCategoryId}\"]`\n );\n if (chip) {\n parts.push(chip.textContent.trim());\n }\n }\n summary.textContent = parts.join(' · ');\n};\n\n/**\n * Reset selection state and disable Insert.\n *\n * @param {obj} modal\n */\nconst clearSelection = (modal) => {\n selectedComponentId = null;\n const root = modal.getRoot()[0];\n root.querySelectorAll('.elementst-dialog-button.selected').forEach(node => {\n node.classList.remove('selected');\n node.setAttribute('aria-selected', 'false');\n });\n const strip = root.querySelector('[data-region=\"variant-strip\"]');\n if (strip) {\n strip.setAttribute('hidden', '');\n }\n const chips = root.querySelector('[data-region=\"variant-chips\"]');\n if (chips) {\n chips.innerHTML = '';\n }\n const insertBtn = root.querySelector('.elements-insert-btn');\n if (insertBtn) {\n insertBtn.disabled = true;\n }\n showPreviewFor(modal, null);\n updatePreviewSummary(modal);\n};\n\n/**\n * Insert the selected component into the editor.\n *\n * @param {obj} editor\n * @param {obj} modal\n */\nconst handleInsertClick = (editor, modal) => {\n if (!selectedComponentId) {\n return;\n }\n const comp = data.getComponentById(selectedComponentId);\n if (!comp) {\n return;\n }\n\n const flavor = comp.flavors.length > 0 ? currentFlavor : '';\n const placeholder = (selection.length > 0 ? selection : comp.text);\n const randomId = generateRandomID();\n const newNode = document.createElement('span');\n newNode.dataset.id = randomId;\n newNode.innerHTML = placeholder;\n const componentCode = updateComponentCode(comp.code, selectedComponentId, newNode.outerHTML, flavor);\n\n editor.selection.setContent(componentCode);\n const nodeSel = editor.dom.select('span[data-id=\"' + randomId + '\"]');\n if (nodeSel?.[0]) {\n editor.selection.select(nodeSel[0]);\n }\n\n modal.destroy();\n editor.focus();\n editor.undoManager.add();\n};\n\n/**\n * Modal close — persist selections.\n *\n * @param {obj} editor\n */\nconst handleModalHidden = (editor) => {\n editor.targetElm.closest('body').classList.remove('elements-modal-no-preview');\n if (currentCategoryId) {\n savePreferences([\n {type: Preferences.category, value: currentCategoryId},\n {type: Preferences.category_flavors, value: JSON.stringify(lastFlavor)},\n {type: Preferences.component_variants, value: JSON.stringify(getVariantPreferences())}\n ]);\n }\n};\n\nconst updateComponentCode = (componentCode, selectedButton, placeholder, flavor = '') => {\n componentCode = componentCode.replace('{{PLACEHOLDER}}', placeholder);\n const comp = data.getComponentById(selectedButton);\n const variants = getVariantsClass(comp.name, flavor);\n\n if (variants.length > 0) {\n componentCode = componentCode.replace('{{VARIANTS}}', variants.join(' '));\n componentCode = componentCode.replace('{{VARIANTSHTML}}', getVariantsHtml(comp.name, flavor));\n } else {\n componentCode = componentCode.replace('{{VARIANTS}}', '');\n componentCode = componentCode.replace('{{VARIANTSHTML}}', '');\n }\n\n if (currentFlavor) {\n componentCode = componentCode.replace('{{FLAVOR}}', 'elements-' + currentFlavor + '-flavor');\n } else {\n componentCode = componentCode.replace('{{FLAVOR}}', '');\n }\n\n componentCode = componentCode.replace('{{COMPONENT}}', 'elements-' + comp.name);\n componentCode = componentCode.replace('{{CATEGORY}}', 'elements-' + data.getCategoryById(currentCategoryId).name);\n\n componentCode = applyRandomID(componentCode);\n componentCode = applyLangStrings(componentCode);\n\n return componentCode;\n};\n\n/**\n * Show only buttons of the active category.\n *\n * @param {object} modal\n * @param {String} context\n */\nconst showCategoryButtons = (modal, context) => {\n const showNodes = modal.getRoot()[0].querySelectorAll(`button[data-type=\"${context}\"]`);\n const hideNodes = modal.getRoot()[0].querySelectorAll(`button[data-type]:not([data-type=\"${context}\"])`);\n showNodes.forEach(node => {\n node.classList.remove('elements-hidden');\n refreshCardDots(node);\n });\n hideNodes.forEach(node => node.classList.add('elements-hidden'));\n};\n\n/**\n * Replace all localized strings.\n *\n * @param {String} text\n * @return {String}\n */\nconst applyLangStrings = (text) => {\n const compRegex = /{{#([^}]*)}}/g;\n [...text.matchAll(compRegex)].forEach(strLang => {\n text = text.replace('{{#' + strLang[1] + '}}', data.getLangString(strLang[1]));\n });\n return text;\n};\n\n/**\n * Generates a random string.\n * @return {string}\n */\nconst generateRandomID = () => {\n const timestamp = new Date().getTime();\n return 'R' + Math.round(Math.random() * 100000) + '-' + timestamp;\n};\n\n/**\n * Replace all ID tags with a random string.\n * @param {String} text\n * @return {String}\n */\nconst applyRandomID = (text) => {\n const compRegex = /{{@ID}}/g;\n if (text.match(compRegex)) {\n text = text.replace(compRegex, generateRandomID());\n }\n return text;\n};\n"],"names":["currentFlavor","currentFlavorId","currentCategoryId","currentCategoryName","selectedComponentId","previewComponentId","stripComponentId","stripHideTimer","lastFlavor","selection","data","async","editor","getContent","Data","loadData","Preferences","category","category_flavors","componentVariants","component_variants","displayDialogue","templateContext","getTemplateContext","modal","ElementsModal","create","type","TYPE","large","modalClass","getPreviewElements","targetElm","closest","classList","add","show","root","getRoot","on","ModalEvents","hidden","handleModalHidden","querySelectorAll","forEach","node","addEventListener","event","handleRailClick","handleFlavorChipClick","handleCardClick","handleCardDblClick","handleCardMouseEvent","dotsContainer","querySelector","showVariantStripFor","dataset","id","scheduleHideVariantStrip","dot","handleDotClick","strip","cancelHideVariantStrip","insertBtn","handleInsertClick","restoreInitialSelection","railItems","length","target","click","savedFlavorId","categoryid","flavorChip","item","currentTarget","categoryname","isActive","toggle","setAttribute","set","toggleAttribute","chip","remove","noneChip","showCategoryButtons","clearSelection","flavor","parseInt","c","button","matchesCategory","flavorlist","matchesFlavor","split","includes","visible","refreshCardDots","updatePreviewSummary","refreshSelectedPreview","hideVariantStrip","disabled","showPreviewFor","componentId","previewBody","defaultHint","comp","getComponentById","flavors","placeholder","text","innerHTML","updateComponentCode","code","chips","variants","getComponentVariants","hasAttribute","activeClasses","name","textContent","displayname","variant","isOn","variantclass","document","createElement","className","state","title","handleVariantChipClick","appendChild","removeAttribute","setTimeout","clearTimeout","stopPropagation","preventDefault","card","toggleVariantForComponent","variantName","variantClass","turningOn","summary","parts","cat","getCategoryById","push","trim","join","randomId","generateRandomID","newNode","componentCode","outerHTML","setContent","nodeSel","dom","select","destroy","focus","undoManager","value","JSON","stringify","selectedButton","replace","applyRandomID","applyLangStrings","context","showNodes","hideNodes","matchAll","strLang","getLangString","timestamp","Date","getTime","Math","round","random","compRegex","match"],"mappings":"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"}
\ No newline at end of file
diff --git a/amd/src/data.js b/amd/src/data.js
index a84fb57..62d4d48 100644
--- a/amd/src/data.js
+++ b/amd/src/data.js
@@ -96,6 +96,13 @@ export default class Data {
*/
getCategories() {
const cats = [];
+ const componentCounts = {};
+ this.components.forEach((component) => {
+ if (component === undefined) {
+ return;
+ }
+ componentCounts[component.categoryname] = (componentCounts[component.categoryname] || 0) + 1;
+ });
// Iterate over contexts.
this.categories.forEach((category) => {
let categoryFlavors = this.getCategoryFlavors(category.name);
@@ -109,6 +116,7 @@ export default class Data {
displayorder: category.displayorder,
flavors: categoryFlavors,
hasFlavors: hasFlavors,
+ componentcount: componentCounts[category.name] || 0,
active: '',
});
});
diff --git a/amd/src/ui.js b/amd/src/ui.js
index 161b70d..629a110 100644
--- a/amd/src/ui.js
+++ b/amd/src/ui.js
@@ -32,7 +32,6 @@ import ModalEvents from 'core/modal_events';
import {
addVariant,
getVariantsClass,
- getVariantHtml,
getVariantPreferences,
getVariantsHtml,
loadVariantPreferences,
@@ -51,12 +50,16 @@ let currentFlavor = '';
let currentFlavorId = 0;
let currentCategoryId = 1;
let currentCategoryName = '';
+let selectedComponentId = null;
+let previewComponentId = null;
+let stripComponentId = null;
+let stripHideTimer = null;
let lastFlavor = [];
let selection = '';
let data = {};
/**
- * Handle action
+ * Handle action.
*
* @param {TinyMCE} editor
*/
@@ -87,431 +90,634 @@ export const handleAction = async(editor) => {
};
/**
- * Display modal
+ * Display modal.
*
* @param {TinyMCE} editor
*/
const displayDialogue = async(editor) => {
const templateContext = data.getTemplateContext(editor);
- // Show modal with buttons.
const modal = await ElementsModal.create({
type: ElementsModal.TYPE,
templateContext: templateContext,
large: true,
});
- // Choose class to modal.
const modalClass = data.getPreviewElements() ? 'elements-modal' : 'elements-modal-no-preview';
-
- // Set class to modal.
editor.targetElm.closest('body').classList.add(modalClass);
modal.show();
- // Event modal listener.
+ const root = modal.getRoot()[0];
+
modal.getRoot().on(ModalEvents.hidden, () => {
handleModalHidden(editor);
});
- // Event listener for categories without flavors.
- const soleCategories = modal.getRoot()[0].querySelectorAll('.elements-category.no-flavors');
- soleCategories.forEach(node => {
- node.addEventListener('click', (event) => {
- handleCategoryClick(event, modal);
- });
+ root.querySelectorAll('.elements-rail-item').forEach(node => {
+ node.addEventListener('click', (event) => handleRailClick(event, modal));
});
- // Event listener for categories with flavors.
- const selectCategories = modal.getRoot()[0].querySelectorAll('.elements-category-flavor');
- selectCategories.forEach(node => {
- node.addEventListener('click', (event) => {
- handleCategoryFlavorClick(event, modal);
- });
- });
-
- // Event listener for category dropdown, triggering to switch to last used flavor.
- const selectCategoriesRemember = modal.getRoot()[0].querySelectorAll('.nav-link.dropdown-toggle');
- selectCategoriesRemember.forEach(node => {
- node.addEventListener('click', (event) => {
- handleCategoryRemember(event, modal);
- });
+ root.querySelectorAll('.elements-flavor-chip').forEach(node => {
+ node.addEventListener('click', (event) => handleFlavorChipClick(event, modal));
});
- // Event buttons listeners.
- const buttons = modal.getRoot()[0].querySelectorAll('.elementst-dialog-button');
- buttons.forEach(node => {
- node.addEventListener('click', (event) => {
- handleButtonClick(event, editor, modal);
- });
+ root.querySelectorAll('.elementst-dialog-button').forEach(node => {
+ node.addEventListener('click', (event) => handleCardClick(event, modal));
+ node.addEventListener('dblclick', (event) => handleCardDblClick(event, editor, modal));
if (data.getPreviewElements()) {
- node.addEventListener('mouseenter', (event) => {
- handleButtonMouseEvent(event, modal, true);
- });
- node.addEventListener('mouseleave', (event) => {
- handleButtonMouseEvent(event, modal, false);
- });
+ node.addEventListener('mouseenter', (event) => handleCardMouseEvent(event, modal, true));
+ node.addEventListener('mouseleave', (event) => handleCardMouseEvent(event, modal, false));
}
- });
-
- // Event variants listeners.
- const variants = modal.getRoot()[0].querySelectorAll('.elements-button-variant');
- variants.forEach(node => {
- node.addEventListener('click', (event) => {
- handleVariantClick(event, modal);
+ const dotsContainer = node.querySelector('.elements-v-dots');
+ if (dotsContainer) {
+ dotsContainer.addEventListener('mouseenter', () => showVariantStripFor(modal, node.dataset.id));
+ dotsContainer.addEventListener('mouseleave', () => scheduleHideVariantStrip(modal));
+ }
+ node.querySelectorAll('.elements-v-dot').forEach(dot => {
+ dot.addEventListener('click', (event) => handleDotClick(event, modal));
});
});
- // Select first or saved category.
- if (soleCategories.length > 0 || selectCategories.length > 0) {
- const savedCategory = currentCategoryId;
- const savedFlavor = lastFlavor[currentCategoryId];
- if (soleCategories.length == 0 || soleCategories[0].displayorder > selectCategories[0].displayorder) {
- selectCategories[0].click();
- } else {
- soleCategories[0].click();
- }
- if (savedCategory != 0) {
- soleCategories.forEach((node) => {
- if (node.dataset.categoryid == savedCategory) {
- node.click();
- }
- });
- selectCategories.forEach((node) => {
- if (node.dataset.categoryid == savedCategory) {
- node.click();
- }
- });
- if (savedFlavor) {
- const flavorlink = modal.getRoot()[0].querySelector(
- '.elements-category-flavor[data-id="' + savedFlavor + '"]'
- );
- if (flavorlink) {
- flavorlink.click();
- }
- }
+ const strip = root.querySelector('[data-region="variant-strip"]');
+ if (strip) {
+ strip.addEventListener('mouseenter', () => cancelHideVariantStrip());
+ strip.addEventListener('mouseleave', () => scheduleHideVariantStrip(modal));
+ }
+
+ const insertBtn = root.querySelector('.elements-insert-btn');
+ if (insertBtn) {
+ insertBtn.addEventListener('click', () => handleInsertClick(editor, modal));
+ }
+
+ restoreInitialSelection(modal);
+};
+
+/**
+ * Restore the previously chosen category + flavor on modal open.
+ *
+ * @param {obj} modal
+ */
+const restoreInitialSelection = (modal) => {
+ const root = modal.getRoot()[0];
+ const railItems = root.querySelectorAll('.elements-rail-item');
+ if (railItems.length === 0) {
+ return;
+ }
+ let target = null;
+ if (currentCategoryId) {
+ target = root.querySelector(`.elements-rail-item[data-categoryid="${currentCategoryId}"]`);
+ }
+ if (!target) {
+ target = railItems[0];
+ }
+ target.click();
+
+ const savedFlavorId = lastFlavor[target.dataset.categoryid];
+ if (savedFlavorId) {
+ const flavorChip = root.querySelector(
+ `.elements-flavor-chip[data-id="${savedFlavorId}"][data-categoryid="${target.dataset.categoryid}"]`
+ );
+ if (flavorChip) {
+ flavorChip.click();
}
}
};
/**
- * Handle a click within filter button.
+ * Activate a category — switches the rail, the flavor bar set, and filters the grid.
*
- * @param {MouseEvent} event The change event
+ * @param {MouseEvent} event
* @param {obj} modal
*/
-const handleCategoryClick = (event, modal) => {
- const link = event.target;
- currentCategoryId = link.dataset.categoryid;
- currentCategoryName = link.dataset.categoryname;
+const handleRailClick = (event, modal) => {
+ const item = event.currentTarget;
+ currentCategoryId = item.dataset.categoryid;
+ currentCategoryName = item.dataset.categoryname;
+ currentFlavor = '';
+ currentFlavorId = 0;
+
+ const root = modal.getRoot()[0];
+
+ root.querySelectorAll('.elements-rail-item').forEach(node => {
+ const isActive = node === item;
+ node.classList.toggle('active', isActive);
+ node.setAttribute('aria-selected', isActive ? 'true' : 'false');
+ });
- // Remove active from all and set to selected.
- const links = modal.getRoot()[0].querySelectorAll('.nav-link, .dropdown-item');
- links.forEach(node => node.classList.remove('active'));
- link.classList.add('active');
+ root.querySelectorAll('.elements-flavor-list').forEach(set => {
+ set.toggleAttribute('hidden', set.dataset.categoryid !== currentCategoryId);
+ });
+ root.querySelectorAll('.elements-flavor-chip').forEach(chip => chip.classList.remove('active'));
+ const noneChip = root.querySelector(
+ `.elements-flavor-list[data-categoryid="${currentCategoryId}"] .elements-flavor-none`
+ );
+ if (noneChip) {
+ noneChip.classList.add('active');
+ }
- // Show/hide component buttons.
showCategoryButtons(modal, currentCategoryName);
+ clearSelection(modal);
};
/**
- * Handle a click on a flavor in the category dropdown.
+ * Handle clicks on a flavor chip (incl. the "Kein Flavor" chip).
*
- * @param {MouseEvent} event The change event
+ * @param {MouseEvent} event
* @param {obj} modal
*/
-const handleCategoryFlavorClick = (event, modal) => {
- const link = event.target;
- currentFlavor = link.dataset.flavor;
- currentFlavorId = link.dataset.id;
- currentCategoryId = link.dataset.categoryid;
- currentCategoryName = link.dataset.categoryname;
- lastFlavor[currentCategoryId] = currentFlavorId;
-
- // Remove active from all and set to selected.
- const links = modal.getRoot()[0].querySelectorAll('.nav-link, .dropdown-item');
- links.forEach(node => node.classList.remove('active'));
- link.classList.add('active');
- const category = modal.getRoot()[0].querySelector('.nav-link[data-categoryid="' + currentCategoryId + '"]');
- category.classList.add('active');
-
- const componentButtons = modal.getRoot()[0].querySelectorAll('.elements-buttons-preview button');
- componentButtons.forEach(componentButton => {
- componentButton.dataset.flavor = currentFlavor;
- if (
- (componentButton.dataset.flavorlist == '' || componentButton.dataset.flavorlist.split(',').includes(currentFlavor)) &&
- componentButton.dataset.category == currentCategoryName
- ) {
- componentButton.classList.remove('elements-hidden');
- if (componentButton.dataset.flavorlist != '') {
- let variants = getVariantsClass(data.getComponentById(componentButton.dataset.id).name, currentFlavor);
- let availableVariants = componentButton.querySelectorAll('.elements-button-variant');
- availableVariants.forEach((variant) => {
- updateVariantButtonState(variant, variants.indexOf(variant.dataset.variantclass) != -1);
- });
- }
- } else {
- componentButton.classList.add('elements-hidden');
+const handleFlavorChipClick = (event, modal) => {
+ const chip = event.currentTarget;
+ currentFlavor = chip.dataset.flavor || '';
+ currentFlavorId = chip.dataset.id ? parseInt(chip.dataset.id, 10) : 0;
+ currentCategoryId = chip.dataset.categoryid;
+ currentCategoryName = chip.dataset.categoryname;
+
+ if (currentFlavorId) {
+ lastFlavor[currentCategoryId] = currentFlavorId;
+ } else {
+ delete lastFlavor[currentCategoryId];
+ }
+
+ const root = modal.getRoot()[0];
+ root.querySelectorAll(
+ `.elements-flavor-list[data-categoryid="${currentCategoryId}"] .elements-flavor-chip`
+ ).forEach(c => c.classList.remove('active'));
+ chip.classList.add('active');
+
+ const buttons = root.querySelectorAll('.elementst-dialog-button');
+ buttons.forEach(button => {
+ const matchesCategory = button.dataset.category === currentCategoryName;
+ const flavorlist = button.dataset.flavorlist || '';
+ const matchesFlavor = !currentFlavor || flavorlist === '' || flavorlist.split(',').includes(currentFlavor);
+ const visible = matchesCategory && matchesFlavor;
+ button.classList.toggle('elements-hidden', !visible);
+
+ if (visible) {
+ button.dataset.flavor = currentFlavor;
+ refreshCardDots(button);
}
});
+ if (selectedComponentId) {
+ const stillVisible = root.querySelector(
+ `.elementst-dialog-button[data-id="${selectedComponentId}"]:not(.elements-hidden)`
+ );
+ if (!stillVisible) {
+ clearSelection(modal);
+ } else {
+ updatePreviewSummary(modal);
+ refreshSelectedPreview(modal);
+ }
+ }
+ hideVariantStrip(modal);
};
/**
- * When opening the category dropdown, try to load remembered flavor.
+ * Handle a click on a component card — sets selection, builds the variant strip, enables Insert.
*
- * @param {MouseEvent} event The change event
+ * @param {MouseEvent} event
* @param {obj} modal
*/
-const handleCategoryRemember = (event, modal) => {
- const link = event.target;
- currentCategoryId = link.dataset.categoryid;
- currentCategoryName = link.dataset.categoryname;
- currentFlavorId = lastFlavor[currentCategoryId];
+const handleCardClick = (event, modal) => {
+ if (event.target.closest('.elements-v-dot')) {
+ return;
+ }
+ const button = event.currentTarget;
+ const root = modal.getRoot()[0];
+
+ selectedComponentId = button.dataset.id;
+
+ root.querySelectorAll('.elementst-dialog-button.selected').forEach(node => {
+ node.classList.remove('selected');
+ node.setAttribute('aria-selected', 'false');
+ });
+ button.classList.add('selected');
+ button.setAttribute('aria-selected', 'true');
+
+ refreshSelectedPreview(modal);
+ updatePreviewSummary(modal);
- if (currentFlavorId != undefined) {
- // Call handleCategoryFlavorClick with tampered data.
- let e = {target: modal.getRoot()[0].querySelector('.elements-category-flavor[data-id="' + currentFlavorId + '"]')};
- handleCategoryFlavorClick(e, modal);
+ const insertBtn = root.querySelector('.elements-insert-btn');
+ if (insertBtn) {
+ insertBtn.disabled = false;
}
};
/**
- * Handle when closing the Modal.
+ * Double-click on a card — same as a click + immediate insert.
*
+ * @param {MouseEvent} event
* @param {obj} editor
+ * @param {obj} modal
*/
-const handleModalHidden = (editor) => {
- editor.targetElm.closest('body').classList.remove('elements-modal-no-preview');
- if (currentCategoryId != 0 && currentFlavorId != 0) {
- savePreferences([
- {type: Preferences.category, value: currentCategoryId},
- {type: Preferences.category_flavors, value: JSON.stringify(lastFlavor)},
- {type: Preferences.component_variants, value: JSON.stringify(getVariantPreferences())}
- ]);
+const handleCardDblClick = (event, editor, modal) => {
+ if (event.target.closest('.elements-v-dot')) {
+ return;
}
+ selectedComponentId = event.currentTarget.dataset.id;
+ handleInsertClick(editor, modal);
};
-const updateComponentCode = (componentCode, selectedButton, placeholder, flavor = '') => {
- componentCode = componentCode.replace('{{PLACEHOLDER}}', placeholder);
- const comp = data.getComponentById(selectedButton);
- // Return active variants for current component.
- const variants = getVariantsClass(comp.name, flavor);
-
- // Apply variants to html component.
- if (variants.length > 0) {
- componentCode = componentCode.replace('{{VARIANTS}}', variants.join(' '));
- componentCode = componentCode.replace('{{VARIANTSHTML}}', getVariantsHtml(comp.name, flavor));
+/**
+ * Hover over a card — show/hide preview (selection takes over on mouseleave).
+ *
+ * @param {MouseEvent} event
+ * @param {obj} modal
+ * @param {bool} show
+ */
+const handleCardMouseEvent = (event, modal, show) => {
+ const button = event.currentTarget;
+ if (show) {
+ showPreviewFor(modal, button.dataset.id);
+ } else if (selectedComponentId) {
+ showPreviewFor(modal, selectedComponentId);
} else {
- componentCode = componentCode.replace('{{VARIANTS}}', '');
- componentCode = componentCode.replace('{{VARIANTSHTML}}', '');
+ showPreviewFor(modal, null);
}
+};
- if (currentFlavor) {
- componentCode = componentCode.replace('{{FLAVOR}}', 'elements-' + currentFlavor + '-flavor');
- } else {
- componentCode = componentCode.replace('{{FLAVOR}}', '');
+/**
+ * Render preview for the given component id (or the default hint when null).
+ *
+ * @param {obj} modal
+ * @param {?string} componentId
+ */
+const showPreviewFor = (modal, componentId) => {
+ const root = modal.getRoot()[0];
+ const previewBody = root.querySelector('.elements-preview-body');
+ if (!previewBody) {
+ previewComponentId = null;
+ return;
}
+ previewBody.querySelectorAll('.elements-component-code, .elements-preview-default').forEach(node => {
+ node.classList.add('elements-hidden');
+ });
- componentCode = componentCode.replace('{{COMPONENT}}', 'elements-' + comp.name);
- componentCode = componentCode.replace('{{CATEGORY}}', 'elements-' + data.getCategoryById(currentCategoryId).name);
-
- // Apply random IDs.
- componentCode = applyRandomID(componentCode);
+ if (!componentId) {
+ const defaultHint = previewBody.querySelector('.elements-preview-default');
+ if (defaultHint) {
+ defaultHint.classList.remove('elements-hidden');
+ }
+ previewComponentId = null;
+ return;
+ }
- // Apply lang strings.
- componentCode = applyLangStrings(componentCode);
+ const comp = data.getComponentById(componentId);
+ if (!comp) {
+ previewComponentId = null;
+ return;
+ }
+ const node = previewBody.querySelector(`div[data-id="code-preview-${componentId}"]`);
+ if (!node) {
+ previewComponentId = null;
+ return;
+ }
+ const flavor = comp.flavors.length > 0 ? currentFlavor : '';
+ const placeholder = (selection.length > 0 ? selection : comp.text);
+ node.innerHTML = updateComponentCode(comp.code, componentId, placeholder, flavor);
+ node.classList.remove('elements-hidden');
+ previewComponentId = componentId;
+};
- return componentCode;
+/**
+ * Re-render the preview for the currently selected component.
+ *
+ * @param {obj} modal
+ */
+const refreshSelectedPreview = (modal) => {
+ if (selectedComponentId) {
+ showPreviewFor(modal, selectedComponentId);
+ } else {
+ showPreviewFor(modal, null);
+ }
};
/**
- * Handle a click in a component button.
+ * Reveal the variant strip below the grid, populated for the given component.
*
- * @param {MouseEvent} event The click event
- * @param {obj} editor
* @param {obj} modal
+ * @param {string} componentId
*/
-const handleButtonClick = async(event, editor, modal) => {
- const selectedButton = event.target.closest('button').dataset.id;
+const showVariantStripFor = (modal, componentId) => {
+ cancelHideVariantStrip();
+ const root = modal.getRoot()[0];
+ const strip = root.querySelector('[data-region="variant-strip"]');
+ const target = root.querySelector('[data-region="variant-target"]');
+ const chips = root.querySelector('[data-region="variant-chips"]');
+ if (!strip || !chips) {
+ return;
+ }
- const comp = data.getComponentById(selectedButton);
+ const comp = data.getComponentById(componentId);
+ if (!comp) {
+ return;
+ }
+ const variants = data.getComponentVariants(comp);
+ if (variants.length === 0) {
+ strip.setAttribute('hidden', '');
+ return;
+ }
- // Component button.
- if (comp) {
- let componentCode = comp.code;
- const placeholder = (selection.length > 0 ? selection : comp.text);
-
- let flavor = comp.flavors.length > 0 ? currentFlavor : '';
-
- // Create a new node to replace the placeholder.
- const randomId = generateRandomID();
- const newNode = document.createElement('span');
- newNode.dataset.id = randomId;
- newNode.innerHTML = placeholder;
- componentCode = updateComponentCode(componentCode, selectedButton, newNode.outerHTML, flavor);
- // Sets new content.
- editor.selection.setContent(componentCode);
-
- // Select text.
- const nodeSel = editor.dom.select('span[data-id="' + randomId + '"]');
- if (nodeSel?.[0]) {
- editor.selection.select(nodeSel[0]);
- }
+ if (stripComponentId === componentId && !strip.hasAttribute('hidden')) {
+ return;
+ }
+ stripComponentId = componentId;
+
+ const flavor = comp.flavors.length > 0 ? currentFlavor : '';
+ const activeClasses = getVariantsClass(comp.name, flavor);
+
+ chips.innerHTML = '';
+ if (target) {
+ target.textContent = comp.displayname;
+ }
+
+ variants.forEach(variant => {
+ const isOn = activeClasses.includes(variant.variantclass);
+ const chip = document.createElement('button');
+ chip.type = 'button';
+ chip.className = 'elements-variant-chip ' + (isOn ? 'active' : '');
+ chip.dataset.variant = variant.name;
+ chip.dataset.variantclass = variant.variantclass;
+ chip.dataset.state = isOn ? 'on' : 'off';
+ chip.title = variant.displayname || variant.name;
+ chip.textContent = variant.displayname || variant.name;
+ chip.addEventListener('click', () => handleVariantChipClick(chip, modal));
+ chips.appendChild(chip);
+ });
+
+ strip.removeAttribute('hidden');
+};
+
+const scheduleHideVariantStrip = (modal) => {
+ cancelHideVariantStrip();
+ stripHideTimer = setTimeout(() => hideVariantStrip(modal), 150);
+};
- modal.destroy();
- editor.focus();
- editor.undoManager.add();
+const cancelHideVariantStrip = () => {
+ if (stripHideTimer) {
+ clearTimeout(stripHideTimer);
+ stripHideTimer = null;
}
};
+const hideVariantStrip = (modal) => {
+ cancelHideVariantStrip();
+ const strip = modal.getRoot()[0].querySelector('[data-region="variant-strip"]');
+ if (strip) {
+ strip.setAttribute('hidden', '');
+ }
+ stripComponentId = null;
+};
+
/**
- * Handle a mouse events mouseenter/mouseleave in a component button.
+ * Toggle a variant on a card via dot click.
*
- * @param {MouseEvent} event The click event
+ * @param {MouseEvent} event
* @param {obj} modal
- * @param {bool} show
*/
-const handleButtonMouseEvent = (event, modal, show) => {
- const selectedButton = event.target.closest('button').dataset.id;
- const node = modal.getRoot()[0].querySelector('div[data-id="code-preview-' + selectedButton + '"]');
- const previewDefault = modal.getRoot()[0].querySelector('div[data-id="code-preview-default"]');
- const comp = data.getComponentById(selectedButton);
+const handleDotClick = (event, modal) => {
+ event.stopPropagation();
+ event.preventDefault();
+ const dot = event.currentTarget;
+ const card = dot.closest('.elementst-dialog-button');
+ if (!card) {
+ return;
+ }
+ toggleVariantForComponent(modal, card.dataset.id, dot.dataset.variant, dot.dataset.variantclass);
+};
+
+/**
+ * Toggle a variant via chip click in the strip.
+ *
+ * @param {HTMLElement} chip
+ * @param {obj} modal
+ */
+const handleVariantChipClick = (chip, modal) => {
+ if (!stripComponentId) {
+ return;
+ }
+ toggleVariantForComponent(modal, stripComponentId, chip.dataset.variant, chip.dataset.variantclass);
+};
- let flavor = comp.flavors.length > 0 ? currentFlavor : '';
+/**
+ * Core toggle: flip a variant on a component, sync dot, chip, and preview.
+ *
+ * @param {obj} modal
+ * @param {string} componentId
+ * @param {string} variantName
+ * @param {string} variantClass
+ */
+const toggleVariantForComponent = (modal, componentId, variantName, variantClass) => {
+ const comp = data.getComponentById(componentId);
+ if (!comp) {
+ return;
+ }
+ const flavor = comp.flavors.length > 0 ? currentFlavor : '';
+ const activeBefore = getVariantsClass(comp.name, flavor).includes(variantClass);
+ const turningOn = !activeBefore;
- const placeholder = (selection.length > 0 ? selection : comp.text);
+ if (turningOn) {
+ addVariant(comp.name, variantName, flavor);
+ } else {
+ removeVariant(comp.name, variantName, flavor);
+ }
- node.innerHTML = updateComponentCode(comp.code, selectedButton, placeholder, flavor);
+ const card = modal.getRoot()[0].querySelector(`.elementst-dialog-button[data-id="${componentId}"]`);
+ if (card) {
+ const dot = card.querySelector(`.elements-v-dot[data-variantclass="${variantClass}"]`);
+ if (dot) {
+ dot.classList.toggle('on', turningOn);
+ }
+ }
- if (node) {
- if (show) {
- previewDefault.classList.toggle('elements-hidden');
- node.classList.toggle('elements-hidden');
- } else {
- node.classList.toggle('elements-hidden');
- previewDefault.classList.toggle('elements-hidden');
+ if (stripComponentId === componentId) {
+ const chip = modal.getRoot()[0].querySelector(
+ `.elements-variant-chip[data-variantclass="${variantClass}"]`
+ );
+ if (chip) {
+ chip.dataset.state = turningOn ? 'on' : 'off';
+ chip.classList.toggle('active', turningOn);
}
}
+
+ if (previewComponentId === componentId) {
+ showPreviewFor(modal, componentId);
+ }
+ updatePreviewSummary(modal);
};
/**
- * Handle a mouse event within the variant buttons.
+ * Sync the dot indicators on a single card to current preferences.
*
- * @param {MouseEvent} event The mouseenter/mouseleave event
- * @param {obj} modal
+ * @param {HTMLElement} card
*/
-const handleVariantClick = (event, modal) => {
- event.stopPropagation();
- const variant = event.target.closest('span');
- const button = event.target.closest('button');
- const comp = data.getComponentById(button.dataset.id);
+const refreshCardDots = (card) => {
+ const comp = data.getComponentById(card.dataset.id);
+ if (!comp) {
+ return;
+ }
const flavor = comp.flavors.length > 0 ? currentFlavor : '';
+ const activeClasses = getVariantsClass(comp.name, flavor);
+ card.querySelectorAll('.elements-v-dot').forEach(dot => {
+ dot.classList.toggle('on', activeClasses.includes(dot.dataset.variantclass));
+ });
+};
- updateVariantComponentState(variant, button, modal, false, true);
+/**
+ * Update the summary line in the preview footer.
+ *
+ * @param {obj} modal
+ */
+const updatePreviewSummary = (modal) => {
+ const root = modal.getRoot()[0];
+ const summary = root.querySelector('[data-region="preview-summary"]');
+ if (!summary) {
+ return;
+ }
+ if (!selectedComponentId) {
+ summary.textContent = '';
+ return;
+ }
+ const comp = data.getComponentById(selectedComponentId);
+ if (!comp) {
+ summary.textContent = '';
+ return;
+ }
+ const parts = [comp.displayname];
+ const cat = data.getCategoryById(currentCategoryId);
+ if (cat) {
+ parts.push(cat.displayname);
+ }
+ if (currentFlavor) {
+ const chip = root.querySelector(
+ `.elements-flavor-chip[data-flavor="${currentFlavor}"][data-categoryid="${currentCategoryId}"]`
+ );
+ if (chip) {
+ parts.push(chip.textContent.trim());
+ }
+ }
+ summary.textContent = parts.join(' · ');
+};
- const node = modal.getRoot()[0].querySelector('div[data-id="code-preview-' + button.dataset.id + '"]');
- node.innerHTML = updateComponentCode(
- comp.code,
- button.dataset.id,
- comp.text,
- flavor
- );
+/**
+ * Reset selection state and disable Insert.
+ *
+ * @param {obj} modal
+ */
+const clearSelection = (modal) => {
+ selectedComponentId = null;
+ const root = modal.getRoot()[0];
+ root.querySelectorAll('.elementst-dialog-button.selected').forEach(node => {
+ node.classList.remove('selected');
+ node.setAttribute('aria-selected', 'false');
+ });
+ const strip = root.querySelector('[data-region="variant-strip"]');
+ if (strip) {
+ strip.setAttribute('hidden', '');
+ }
+ const chips = root.querySelector('[data-region="variant-chips"]');
+ if (chips) {
+ chips.innerHTML = '';
+ }
+ const insertBtn = root.querySelector('.elements-insert-btn');
+ if (insertBtn) {
+ insertBtn.disabled = true;
+ }
+ showPreviewFor(modal, null);
+ updatePreviewSummary(modal);
};
/**
- * Update a variant component UI.
+ * Insert the selected component into the editor.
*
- * @param {obj} variant
- * @param {obj} button
+ * @param {obj} editor
* @param {obj} modal
- * @param {bool} show
- * @param {bool} updateHtml
*/
-const updateVariantComponentState = (variant, button, modal, show, updateHtml) => {
- const selectedVariant = variant.dataset.variantclass;
- const selectedButton = button.dataset.id;
- const componentClass = button.dataset.classcomponent;
- const previewComponent = modal.getRoot()[0]
- .querySelector('div[data-id="code-preview-' + button.dataset.id + '"] .' + componentClass);
- const variantPreview = modal.getRoot()[0]
- .querySelector('span[data-id="variantHTML-' + button.dataset.id + '"]');
- const comp = data.getComponentById(selectedButton);
- let variantsHtml = '';
- let hasflavors = comp.flavors.length > 0;
-
- if (previewComponent) {
- if (updateHtml) {
- if (variant.dataset.state == 'on') {
- removeVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : '');
- updateVariantButtonState(variant, false);
- previewComponent.classList.remove(selectedVariant);
- } else {
- addVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : '');
- updateVariantButtonState(variant, true);
- previewComponent.classList.add(selectedVariant);
- }
-
- // Update variant preview HTML.
- if (variantPreview) {
- variantPreview.innerHTML = getVariantsHtml(comp.name, currentFlavor);
- }
- } else {
- variantsHtml = getVariantsHtml(comp.name, currentFlavor);
- if (show) {
- previewComponent.classList.add(selectedVariant);
- variantsHtml += getVariantHtml(variant.dataset.variant);
- } else {
- previewComponent.classList.remove(selectedVariant);
- }
-
- // Update variant preview HTML.
- if (variantPreview) {
- variantPreview.innerHTML = variantsHtml;
- }
- }
- } else {
- // Update variants preferences.
- if (variant.dataset.state == 'on') {
- removeVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : '');
- updateVariantButtonState(variant, false);
- } else {
- addVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : '');
- updateVariantButtonState(variant, true);
- }
+const handleInsertClick = (editor, modal) => {
+ if (!selectedComponentId) {
+ return;
+ }
+ const comp = data.getComponentById(selectedComponentId);
+ if (!comp) {
+ return;
}
+
+ const flavor = comp.flavors.length > 0 ? currentFlavor : '';
+ const placeholder = (selection.length > 0 ? selection : comp.text);
+ const randomId = generateRandomID();
+ const newNode = document.createElement('span');
+ newNode.dataset.id = randomId;
+ newNode.innerHTML = placeholder;
+ const componentCode = updateComponentCode(comp.code, selectedComponentId, newNode.outerHTML, flavor);
+
+ editor.selection.setContent(componentCode);
+ const nodeSel = editor.dom.select('span[data-id="' + randomId + '"]');
+ if (nodeSel?.[0]) {
+ editor.selection.select(nodeSel[0]);
+ }
+
+ modal.destroy();
+ editor.focus();
+ editor.undoManager.add();
};
/**
- * Update a variant button UI.
+ * Modal close — persist selections.
*
- * @param {obj} variant
- * @param {bool} activate
+ * @param {obj} editor
*/
-const updateVariantButtonState = (variant, activate) => {
- if (activate) {
- variant.dataset.state = 'on';
- variant.classList.remove(variant.dataset.variant + '-variant-off');
- variant.classList.add(variant.dataset.variant + '-variant-on');
- variant.classList.add('on');
+const handleModalHidden = (editor) => {
+ editor.targetElm.closest('body').classList.remove('elements-modal-no-preview');
+ if (currentCategoryId) {
+ savePreferences([
+ {type: Preferences.category, value: currentCategoryId},
+ {type: Preferences.category_flavors, value: JSON.stringify(lastFlavor)},
+ {type: Preferences.component_variants, value: JSON.stringify(getVariantPreferences())}
+ ]);
+ }
+};
+
+const updateComponentCode = (componentCode, selectedButton, placeholder, flavor = '') => {
+ componentCode = componentCode.replace('{{PLACEHOLDER}}', placeholder);
+ const comp = data.getComponentById(selectedButton);
+ const variants = getVariantsClass(comp.name, flavor);
+
+ if (variants.length > 0) {
+ componentCode = componentCode.replace('{{VARIANTS}}', variants.join(' '));
+ componentCode = componentCode.replace('{{VARIANTSHTML}}', getVariantsHtml(comp.name, flavor));
+ } else {
+ componentCode = componentCode.replace('{{VARIANTS}}', '');
+ componentCode = componentCode.replace('{{VARIANTSHTML}}', '');
+ }
+
+ if (currentFlavor) {
+ componentCode = componentCode.replace('{{FLAVOR}}', 'elements-' + currentFlavor + '-flavor');
} else {
- variant.dataset.state = 'off';
- variant.classList.remove(variant.dataset.variant + '-variant-on');
- variant.classList.add(variant.dataset.variant + '-variant-off');
- variant.classList.remove('on');
+ componentCode = componentCode.replace('{{FLAVOR}}', '');
}
+
+ componentCode = componentCode.replace('{{COMPONENT}}', 'elements-' + comp.name);
+ componentCode = componentCode.replace('{{CATEGORY}}', 'elements-' + data.getCategoryById(currentCategoryId).name);
+
+ componentCode = applyRandomID(componentCode);
+ componentCode = applyLangStrings(componentCode);
+
+ return componentCode;
};
/**
- * Show/hide buttons depend on selected context.
+ * Show only buttons of the active category.
*
* @param {object} modal
* @param {String} context
*/
const showCategoryButtons = (modal, context) => {
- const showNodes = modal.getRoot()[0].querySelectorAll('button[data-type="' + context + '"]');
- const hideNodes = modal.getRoot()[0].querySelectorAll('button[data-type]:not([data-type="' + context + '"])');
-
- showNodes.forEach(node => node.classList.remove('elements-hidden'));
+ const showNodes = modal.getRoot()[0].querySelectorAll(`button[data-type="${context}"]`);
+ const hideNodes = modal.getRoot()[0].querySelectorAll(`button[data-type]:not([data-type="${context}"])`);
+ showNodes.forEach(node => {
+ node.classList.remove('elements-hidden');
+ refreshCardDots(node);
+ });
hideNodes.forEach(node => node.classList.add('elements-hidden'));
};
@@ -519,21 +725,19 @@ const showCategoryButtons = (modal, context) => {
* Replace all localized strings.
*
* @param {String} text
- * @return {String} String with lang tags replaced with a localized string.
+ * @return {String}
*/
const applyLangStrings = (text) => {
const compRegex = /{{#([^}]*)}}/g;
-
[...text.matchAll(compRegex)].forEach(strLang => {
text = text.replace('{{#' + strLang[1] + '}}', data.getLangString(strLang[1]));
});
-
return text;
};
/**
* Generates a random string.
- * @return {string} A random string
+ * @return {string}
*/
const generateRandomID = () => {
const timestamp = new Date().getTime();
@@ -543,14 +747,12 @@ const generateRandomID = () => {
/**
* Replace all ID tags with a random string.
* @param {String} text
- * @return {String} String with all ID tags replaced with a random string.
+ * @return {String}
*/
const applyRandomID = (text) => {
const compRegex = /{{@ID}}/g;
-
if (text.match(compRegex)) {
text = text.replace(compRegex, generateRandomID());
}
-
return text;
};
diff --git a/classes/local/utils.php b/classes/local/utils.php
index 7ad689c..10118af 100644
--- a/classes/local/utils.php
+++ b/classes/local/utils.php
@@ -435,9 +435,11 @@ public static function variant_icon_css(string $variant, string $iconurl): strin
* @return string
*/
public static function button_icon_css(string $buttonclass, string $iconurl, string $flavor = ''): string {
- $flavor = empty($flavor) ? '' : '[data-flavor="' . $flavor . '"]';
+ $flavorattr = empty($flavor) ? '' : '[data-flavor="' . $flavor . '"]';
+ $flavorparent = empty($flavor) ? '' : '.elementst-dialog-button[data-flavor="' . $flavor . '"] ';
return <<management page to edit categories, components, flavors and variants.';
$string['linktomanagername'] = 'Link to management';
@@ -114,6 +116,7 @@
$string['newmetadata'] = 'New license "{$a}"';
$string['newmetadatafilemissing'] = 'Related file not found: "{$a}"';
$string['newvariant'] = 'New variant "{$a}"';
+$string['noflavor'] = 'No flavor';
$string['pluginname'] = 'Course elements';
$string['preview'] = 'Preview';
$string['previewall'] = 'Preview all elements';
diff --git a/scss/_buttons.scss b/scss/_buttons.scss
index 786e223..f160eee 100644
--- a/scss/_buttons.scss
+++ b/scss/_buttons.scss
@@ -1,134 +1,326 @@
-/* PLUGIN UI STYLES */
+/* PLUGIN UI STYLES — Tiny Elements Modal */
.elements-plugin-container {
- .nav.nav-tabs {
- flex-wrap: wrap;
- .nav-item a.active {
- background-color: #f6f7f8;
- border-bottom: 1px solid #f6f7f8;
- &.dropdown-item {
- color: inherit;
- }
- }
- &,
- .dropdown-item {
- font-size: .8rem;
- }
- .nav-link {
- border: 1px solid #d3d3d3;
- border-bottom: none;
- border-top-right-radius: .2rem;
- border-top-left-radius: .2rem;
- }
+ --elements-orange: #d33f01;
+ --elements-orange-dark: #a93201;
+ --elements-orange-bright: #ee7430;
+ --elements-orange-soft: #fce3d6;
+ --elements-orange-soft-2: #f9c7ac;
+ --elements-blue-1: #25263e;
+ --elements-blue-2: #194d83;
+ --elements-blue-3: #00aada;
+ --elements-surface: #ececef;
+ --elements-surface-2: #e0e0e4;
+ --elements-border: #d4d4d9;
+ --elements-gray-400: #a4a5ac;
+ --elements-gray-600: #60616d;
+ --elements-gray-900: #00091e;
+ --elements-radius: 0.375rem;
+ --elements-radius-md: 0.5rem;
+ --elements-shadow-card: 0 1px 2px rgba(0, 9, 30, 0.06), 0 1px 3px rgba(0, 9, 30, 0.08);
+ --elements-shadow-card-hover: 0 4px 10px rgba(0, 9, 30, 0.10), 0 2px 4px rgba(0, 9, 30, 0.06);
+
+ display: flex;
+ flex-direction: column;
+ min-height: 60vh;
+ max-height: 80vh;
+ background-color: var(--elements-surface);
+ color: var(--elements-gray-900);
+}
+
+.elements-modal-grid {
+ display: grid;
+ grid-template-columns: 240px minmax(0, 1fr);
+ grid-template-rows: minmax(0, 1fr);
+ min-height: 0;
+ flex: 1;
+}
+
+.elements-modal-grid:not(.elements-no-preview) {
+ grid-template-columns: 240px minmax(0, 1fr) 340px;
+}
+
+/* ---------- Left rail: categories with nested flavor list ---------- */
+
+.elements-rail {
+ background: var(--elements-surface);
+ border-right: 1px solid var(--elements-border);
+ overflow-y: auto;
+ padding: 12px 0;
+ min-width: 0;
+}
+
+.elements-rail-list {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.elements-rail-group {
+ display: flex;
+ flex-direction: column;
+}
+
+.elements-rail-item {
+ display: flex;
+ align-items: center;
+ padding: 10px 14px;
+ border: none;
+ border-radius: 0;
+ background: transparent;
+ color: var(--elements-gray-900);
+ text-align: left;
+ cursor: pointer;
+ transition: background 80ms;
+
+ &:hover {
+ background: #fff;
+ }
+
+ &.active {
+ background: #fff;
}
}
-.elements-buttons-preview {
+.elements-rail-meta {
display: flex;
- justify-content: space-between;
- width: 100%;
- min-height: 31vh;
- max-height: 60vh;
- background-color: #f6f7f8;
+ flex-direction: column;
+ min-width: 0;
}
-.elements-buttons-grid {
+.elements-rail-name {
+ font-size: 0.92rem;
+ font-weight: 600;
+ line-height: 1.2;
+ overflow-wrap: anywhere;
+}
+
+.elements-rail-count {
+ font-size: 0.75rem;
+ color: var(--elements-gray-600);
+}
+
+.elements-flavor-list {
display: flex;
- flex-wrap: wrap;
- justify-content: start;
- row-gap: 12px;
- column-gap: 12px;
- margin-bottom: 0;
- width: 100%;
+ flex-direction: column;
+ gap: 1px;
+ margin: 2px 0 4px;
+ padding: 2px 0 2px 12px;
+ background: #fff;
+
+ &[hidden] {
+ display: none;
+ }
+}
+
+/* ---------- Main column ---------- */
+
+.elements-main {
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ overflow: hidden;
+ background: #fff;
+}
+
+.elements-bar-label {
+ font-size: 0.7rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--elements-gray-400);
+}
+
+/* ---------- Component grid ---------- */
+
+.elements-grid-wrap {
+ flex: 1;
overflow-y: auto;
+ overflow-x: hidden;
+ padding: 18px 22px 22px;
+ min-width: 0;
+ background: #fff;
}
-.elements-hidden {
- display: none !important;
- max-height: 0;
- padding: 0;
+.elements-buttons-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+ gap: 10px;
+ min-width: 0;
}
.elementst-dialog-button {
+ position: relative;
display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
+ flex-direction: column;
+ gap: 8px;
+ padding: 10px 12px 12px;
+ border: 1px solid var(--elements-border);
+ border-radius: var(--elements-radius-md);
+ background: #fff;
+ cursor: pointer;
+ text-align: left;
+ color: var(--elements-gray-900);
+ min-height: 120px;
+ transition: border-color 100ms, box-shadow 100ms, transform 100ms;
+
+ &:hover {
+ border-color: var(--elements-orange);
+ box-shadow: var(--elements-shadow-card-hover);
+ transform: translateY(-1px);
+ }
+
+ &.selected {
+ border-color: var(--elements-orange);
+ outline: 2px solid var(--elements-orange-soft);
+ outline-offset: -1px;
+ box-shadow: var(--elements-shadow-card-hover);
+ }
+}
+
+.elements-comp-icon {
position: relative;
- height: 48px;
- max-height: 100px;
- width: 100%;
- max-width: 100%;
- background-color: #fff;
- border: 1px solid #e4e9ec;
- color: #535d76;
- box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.10);
- overflow-wrap: anywhere;
- overflow: hidden;
- padding: 0;
- margin-bottom: 8px;
+ width: 80px;
+ height: 80px;
+ background: transparent;
+
+ /* Dynamic CSS (utils::button_icon_css) sets background-image + content:'' on ::before. */
+ &::before {
+ position: absolute;
+ inset: 0;
+ background-size: contain;
+ background-position: center;
+ background-repeat: no-repeat;
+ }
+
+ .icon {
+ display: block;
+ width: 100%;
+ height: 100%;
+ background-size: contain;
+ background-position: center;
+ background-repeat: no-repeat;
+ margin: 0;
+ }
}
-.elementst-dialog-button.elements-custom-icon .elements-button-text i {
- position: absolute;
- background-repeat: no-repeat;
- background-size: contain;
- background-position: center;
- min-height: 23px;
- min-width: 49px;
- top: auto;
- left: 0;
- border-right: 1px solid #dce3f0;
+.elements-comp-name {
+ font-size: 0.88rem;
+ font-weight: 600;
+ line-height: 1.25;
+ overflow-wrap: anywhere;
}
-.elementst-dialog-button .elements-button-text {
+.elements-v-dots {
display: flex;
+ gap: 6px;
align-items: center;
- height: 100%;
+ justify-content: flex-end;
+ min-height: 15px;
+}
+
+.elements-v-dot {
+ width: 15px;
+ height: 15px;
+ padding: 0;
+ border-radius: 25%;
+ background: var(--elements-surface-2);
+ border: 1px solid var(--elements-border);
+ cursor: pointer;
+ transition: background 80ms, border-color 80ms, transform 80ms;
+
+ &:hover {
+ border-color: var(--elements-orange);
+ transform: scale(1.15);
+ }
+
+ &:focus,
+ &:focus-visible {
+ outline: none;
+ box-shadow: none;
+ border-color: var(--elements-orange);
+ }
+
+ &.on {
+ background: var(--elements-orange);
+ border-color: var(--elements-orange);
+ }
+}
+
+.elements-hidden {
+ display: none !important;
}
-.elementst-dialog-button .elements-button-text::before {
- position: absolute;
- left: 0;
- height: 100%;
- min-height: 23px;
+/* ---------- Preview pane ---------- */
+
+.elements-preview {
+ background: var(--elements-surface);
+ border-left: 1px solid var(--elements-border);
display: flex;
- justify-content: center;
- align-items: center;
- border-right: 1px solid #dce3f0;
- width: 60px;
- height: 60px;
- top: 3px;
- align-content: center;
+ flex-direction: column;
+ overflow: hidden;
}
-.elementst-dialog-button:hover,
-.elementst-dialog-button:active {
- box-shadow: inset 0 0 3px rgba(22, 121, 249, 0.3);
+.elements-preview-head {
+ padding: 14px 20px;
+ border-bottom: 1px solid var(--elements-border);
}
-.elementst-dialog-button .elements-button-text {
+.elements-preview-body {
+ flex: 1;
+ padding: 18px 20px;
+ overflow-y: auto;
+ background: #fff;
+ border: 1px solid var(--elements-border);
+ border-radius: var(--elements-radius-md);
+ margin: 14px 16px;
position: relative;
- font-size: 12px;
- font-weight: 500;
- width: 100%;
- text-align: left;
- padding-left: 62px;
}
-.elements-code-preview {
- display: none;
- overflow: hidden;
- background-color: #fff;
- border-top: 2px solid #f6f7f8;
+.elements-preview-default {
+ border: 1px dashed var(--elements-border);
+ border-radius: var(--elements-radius-md);
+ padding: 22px 14px;
+ color: var(--elements-gray-600);
+ font-size: 0.85rem;
+ line-height: 1.5;
+ text-align: center;
+ user-select: none;
+}
+
+.elements-component-code {
+ transform: scale(0.7);
+ transform-origin: top left;
+ width: calc(100% / 0.7);
+}
+
+.elements-preview-footer {
+ padding: 14px 20px;
+ border-top: 1px solid var(--elements-border);
+ background: var(--elements-surface);
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
}
-.elements-code-preview .elements-component-code {
- -webkit-transform: scale(0.6);
- -ms-transform: scale(0.6);
- transform: scale(0.6);
- width: 160%;
- margin-top: 35px;
- position: absolute;
+.elements-preview-summary {
+ font-size: 0.78rem;
+ color: var(--elements-gray-600);
+ line-height: 1.4;
+ min-height: 1.4em;
}
+.elements-insert-btn {
+ width: 100%;
+
+ &[disabled] {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+}
+
+.elements-footer-inline {
+ grid-column: 1 / -1;
+ border-top: 1px solid var(--elements-border);
+ background: var(--elements-surface);
+}
diff --git a/scss/_buttons_variants.scss b/scss/_buttons_variants.scss
index db0c433..aba5b6c 100644
--- a/scss/_buttons_variants.scss
+++ b/scss/_buttons_variants.scss
@@ -1,39 +1,94 @@
-/* BUTTON VARIANTS */
-
-.elementst-dialog-button .elements-button-variants {
- display: grid;
- grid-auto-flow: column;
- grid-gap: 3px;
- justify-content: end;
- width: 100%;
- height: 42px;
- margin-right: 3px;
-
- .elements-button-variant {
- display: flex;
- justify-content: center;
- align-items: center;
- background-color: #ecf3ff;
- color: #1679f9;
- height: 42px;
- width: 36px;
-
- &::before {
- content: ' ';
- background-size: 50%;
- background-repeat: no-repeat;
- background-position: center;
- height: 42px;
- width: 36px;
- }
+/* VARIANT STRIP — context-sensitive variant chips above the grid */
+
+.elements-variant-strip {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 22px;
+ background: var(--elements-orange-soft);
+ border-top: 1px solid var(--elements-orange-soft-2);
+
+ &[hidden] {
+ display: none;
+ }
+}
+
+.elements-variant-strip .elements-bar-label {
+ color: var(--elements-orange-dark);
+}
+
+.elements-variant-target {
+ font-size: 0.78rem;
+ font-weight: 600;
+ color: var(--elements-orange-dark);
+}
+
+.elements-variant-chips {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+}
+
+.elements-variant-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 5px 10px;
+ border: 1px solid var(--elements-orange-soft-2);
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.7);
+ color: var(--elements-orange-dark);
+ font-size: 0.82rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 80ms;
+
+ &:hover {
+ background: #fff;
+ }
+
+ &.active {
+ background: var(--elements-orange);
+ border-color: var(--elements-orange);
+ color: #fff;
+ }
+}
+
+/* Flavor list (now inside the rail, under the active category) */
+
+.elements-flavor-chip {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 10px;
+ border: none;
+ border-radius: var(--elements-radius);
+ background: transparent;
+ color: var(--elements-gray-600);
+ font-size: 0.82rem;
+ font-weight: 500;
+ cursor: pointer;
+ text-align: left;
+ transition: background 80ms, color 80ms;
+
+ &:hover {
+ background: rgba(255, 255, 255, 0.6);
+ color: var(--elements-gray-900);
+ }
+
+ &.active {
+ background: #fff;
+ color: var(--elements-orange);
+ font-weight: 600;
+ }
- &.on, &:hover {
- background-color: #1679f9;
- color: #fff;
+ &.elements-flavor-none {
+ font-style: italic;
- &::before {
- filter: brightness(10);
- }
+ &.active {
+ color: var(--elements-gray-900);
+ font-style: normal;
}
}
}
diff --git a/scss/_overrides.scss b/scss/_overrides.scss
index 77c435b..ca70c47 100644
--- a/scss/_overrides.scss
+++ b/scss/_overrides.scss
@@ -4,3 +4,20 @@ body.mce-content-body,
display: block !important;
}
}
+
+.elements-modal,
+.elements-modal-no-preview {
+ .modal-body {
+ padding: 0;
+ }
+}
+
+.elements-flavor-chip,
+.elements-rail-item {
+ /* Scope our buttons to opt out of Moodle/Bootstrap default button reset. */
+ &,
+ &:focus,
+ &:active {
+ box-shadow: none;
+ }
+}
diff --git a/scss/_responsive.scss b/scss/_responsive.scss
index ee09610..794e59c 100644
--- a/scss/_responsive.scss
+++ b/scss/_responsive.scss
@@ -1,163 +1,118 @@
-/* Media queries */
+/* RESPONSIVE BREAKPOINTS — Tiny Elements Modal */
-@media only screen and (min-width: 576px) {
+@media (min-width: 576px) {
.elements-modal.modal-open .modal-dialog,
.elements-modal-no-preview.modal-open .modal-dialog {
- min-width: 550px;
+ min-width: 720px;
}
+}
- .elements-select-filters {
- display: none;
+@media (min-width: 992px) {
+ .elements-modal.modal-open .modal-dialog {
+ max-width: 1320px;
}
- .elements-buttons-filters, .elements-buttons-flavors {
- display: flex;
- justify-content: center;
- align-items: center;
- margin: 10px 0 10px;
+ .elements-modal-no-preview.modal-open .modal-dialog {
+ max-width: 900px;
}
}
-@media only screen and (min-width: 992px) {
- .elements-modal-no-preview.modal-open .modal-dialog {
- max-width: 550px;
+/* Below 992px: drop the preview column. The footer (with Insert button) reflows under the grid. */
+@media (max-width: 991px) {
+ .elements-modal-grid,
+ .elements-modal-grid:not(.elements-no-preview) {
+ grid-template-columns: 220px minmax(0, 1fr);
}
- .elements-buttons-preview.elements-no-preview {
- justify-content: center;
+ .elements-preview {
+ display: none;
}
- .elements-buttons-preview.elements-no-preview .elements-buttons-grid {
- grid-gap: 9px;
- justify-content: center;
- width: 405px;
+ .elements-main {
+ position: relative;
}
- .elements-buttons-preview.elements-no-preview .elements-code-preview {
- display: none;
+ .elements-footer-inline {
+ position: sticky;
+ bottom: 0;
+ z-index: 2;
}
+}
- .elements-buttons-grid {
- width: 440px;
- padding: 10px;
- }
-
- .elementst-dialog-button.elements-custom-icon .elements-button-text i {
- top: 20px;
- left: 0;
- right: 0;
- margin: 0 auto;
- max-width: 25px;
- min-width: 25px;
- width: 100%;
- border-right: none;
+/* Below 576px: rail collapses into a horizontal scrolling strip above the main column.
+ The inline flavor list is hidden in this mode — flavors selectable via the active category only. */
+@media (max-width: 575px) {
+ .elements-plugin-container {
+ max-height: 90vh;
}
- .elementst-dialog-button {
- height: 100px;
- width: 116px;
- max-width: 116px;
- box-shadow: none;
- border-radius: 4px;
- margin-bottom: 0;
+ .elements-modal-grid,
+ .elements-modal-grid:not(.elements-no-preview) {
+ grid-template-columns: minmax(0, 1fr);
+ grid-template-rows: auto minmax(0, 1fr);
}
- .elementst-dialog-button::before {
- content: '';
+ .elements-rail {
+ border-right: none;
+ border-bottom: 1px solid var(--elements-border);
+ max-height: 90px;
+ overflow-x: auto;
+ overflow-y: hidden;
+ padding: 8px;
}
- .elementst-dialog-button .elements-button-text::before {
- left: 0;
- right: 0;
- margin: 0 auto;
- border-right: none;
- display: inline-block;
- width: 60px;
- height: 60px;
- top: 3px;
- }
-
- .elementst-dialog-button .elements-button-text {
- padding: 70px 8px 15px 8px;
- font-size: 12px;
- font-weight: 500;
- line-height: 13px;
- width: 80px;
- text-align: center;
- justify-content: center;
+ .elements-rail-list {
+ flex-direction: row;
+ gap: 6px;
}
- .elementst-dialog-button .elements-button-variants {
- grid-auto-flow: row;
- grid-gap: 0;
- align-content: flex-start;
- justify-content: start;
- width: 36px;
- height: 99px;
- background-color: #ecf3ff;
- margin: 0;
+ .elements-rail-group {
+ flex-direction: row;
}
- .elementst-dialog-button .elements-button-variants .elements-button-variant {
- display: flex;
- justify-content: center;
+ .elements-rail-item {
+ flex-direction: column;
+ gap: 4px;
+ padding: 6px 10px;
+ min-width: 84px;
+
+ &.active::before {
+ top: auto;
+ bottom: 0;
+ left: 12px;
+ right: 12px;
+ width: auto;
+ height: 3px;
+ border-radius: 3px 3px 0 0;
+ }
+ }
+
+ .elements-rail-meta {
align-items: center;
- color: #1679f9;
- height: 33px;
- width: 36px;
}
- .elementst-dialog-button:hover,
- .elementst-dialog-button:active {
- box-shadow: 0 0 0 3px rgba(22, 121, 249, 0.3);
+ .elements-rail-name {
+ font-size: 0.75rem;
+ text-align: center;
}
- .elementst-dialog-button .elements-button-variants .elements-button-variant:first-child,
- .elementst-dialog-button .elements-button-variants .elements-button-variant:nth-child(2) {
- border-bottom: 1px solid #fff;
+ .elements-rail-count {
+ display: none;
}
- .elementst-dialog-button:hover .elements-button-variants .elements-button-variant {
- box-shadow: none;
+ .elements-flavor-list {
+ display: none;
}
- .elements-code-preview {
- position: relative;
- display: flex;
- flex-direction: column;
- width: 377px;
- align-items: center;
- justify-content: center;
+ .elements-variant-strip {
+ padding: 8px 12px;
}
- .elements-preview-default {
- border: 1px solid #e1e5ee;
- border-radius: 8px;
- color: #9297a1;
- padding: 23px 10px;
- font-weight: 400;
- font-size: 12px;
- line-height: 16px;
- text-align: center;
- width: 60%;
- margin: 0 auto;
- -webkit-user-select: none;
- -ms-user-select: none;
- user-select: none;
- }
-
- .elements-text-preview {
- position: absolute;
- top: 10px;
- right: 10px;
- font-weight: 600;
- font-size: 9.5px;
- line-height: 11px;
- letter-spacing: 0.06em;
- color: #fff;
- background-color: #535d76;
- border-radius: 6px;
- padding: 5px;
- text-transform: uppercase;
+ .elements-grid-wrap {
+ padding: 12px;
+ }
+
+ .elements-buttons-grid {
+ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
}
diff --git a/styles.css b/styles.css
index ab54216..7afea54 100644
--- a/styles.css
+++ b/styles.css
@@ -1 +1 @@
-#page-lib-editor-tiny-plugins-elements-preview #page{padding-top:0;margin-top:0}#page-lib-editor-tiny-plugins-elements-management .col.item,#page-lib-editor-tiny-plugins-elements-printurls .col.item{min-width:250px;max-width:250px}#page-lib-editor-tiny-plugins-elements-management .col.item.addcontainer .card,#page-lib-editor-tiny-plugins-elements-printurls .col.item.addcontainer .card{background-color:#d3d3d3}#page-lib-editor-tiny-plugins-elements-management .tiny_elements_thumbnail,#page-lib-editor-tiny-plugins-elements-printurls .tiny_elements_thumbnail{width:48px;height:48px}#page-lib-editor-tiny-plugins-elements-management .preview.management,#page-lib-editor-tiny-plugins-elements-printurls .preview.management{display:flex;justify-content:space-evenly;width:100%;height:3rem;padding-bottom:.25rem}#page-lib-editor-tiny-plugins-elements-management .preview.management>div,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div{width:45%}#page-lib-editor-tiny-plugins-elements-management .preview.management>div .elements-button-text,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div .elements-button-text{height:100%}#page-lib-editor-tiny-plugins-elements-management .preview.management>div .elements-button-text::before,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div .elements-button-text::before{display:flex;width:unset;height:100%}#page-lib-editor-tiny-plugins-elements-management .preview.management>div.more,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div.more{display:flex;flex-direction:column;justify-content:center;width:10%}#page-lib-editor-tiny-plugins-elements-management .preview.management>div.more .elements-button-text::before,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div.more .elements-button-text::before{all:unset;content:"..."}#page-lib-editor-tiny-plugins-elements-management .preview.management>div.more .elements-button-text,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div.more .elements-button-text{word-wrap:unset}#page-lib-editor-tiny-plugins-elements-management .compcat:hover .card,#page-lib-editor-tiny-plugins-elements-printurls .compcat:hover .card{border:1px solid #787878}#page-lib-editor-tiny-plugins-elements-management .compcat.active .card,#page-lib-editor-tiny-plugins-elements-printurls .compcat.active .card{border:1px solid #0b5190}#page-lib-editor-tiny-plugins-elements-management .card-body,#page-lib-editor-tiny-plugins-elements-printurls .card-body{display:flex;flex-direction:column;padding-top:.75rem}#page-lib-editor-tiny-plugins-elements-management .card-body>div:hover,#page-lib-editor-tiny-plugins-elements-printurls .card-body>div:hover{cursor:pointer}#page-lib-editor-tiny-plugins-elements-management .card-body>div:hover+a.edit,#page-lib-editor-tiny-plugins-elements-printurls .card-body>div:hover+a.edit{color:#094478}#page-lib-editor-tiny-plugins-elements-management form[data-formtype=tiny_elements_comp_flavor] input.form-control,#page-lib-editor-tiny-plugins-elements-printurls form[data-formtype=tiny_elements_comp_flavor] input.form-control{width:100%}#page-lib-editor-tiny-plugins-elements-management .compcat[data-compcat=found-items] a,#page-lib-editor-tiny-plugins-elements-printurls .compcat[data-compcat=found-items] a{display:none}#page-lib-editor-tiny-plugins-elements-management form[data-formtype=tiny_elements_editlicense] div[data-fieldtype=static],#page-lib-editor-tiny-plugins-elements-printurls form[data-formtype=tiny_elements_editlicense] div[data-fieldtype=static]{padding-top:calc(.75rem + 1px)}.elements-plugin-container .nav.nav-tabs{flex-wrap:wrap}.elements-plugin-container .nav.nav-tabs .nav-item a.active{background-color:#f6f7f8;border-bottom:1px solid #f6f7f8}.elements-plugin-container .nav.nav-tabs .nav-item a.active.dropdown-item{color:inherit}.elements-plugin-container .nav.nav-tabs,.elements-plugin-container .nav.nav-tabs .dropdown-item{font-size:.8rem}.elements-plugin-container .nav.nav-tabs .nav-link{border:1px solid #d3d3d3;border-bottom:none;border-top-right-radius:.2rem;border-top-left-radius:.2rem}.elements-buttons-preview{display:flex;justify-content:space-between;width:100%;min-height:31vh;max-height:60vh;background-color:#f6f7f8}.elements-buttons-grid{display:flex;flex-wrap:wrap;justify-content:start;row-gap:12px;column-gap:12px;margin-bottom:0;width:100%;overflow-y:auto}.elements-hidden{display:none !important;max-height:0;padding:0}.elementst-dialog-button{display:flex;flex-direction:row;align-items:center;justify-content:space-between;position:relative;height:48px;max-height:100px;width:100%;max-width:100%;background-color:#fff;border:1px solid #e4e9ec;color:#535d76;box-shadow:0 0 6px 0 rgba(0,0,0,.1);overflow-wrap:anywhere;overflow:hidden;padding:0;margin-bottom:8px}.elementst-dialog-button.elements-custom-icon .elements-button-text i{position:absolute;background-repeat:no-repeat;background-size:contain;background-position:center;min-height:23px;min-width:49px;top:auto;left:0;border-right:1px solid #dce3f0}.elementst-dialog-button .elements-button-text{display:flex;align-items:center;height:100%}.elementst-dialog-button .elements-button-text::before{position:absolute;left:0;height:100%;min-height:23px;display:flex;justify-content:center;align-items:center;border-right:1px solid #dce3f0;width:60px;height:60px;top:3px;align-content:center}.elementst-dialog-button:hover,.elementst-dialog-button:active{box-shadow:inset 0 0 3px rgba(22,121,249,.3)}.elementst-dialog-button .elements-button-text{position:relative;font-size:12px;font-weight:500;width:100%;text-align:left;padding-left:62px}.elements-code-preview{display:none;overflow:hidden;background-color:#fff;border-top:2px solid #f6f7f8}.elements-code-preview .elements-component-code{-webkit-transform:scale(0.6);-ms-transform:scale(0.6);transform:scale(0.6);width:160%;margin-top:35px;position:absolute}.elementst-dialog-button .elements-button-variants{display:grid;grid-auto-flow:column;grid-gap:3px;justify-content:end;width:100%;height:42px;margin-right:3px}.elementst-dialog-button .elements-button-variants .elements-button-variant{display:flex;justify-content:center;align-items:center;background-color:#ecf3ff;color:#1679f9;height:42px;width:36px}.elementst-dialog-button .elements-button-variants .elements-button-variant::before{content:" ";background-size:50%;background-repeat:no-repeat;background-position:center;height:42px;width:36px}.elementst-dialog-button .elements-button-variants .elements-button-variant.on,.elementst-dialog-button .elements-button-variants .elements-button-variant:hover{background-color:#1679f9;color:#fff}.elementst-dialog-button .elements-button-variants .elements-button-variant.on::before,.elementst-dialog-button .elements-button-variants .elements-button-variant:hover::before{filter:brightness(10)}body.mce-content-body .collapse:not(.show),.elements-code-preview .collapse:not(.show){display:block !important}@media only screen and (min-width: 576px){.elements-modal.modal-open .modal-dialog,.elements-modal-no-preview.modal-open .modal-dialog{min-width:550px}.elements-select-filters{display:none}.elements-buttons-filters,.elements-buttons-flavors{display:flex;justify-content:center;align-items:center;margin:10px 0 10px}}@media only screen and (min-width: 992px){.elements-modal-no-preview.modal-open .modal-dialog{max-width:550px}.elements-buttons-preview.elements-no-preview{justify-content:center}.elements-buttons-preview.elements-no-preview .elements-buttons-grid{grid-gap:9px;justify-content:center;width:405px}.elements-buttons-preview.elements-no-preview .elements-code-preview{display:none}.elements-buttons-grid{width:440px;padding:10px}.elementst-dialog-button.elements-custom-icon .elements-button-text i{top:20px;left:0;right:0;margin:0 auto;max-width:25px;min-width:25px;width:100%;border-right:none}.elementst-dialog-button{height:100px;width:116px;max-width:116px;box-shadow:none;border-radius:4px;margin-bottom:0}.elementst-dialog-button::before{content:""}.elementst-dialog-button .elements-button-text::before{left:0;right:0;margin:0 auto;border-right:none;display:inline-block;width:60px;height:60px;top:3px}.elementst-dialog-button .elements-button-text{padding:70px 8px 15px 8px;font-size:12px;font-weight:500;line-height:13px;width:80px;text-align:center;justify-content:center}.elementst-dialog-button .elements-button-variants{grid-auto-flow:row;grid-gap:0;align-content:flex-start;justify-content:start;width:36px;height:99px;background-color:#ecf3ff;margin:0}.elementst-dialog-button .elements-button-variants .elements-button-variant{display:flex;justify-content:center;align-items:center;color:#1679f9;height:33px;width:36px}.elementst-dialog-button:hover,.elementst-dialog-button:active{box-shadow:0 0 0 3px rgba(22,121,249,.3)}.elementst-dialog-button .elements-button-variants .elements-button-variant:first-child,.elementst-dialog-button .elements-button-variants .elements-button-variant:nth-child(2){border-bottom:1px solid #fff}.elementst-dialog-button:hover .elements-button-variants .elements-button-variant{box-shadow:none}.elements-code-preview{position:relative;display:flex;flex-direction:column;width:377px;align-items:center;justify-content:center}.elements-preview-default{border:1px solid #e1e5ee;border-radius:8px;color:#9297a1;padding:23px 10px;font-weight:400;font-size:12px;line-height:16px;text-align:center;width:60%;margin:0 auto;-webkit-user-select:none;-ms-user-select:none;user-select:none}.elements-text-preview{position:absolute;top:10px;right:10px;font-weight:600;font-size:9.5px;line-height:11px;letter-spacing:.06em;color:#fff;background-color:#535d76;border-radius:6px;padding:5px;text-transform:uppercase}}
+#page-lib-editor-tiny-plugins-elements-preview #page{padding-top:0;margin-top:0}#page-lib-editor-tiny-plugins-elements-management .col.item,#page-lib-editor-tiny-plugins-elements-printurls .col.item{min-width:250px;max-width:250px}#page-lib-editor-tiny-plugins-elements-management .col.item.addcontainer .card,#page-lib-editor-tiny-plugins-elements-printurls .col.item.addcontainer .card{background-color:#d3d3d3}#page-lib-editor-tiny-plugins-elements-management .tiny_elements_thumbnail,#page-lib-editor-tiny-plugins-elements-printurls .tiny_elements_thumbnail{width:48px;height:48px}#page-lib-editor-tiny-plugins-elements-management .preview.management,#page-lib-editor-tiny-plugins-elements-printurls .preview.management{display:flex;justify-content:space-evenly;width:100%;height:3rem;padding-bottom:.25rem}#page-lib-editor-tiny-plugins-elements-management .preview.management>div,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div{width:45%}#page-lib-editor-tiny-plugins-elements-management .preview.management>div .elements-button-text,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div .elements-button-text{height:100%}#page-lib-editor-tiny-plugins-elements-management .preview.management>div .elements-button-text::before,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div .elements-button-text::before{display:flex;width:unset;height:100%}#page-lib-editor-tiny-plugins-elements-management .preview.management>div.more,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div.more{display:flex;flex-direction:column;justify-content:center;width:10%}#page-lib-editor-tiny-plugins-elements-management .preview.management>div.more .elements-button-text::before,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div.more .elements-button-text::before{all:unset;content:"..."}#page-lib-editor-tiny-plugins-elements-management .preview.management>div.more .elements-button-text,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div.more .elements-button-text{word-wrap:unset}#page-lib-editor-tiny-plugins-elements-management .compcat:hover .card,#page-lib-editor-tiny-plugins-elements-printurls .compcat:hover .card{border:1px solid #787878}#page-lib-editor-tiny-plugins-elements-management .compcat.active .card,#page-lib-editor-tiny-plugins-elements-printurls .compcat.active .card{border:1px solid #0b5190}#page-lib-editor-tiny-plugins-elements-management .card-body,#page-lib-editor-tiny-plugins-elements-printurls .card-body{display:flex;flex-direction:column;padding-top:.75rem}#page-lib-editor-tiny-plugins-elements-management .card-body>div:hover,#page-lib-editor-tiny-plugins-elements-printurls .card-body>div:hover{cursor:pointer}#page-lib-editor-tiny-plugins-elements-management .card-body>div:hover+a.edit,#page-lib-editor-tiny-plugins-elements-printurls .card-body>div:hover+a.edit{color:#094478}#page-lib-editor-tiny-plugins-elements-management form[data-formtype=tiny_elements_comp_flavor] input.form-control,#page-lib-editor-tiny-plugins-elements-printurls form[data-formtype=tiny_elements_comp_flavor] input.form-control{width:100%}#page-lib-editor-tiny-plugins-elements-management .compcat[data-compcat=found-items] a,#page-lib-editor-tiny-plugins-elements-printurls .compcat[data-compcat=found-items] a{display:none}#page-lib-editor-tiny-plugins-elements-management form[data-formtype=tiny_elements_editlicense] div[data-fieldtype=static],#page-lib-editor-tiny-plugins-elements-printurls form[data-formtype=tiny_elements_editlicense] div[data-fieldtype=static]{padding-top:calc(.75rem + 1px)}.elements-plugin-container{--elements-orange: #d33f01;--elements-orange-dark: #a93201;--elements-orange-bright: #ee7430;--elements-orange-soft: #fce3d6;--elements-orange-soft-2: #f9c7ac;--elements-blue-1: #25263e;--elements-blue-2: #194d83;--elements-blue-3: #00aada;--elements-surface: #ececef;--elements-surface-2: #e0e0e4;--elements-border: #d4d4d9;--elements-gray-400: #a4a5ac;--elements-gray-600: #60616d;--elements-gray-900: #00091e;--elements-radius: 0.375rem;--elements-radius-md: 0.5rem;--elements-shadow-card: 0 1px 2px rgba(0, 9, 30, 0.06), 0 1px 3px rgba(0, 9, 30, 0.08);--elements-shadow-card-hover: 0 4px 10px rgba(0, 9, 30, 0.10), 0 2px 4px rgba(0, 9, 30, 0.06);display:flex;flex-direction:column;min-height:60vh;max-height:80vh;background-color:var(--elements-surface);color:var(--elements-gray-900)}.elements-modal-grid{display:grid;grid-template-columns:240px minmax(0, 1fr);grid-template-rows:minmax(0, 1fr);min-height:0;flex:1}.elements-modal-grid:not(.elements-no-preview){grid-template-columns:240px minmax(0, 1fr) 340px}.elements-rail{background:var(--elements-surface);border-right:1px solid var(--elements-border);overflow-y:auto;padding:12px 0;min-width:0}.elements-rail-list{display:flex;flex-direction:column;gap:2px}.elements-rail-group{display:flex;flex-direction:column}.elements-rail-item{display:flex;align-items:center;padding:10px 14px;border:none;border-radius:0;background:rgba(0,0,0,0);color:var(--elements-gray-900);text-align:left;cursor:pointer;transition:background 80ms}.elements-rail-item:hover{background:#fff}.elements-rail-item.active{background:#fff}.elements-rail-meta{display:flex;flex-direction:column;min-width:0}.elements-rail-name{font-size:.92rem;font-weight:600;line-height:1.2;overflow-wrap:anywhere}.elements-rail-count{font-size:.75rem;color:var(--elements-gray-600)}.elements-flavor-list{display:flex;flex-direction:column;gap:1px;margin:2px 0 4px;padding:2px 0 2px 12px;background:#fff}.elements-flavor-list[hidden]{display:none}.elements-main{display:flex;flex-direction:column;min-width:0;overflow:hidden;background:#fff}.elements-bar-label{font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--elements-gray-400)}.elements-grid-wrap{flex:1;overflow-y:auto;overflow-x:hidden;padding:18px 22px 22px;min-width:0;background:#fff}.elements-buttons-grid{display:grid;grid-template-columns:repeat(auto-fill, minmax(120px, 1fr));gap:10px;min-width:0}.elementst-dialog-button{position:relative;display:flex;flex-direction:column;gap:8px;padding:10px 12px 12px;border:1px solid var(--elements-border);border-radius:var(--elements-radius-md);background:#fff;cursor:pointer;text-align:left;color:var(--elements-gray-900);min-height:120px;transition:border-color 100ms,box-shadow 100ms,transform 100ms}.elementst-dialog-button:hover{border-color:var(--elements-orange);box-shadow:var(--elements-shadow-card-hover);transform:translateY(-1px)}.elementst-dialog-button.selected{border-color:var(--elements-orange);outline:2px solid var(--elements-orange-soft);outline-offset:-1px;box-shadow:var(--elements-shadow-card-hover)}.elements-comp-icon{position:relative;width:80px;height:80px;background:rgba(0,0,0,0)}.elements-comp-icon::before{position:absolute;inset:0;background-size:contain;background-position:center;background-repeat:no-repeat}.elements-comp-icon .icon{display:block;width:100%;height:100%;background-size:contain;background-position:center;background-repeat:no-repeat;margin:0}.elements-comp-name{font-size:.88rem;font-weight:600;line-height:1.25;overflow-wrap:anywhere}.elements-v-dots{display:flex;gap:6px;align-items:center;justify-content:flex-end;min-height:15px}.elements-v-dot{width:15px;height:15px;padding:0;border-radius:25%;background:var(--elements-surface-2);border:1px solid var(--elements-border);cursor:pointer;transition:background 80ms,border-color 80ms,transform 80ms}.elements-v-dot:hover{border-color:var(--elements-orange);transform:scale(1.15)}.elements-v-dot:focus,.elements-v-dot:focus-visible{outline:none;box-shadow:none;border-color:var(--elements-orange)}.elements-v-dot.on{background:var(--elements-orange);border-color:var(--elements-orange)}.elements-hidden{display:none !important}.elements-preview{background:var(--elements-surface);border-left:1px solid var(--elements-border);display:flex;flex-direction:column;overflow:hidden}.elements-preview-head{padding:14px 20px;border-bottom:1px solid var(--elements-border)}.elements-preview-body{flex:1;padding:18px 20px;overflow-y:auto;background:#fff;border:1px solid var(--elements-border);border-radius:var(--elements-radius-md);margin:14px 16px;position:relative}.elements-preview-default{border:1px dashed var(--elements-border);border-radius:var(--elements-radius-md);padding:22px 14px;color:var(--elements-gray-600);font-size:.85rem;line-height:1.5;text-align:center;user-select:none}.elements-component-code{transform:scale(0.7);transform-origin:top left;width:142.8571428571%}.elements-preview-footer{padding:14px 20px;border-top:1px solid var(--elements-border);background:var(--elements-surface);display:flex;flex-direction:column;gap:10px}.elements-preview-summary{font-size:.78rem;color:var(--elements-gray-600);line-height:1.4;min-height:1.4em}.elements-insert-btn{width:100%}.elements-insert-btn[disabled]{opacity:.5;cursor:not-allowed}.elements-footer-inline{grid-column:1/-1;border-top:1px solid var(--elements-border);background:var(--elements-surface)}.elements-variant-strip{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:10px 22px;background:var(--elements-orange-soft);border-top:1px solid var(--elements-orange-soft-2)}.elements-variant-strip[hidden]{display:none}.elements-variant-strip .elements-bar-label{color:var(--elements-orange-dark)}.elements-variant-target{font-size:.78rem;font-weight:600;color:var(--elements-orange-dark)}.elements-variant-chips{display:flex;flex-wrap:wrap;gap:6px}.elements-variant-chip{display:inline-flex;align-items:center;gap:6px;padding:5px 10px;border:1px solid var(--elements-orange-soft-2);border-radius:999px;background:hsla(0,0%,100%,.7);color:var(--elements-orange-dark);font-size:.82rem;font-weight:500;cursor:pointer;transition:all 80ms}.elements-variant-chip:hover{background:#fff}.elements-variant-chip.active{background:var(--elements-orange);border-color:var(--elements-orange);color:#fff}.elements-flavor-chip{display:flex;align-items:center;gap:8px;padding:6px 10px;border:none;border-radius:var(--elements-radius);background:rgba(0,0,0,0);color:var(--elements-gray-600);font-size:.82rem;font-weight:500;cursor:pointer;text-align:left;transition:background 80ms,color 80ms}.elements-flavor-chip:hover{background:hsla(0,0%,100%,.6);color:var(--elements-gray-900)}.elements-flavor-chip.active{background:#fff;color:var(--elements-orange);font-weight:600}.elements-flavor-chip.elements-flavor-none{font-style:italic}.elements-flavor-chip.elements-flavor-none.active{color:var(--elements-gray-900);font-style:normal}body.mce-content-body .collapse:not(.show),.elements-code-preview .collapse:not(.show){display:block !important}.elements-modal .modal-body,.elements-modal-no-preview .modal-body{padding:0}.elements-flavor-chip,.elements-flavor-chip:focus,.elements-flavor-chip:active,.elements-rail-item,.elements-rail-item:focus,.elements-rail-item:active{box-shadow:none}@media(min-width: 576px){.elements-modal.modal-open .modal-dialog,.elements-modal-no-preview.modal-open .modal-dialog{min-width:720px}}@media(min-width: 992px){.elements-modal.modal-open .modal-dialog{max-width:1320px}.elements-modal-no-preview.modal-open .modal-dialog{max-width:900px}}@media(max-width: 991px){.elements-modal-grid,.elements-modal-grid:not(.elements-no-preview){grid-template-columns:220px minmax(0, 1fr)}.elements-preview{display:none}.elements-main{position:relative}.elements-footer-inline{position:sticky;bottom:0;z-index:2}}@media(max-width: 575px){.elements-plugin-container{max-height:90vh}.elements-modal-grid,.elements-modal-grid:not(.elements-no-preview){grid-template-columns:minmax(0, 1fr);grid-template-rows:auto minmax(0, 1fr)}.elements-rail{border-right:none;border-bottom:1px solid var(--elements-border);max-height:90px;overflow-x:auto;overflow-y:hidden;padding:8px}.elements-rail-list{flex-direction:row;gap:6px}.elements-rail-group{flex-direction:row}.elements-rail-item{flex-direction:column;gap:4px;padding:6px 10px;min-width:84px}.elements-rail-item.active::before{top:auto;bottom:0;left:12px;right:12px;width:auto;height:3px;border-radius:3px 3px 0 0}.elements-rail-meta{align-items:center}.elements-rail-name{font-size:.75rem;text-align:center}.elements-rail-count{display:none}.elements-flavor-list{display:none}.elements-variant-strip{padding:8px 12px}.elements-grid-wrap{padding:12px}.elements-buttons-grid{grid-template-columns:repeat(auto-fill, minmax(100px, 1fr))}}
diff --git a/templates/modal.mustache b/templates/modal.mustache
index 7f6d8ab..4a9d867 100644
--- a/templates/modal.mustache
+++ b/templates/modal.mustache
@@ -19,21 +19,19 @@
Modal to manage components within the Tiny Editor.
- Classes required for JS:
- * none
-
- Data attributes required for JS:
- * none
-
- Context variables required for this template:
+ Three-axis layout: category rail (left), flavor chips + variant strip + component grid (main),
+ preview pane with explicit insert button (right). All interactivity is wired by amd/src/ui.js.
Example context (json):
{
+ "elementid": "elements-plugin-container",
+ "preview": true,
+ "canmanage": true,
"buttons": [
{
"id": 1,
- "name": "tip",
- "type": 4,
+ "name": "Tip",
+ "type": "teachpals",
"imageClass": "elements-teachpals-tip-icon",
"htmlcode": "",
"variants": [
@@ -44,61 +42,31 @@
"state": "off",
"imageClass": "avatar2-variant-off",
"variantclass": "elements-avatar2-variant",
- "content": ""
- }
- ],
- "flavorlist": "robi-bot,horst-cloud",
- "category": 4
- },
- {
- "id": 2,
- "name": "scenario",
- "type": 4,
- "imageClass": "elements-teachpals-scenario-icon",
- "htmlcode": "",
- "variants": [
- {
- "id": 8,
- "displayname": "Avatar 2",
- "name": "avatar2",
- "state": "off",
- "imageClass": "avatar2-variant-off",
- "variantclass": "elements-avatar2-variant",
- "content": ""
+ "title": "Avatar 2"
}
],
"flavorlist": "robi-bot,horst-cloud",
- "category": 4
+ "category": "teachpals"
}
],
"categories": [
{
"categoryid": 4,
"name": "Teach Pals",
+ "categoryname": "teachpals",
"type": 4,
"displayorder": 1,
+ "componentcount": 2,
+ "initial": "T",
"flavors": [
- {
- "id": 1,
- "name": "horst-cloud",
- "displayname": "Horst und Woelkchen",
- "factive": "active"
- },
- {
- "id": 12,
- "name": "robi-bot",
- "displayname": "Robi und Bot"
- }
+ {"id": 1, "name": "horst-cloud", "displayname": "Horst und Wölkchen"},
+ {"id": 12, "name": "robi-bot", "displayname": "Robi und Bot"}
],
"hasFlavors": 2,
"active": "active"
}
- ],
- "preview": true,
- "elementid": "elements-plugin-container",
- "canmanage": true
+ ]
}
-
}}
{{< core/modal }}
{{$header}}
@@ -110,77 +78,125 @@
{{/canmanage}}
{{/header}}
{{$body}}
-
-
- {{#categories}}
- {{#hasFlavors}}
- -
- {{name}}
-