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":"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"} \ 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 <<