diff --git a/amd/build/commands.min.js b/amd/build/commands.min.js index e0eea0f..c6e3bef 100644 --- a/amd/build/commands.min.js +++ b/amd/build/commands.min.js @@ -1,3 +1,3 @@ -define("tiny_multilang2/commands",["exports","./options","./common","core/str","./ui"],(function(_exports,_options,_common,_str,_ui){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getSetup=void 0;_exports.getSetup=async()=>{const[buttonText,tooltip,removeTag]=await(0,_str.get_strings)(["multilang2:language","multilang2:desc","multilang2:removetag"].map((key=>({key:key,component:_common.component}))));return editor=>{const languageList=(0,_options.getLanguageList)(editor);if(!(languageList.length<2)){if(editor.ui.registry.addSplitButton(_common.component,{icon:"language",tooltip:tooltip,fetch:function(callback){callback(languageList.map((lang=>({type:"choiceitem",value:lang.iso,text:lang.label}))))},onAction:()=>{(0,_ui.applyLanguage)(editor,null)},onItemAction:(_splitButtonApi,value)=>{(0,_ui.applyLanguage)(editor,value)}}),editor.ui.registry.addNestedMenuItem(_common.component,{icon:"language",text:buttonText,getSubmenuItems:()=>languageList.map((lang=>({type:"menuitem",text:lang.label,onAction:()=>{(0,_ui.applyLanguage)(editor,lang.iso)}})))}),!(0,_options.showAllLanguages)(editor)||(0,_options.isAddLanguage)(editor)){for(const lang of languageList)editor.ui.registry.addButton(_common.component+"_remove",{icon:"remove",tooltip:removeTag,onAction:()=>{(0,_ui.onDelete)(editor,event)}}),"remove"!==lang.iso&&editor.ui.registry.addButton(_common.component+"_"+lang.iso,{text:lang.iso,tooltip:lang.label,onAction:()=>{(0,_ui.applyLanguage)(editor,lang.iso,event)}});editor.ui.registry.addContextToolbar(_common.component,{predicate:function(node){return node.classList.contains("multilang-begin")||node.classList.contains("multilang-end")},items:languageList.map((lang=>_common.component+"_"+lang.iso)).join(" "),position:"node",scope:"node"})}editor.on("init",(()=>{(0,_ui.onInit)(editor)})),editor.on("BeforeGetContent",(format=>{(0,_ui.onBeforeGetContent)(editor,format)})),editor.on("submit",(()=>{(0,_ui.onSubmit)(editor)})),editor.on("keydown",(event=>{(0,_ui.onDelete)(editor,event)}))}}}})); +define("tiny_multilang2/commands",["exports","./options","./common","core/str","./ui"],(function(_exports,_options,_common,_str,_ui){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getSetup=void 0;_exports.getSetup=async()=>{const[buttonText,tooltip,removeTag]=await(0,_str.get_strings)(["multilang2:language","multilang2:desc","multilang2:removetag"].map((key=>({key:key,component:_common.component}))));return editor=>{const languageList=(0,_options.getLanguageList)(editor);if(!(languageList.length<2)){if(editor.ui.registry.addSplitButton(_common.component,{icon:"language",tooltip:tooltip,fetch:function(callback){callback(languageList.map((lang=>({type:"choiceitem",value:lang.iso,text:lang.label}))))},onAction:()=>{(0,_ui.applyLanguage)(editor,null)},onItemAction:(_splitButtonApi,value)=>{(0,_ui.applyLanguage)(editor,value)}}),editor.ui.registry.addNestedMenuItem(_common.component,{icon:"language",text:buttonText,getSubmenuItems:()=>languageList.map((lang=>({type:"menuitem",text:lang.label,onAction:()=>{(0,_ui.applyLanguage)(editor,lang.iso)}})))}),!(0,_options.showAllLanguages)(editor)||(0,_options.isAddLanguage)(editor)){for(const lang of languageList)editor.ui.registry.addButton(_common.component+"_remove",{icon:"remove",tooltip:removeTag,onAction:()=>{(0,_ui.onDelete)(editor,event)}}),"remove"!==lang.iso&&editor.ui.registry.addButton(_common.component+"_"+lang.iso,{text:lang.iso,tooltip:lang.label,onAction:()=>{(0,_ui.applyLanguage)(editor,lang.iso,event)}});editor.ui.registry.addContextToolbar(_common.component,{predicate:function(node){return node.classList.contains("multilang-begin")||node.classList.contains("multilang-end")},items:languageList.map((lang=>_common.component+"_"+lang.iso)).join(" "),position:"node",scope:"node"})}editor.on("init",(()=>{(0,_ui.onInit)(editor)})),editor.on("BeforeGetContent",(format=>{(0,_ui.onBeforeGetContent)(editor,format)})),editor.on("focus",(()=>{(0,_ui.onSetContent)(editor)})),editor.on("SetContent",(()=>{(0,_ui.onSetContent)(editor)})),editor.on("submit",(()=>{(0,_ui.onSubmit)(editor)})),editor.on("keydown",(event=>{(0,_ui.onDelete)(editor,event)}))}}}})); //# sourceMappingURL=commands.min.js.map \ No newline at end of file diff --git a/amd/build/commands.min.js.map b/amd/build/commands.min.js.map index d1d2f1a..2ec883e 100644 --- a/amd/build/commands.min.js.map +++ b/amd/build/commands.min.js.map @@ -1 +1 @@ -{"version":3,"file":"commands.min.js","sources":["../src/commands.js"],"sourcesContent":["// This file is part of Moodle - https://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 * Commands helper for the Moodle tiny_multilang2 plugin.\n *\n * @module tiny_multilang2\n * @author Iñaki Arenaza \n * @author Stephan Robotta \n * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getLanguageList, showAllLanguages, isAddLanguage} from './options';\nimport {component} from './common';\nimport {get_strings as getStrings} from 'core/str';\nimport {applyLanguage, onInit, onBeforeGetContent, onSubmit, onDelete} from './ui';\n\n/**\n * Get the setup function for the button and the menu entry.\n *\n * This is performed in an async function which ultimately returns the registration function as the\n * Tiny.AddOnManager.Add() function does not support async functions.\n *\n * @returns {function} The registration function to call within the Plugin.add function.\n */\nexport const getSetup = async() => {\n const [\n buttonText,\n tooltip,\n removeTag,\n ] = await getStrings(['multilang2:language', 'multilang2:desc', 'multilang2:removetag'].map((key) => ({key, component})));\n\n return (editor) => {\n const languageList = getLanguageList(editor);\n\n // If there is just one language, we don't need the plugin.\n if (languageList.length < 2) {\n return;\n }\n editor.ui.registry.addSplitButton(component, {\n icon: 'language',\n tooltip: tooltip,\n fetch: function(callback) {\n const items = languageList.map((lang) => ({\n type: 'choiceitem',\n value: lang.iso,\n text: lang.label,\n }));\n callback(items);\n },\n onAction: () => {\n applyLanguage(editor, null);\n },\n onItemAction: (_splitButtonApi, value) => {\n applyLanguage(editor, value);\n }\n });\n\n editor.ui.registry.addNestedMenuItem(component, {\n icon: 'language',\n text: buttonText,\n getSubmenuItems: () => languageList.map((lang) => ({\n type: 'menuitem',\n text: lang.label,\n onAction: () => {\n applyLanguage(editor, lang.iso);\n },\n }))\n });\n\n // Context menu with languages is shown only when showalllangs is set to false. Otherwise the\n // List would be overwhelming.\n if (!showAllLanguages(editor) || isAddLanguage(editor)) {\n for (const lang of languageList) {\n editor.ui.registry.addButton(component + '_remove', {\n icon: 'remove',\n tooltip: removeTag,\n onAction: () => {\n onDelete(editor, event);\n }\n });\n if (lang.iso !== 'remove') {\n editor.ui.registry.addButton(component + '_' + lang.iso, {\n text: lang.iso,\n tooltip: lang.label,\n onAction: () => {\n applyLanguage(editor, lang.iso, event);\n }\n });\n }\n }\n editor.ui.registry.addContextToolbar(component, {\n predicate: function(node) {\n return node.classList.contains('multilang-begin') || node.classList.contains('multilang-end');\n },\n items: languageList.map((lang) => (component + '_' + lang.iso)).join(' '),\n position: 'node',\n scope: 'node'\n });\n }\n\n editor.on('init', () => {\n onInit(editor);\n });\n editor.on('BeforeGetContent', (format) => {\n onBeforeGetContent(editor, format);\n });\n editor.on('submit', () => {\n onSubmit(editor);\n });\n editor.on('keydown', (event) => {\n onDelete(editor, event);\n });\n\n };\n};\n"],"names":["async","buttonText","tooltip","removeTag","map","key","component","editor","languageList","length","ui","registry","addSplitButton","icon","fetch","callback","lang","type","value","iso","text","label","onAction","onItemAction","_splitButtonApi","addNestedMenuItem","getSubmenuItems","addButton","event","addContextToolbar","predicate","node","classList","contains","items","join","position","scope","on","format"],"mappings":"wOAsCwBA,gBAEhBC,WACAC,QACAC,iBACM,oBAAW,CAAC,sBAAuB,kBAAmB,wBAAwBC,KAAKC,OAAUA,IAAAA,IAAKC,UAAAA,8BAEpGC,eACEC,cAAe,4BAAgBD,aAGjCC,aAAaC,OAAS,OAG1BF,OAAOG,GAAGC,SAASC,eAAeN,kBAAW,CACzCO,KAAM,WACNX,QAASA,QACTY,MAAO,SAASC,UAMZA,SALcP,aAAaJ,KAAKY,QAC5BC,KAAM,aACNC,MAAOF,KAAKG,IACZC,KAAMJ,KAAKK,YAInBC,SAAU,2BACQf,OAAQ,OAE1BgB,aAAc,CAACC,gBAAiBN,+BACdX,OAAQW,UAI9BX,OAAOG,GAAGC,SAASc,kBAAkBnB,kBAAW,CAC5CO,KAAM,WACNO,KAAMnB,WACNyB,gBAAiB,IAAMlB,aAAaJ,KAAKY,QACrCC,KAAM,WACNG,KAAMJ,KAAKK,MACXC,SAAU,2BACQf,OAAQS,KAAKG,cAOlC,6BAAiBZ,UAAW,0BAAcA,QAAS,KAC/C,MAAMS,QAAQR,aACfD,OAAOG,GAAGC,SAASgB,UAAUrB,kBAAY,UAAW,CAChDO,KAAM,SACNX,QAASC,UACTmB,SAAU,sBACGf,OAAQqB,UAGR,WAAbZ,KAAKG,KACLZ,OAAOG,GAAGC,SAASgB,UAAUrB,kBAAY,IAAMU,KAAKG,IAAK,CACrDC,KAAMJ,KAAKG,IACXjB,QAASc,KAAKK,MACdC,SAAU,2BACQf,OAAQS,KAAKG,IAAKS,UAKhDrB,OAAOG,GAAGC,SAASkB,kBAAkBvB,kBAAW,CAC5CwB,UAAW,SAASC,aACTA,KAAKC,UAAUC,SAAS,oBAAsBF,KAAKC,UAAUC,SAAS,kBAEjFC,MAAO1B,aAAaJ,KAAKY,MAAUV,kBAAY,IAAMU,KAAKG,MAAMgB,KAAK,KACrEC,SAAU,OACVC,MAAO,SAIf9B,OAAO+B,GAAG,QAAQ,oBACP/B,WAEXA,OAAO+B,GAAG,oBAAqBC,oCACRhC,OAAQgC,WAE/BhC,OAAO+B,GAAG,UAAU,sBACP/B,WAEbA,OAAO+B,GAAG,WAAYV,yBACTrB,OAAQqB"} \ No newline at end of file +{"version":3,"file":"commands.min.js","sources":["../src/commands.js"],"sourcesContent":["// This file is part of Moodle - https://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 * Commands helper for the Moodle tiny_multilang2 plugin.\n *\n * @module tiny_multilang2\n * @author Iñaki Arenaza \n * @author Stephan Robotta \n * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getLanguageList, showAllLanguages, isAddLanguage} from './options';\nimport {component} from './common';\nimport {get_strings as getStrings} from 'core/str';\nimport {applyLanguage, onInit, onBeforeGetContent, onSetContent, onSubmit, onDelete} from './ui';\n\n/**\n * Get the setup function for the button and the menu entry.\n *\n * This is performed in an async function which ultimately returns the registration function as the\n * Tiny.AddOnManager.Add() function does not support async functions.\n *\n * @returns {function} The registration function to call within the Plugin.add function.\n */\nexport const getSetup = async() => {\n const [\n buttonText,\n tooltip,\n removeTag,\n ] = await getStrings(['multilang2:language', 'multilang2:desc', 'multilang2:removetag'].map((key) => ({key, component})));\n\n return (editor) => {\n const languageList = getLanguageList(editor);\n\n // If there is just one language, we don't need the plugin.\n if (languageList.length < 2) {\n return;\n }\n editor.ui.registry.addSplitButton(component, {\n icon: 'language',\n tooltip: tooltip,\n fetch: function(callback) {\n const items = languageList.map((lang) => ({\n type: 'choiceitem',\n value: lang.iso,\n text: lang.label,\n }));\n callback(items);\n },\n onAction: () => {\n applyLanguage(editor, null);\n },\n onItemAction: (_splitButtonApi, value) => {\n applyLanguage(editor, value);\n }\n });\n\n editor.ui.registry.addNestedMenuItem(component, {\n icon: 'language',\n text: buttonText,\n getSubmenuItems: () => languageList.map((lang) => ({\n type: 'menuitem',\n text: lang.label,\n onAction: () => {\n applyLanguage(editor, lang.iso);\n },\n }))\n });\n\n // Context menu with languages is shown only when showalllangs is set to false. Otherwise the\n // List would be overwhelming.\n if (!showAllLanguages(editor) || isAddLanguage(editor)) {\n for (const lang of languageList) {\n editor.ui.registry.addButton(component + '_remove', {\n icon: 'remove',\n tooltip: removeTag,\n onAction: () => {\n onDelete(editor, event);\n }\n });\n if (lang.iso !== 'remove') {\n editor.ui.registry.addButton(component + '_' + lang.iso, {\n text: lang.iso,\n tooltip: lang.label,\n onAction: () => {\n applyLanguage(editor, lang.iso, event);\n }\n });\n }\n }\n editor.ui.registry.addContextToolbar(component, {\n predicate: function(node) {\n return node.classList.contains('multilang-begin') || node.classList.contains('multilang-end');\n },\n items: languageList.map((lang) => (component + '_' + lang.iso)).join(' '),\n position: 'node',\n scope: 'node'\n });\n }\n\n editor.on('init', () => {\n onInit(editor);\n });\n editor.on('BeforeGetContent', (format) => {\n onBeforeGetContent(editor, format);\n });\n editor.on('focus', () => {\n onSetContent(editor);\n });\n editor.on('SetContent', () => {\n onSetContent(editor);\n });\n editor.on('submit', () => {\n onSubmit(editor);\n });\n editor.on('keydown', (event) => {\n onDelete(editor, event);\n });\n\n };\n};\n"],"names":["async","buttonText","tooltip","removeTag","map","key","component","editor","languageList","length","ui","registry","addSplitButton","icon","fetch","callback","lang","type","value","iso","text","label","onAction","onItemAction","_splitButtonApi","addNestedMenuItem","getSubmenuItems","addButton","event","addContextToolbar","predicate","node","classList","contains","items","join","position","scope","on","format"],"mappings":"wOAsCwBA,gBAEhBC,WACAC,QACAC,iBACM,oBAAW,CAAC,sBAAuB,kBAAmB,wBAAwBC,KAAKC,OAAUA,IAAAA,IAAKC,UAAAA,8BAEpGC,eACEC,cAAe,4BAAgBD,aAGjCC,aAAaC,OAAS,OAG1BF,OAAOG,GAAGC,SAASC,eAAeN,kBAAW,CACzCO,KAAM,WACNX,QAASA,QACTY,MAAO,SAASC,UAMZA,SALcP,aAAaJ,KAAKY,QAC5BC,KAAM,aACNC,MAAOF,KAAKG,IACZC,KAAMJ,KAAKK,YAInBC,SAAU,2BACQf,OAAQ,OAE1BgB,aAAc,CAACC,gBAAiBN,+BACdX,OAAQW,UAI9BX,OAAOG,GAAGC,SAASc,kBAAkBnB,kBAAW,CAC5CO,KAAM,WACNO,KAAMnB,WACNyB,gBAAiB,IAAMlB,aAAaJ,KAAKY,QACrCC,KAAM,WACNG,KAAMJ,KAAKK,MACXC,SAAU,2BACQf,OAAQS,KAAKG,cAOlC,6BAAiBZ,UAAW,0BAAcA,QAAS,KAC/C,MAAMS,QAAQR,aACfD,OAAOG,GAAGC,SAASgB,UAAUrB,kBAAY,UAAW,CAChDO,KAAM,SACNX,QAASC,UACTmB,SAAU,sBACGf,OAAQqB,UAGR,WAAbZ,KAAKG,KACLZ,OAAOG,GAAGC,SAASgB,UAAUrB,kBAAY,IAAMU,KAAKG,IAAK,CACrDC,KAAMJ,KAAKG,IACXjB,QAASc,KAAKK,MACdC,SAAU,2BACQf,OAAQS,KAAKG,IAAKS,UAKhDrB,OAAOG,GAAGC,SAASkB,kBAAkBvB,kBAAW,CAC5CwB,UAAW,SAASC,aACTA,KAAKC,UAAUC,SAAS,oBAAsBF,KAAKC,UAAUC,SAAS,kBAEjFC,MAAO1B,aAAaJ,KAAKY,MAAUV,kBAAY,IAAMU,KAAKG,MAAMgB,KAAK,KACrEC,SAAU,OACVC,MAAO,SAIf9B,OAAO+B,GAAG,QAAQ,oBACP/B,WAEXA,OAAO+B,GAAG,oBAAqBC,oCACRhC,OAAQgC,WAE/BhC,OAAO+B,GAAG,SAAS,0BACF/B,WAEjBA,OAAO+B,GAAG,cAAc,0BACP/B,WAEjBA,OAAO+B,GAAG,UAAU,sBACP/B,WAEbA,OAAO+B,GAAG,WAAYV,yBACTrB,OAAQqB"} \ No newline at end of file diff --git a/amd/build/ui.min.js b/amd/build/ui.min.js index 3e56f22..1bd5a7b 100644 --- a/amd/build/ui.min.js +++ b/amd/build/ui.min.js @@ -1,4 +1,4 @@ -define("tiny_multilang2/ui",["exports","./options"],(function(_exports,_options){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.onSubmit=_exports.onInit=_exports.onDelete=_exports.onBeforeGetContent=_exports.applyLanguage=void 0; +define("tiny_multilang2/ui",["exports","./options"],(function(_exports,_options){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.onSubmit=_exports.onSetContent=_exports.onInit=_exports.onDelete=_exports.onBeforeGetContent=_exports.applyLanguage=void 0; /** * Commands for the plugin logic of the Moodle tiny_multilang2 plugin. * @@ -9,6 +9,6 @@ define("tiny_multilang2/ui",["exports","./options"],(function(_exports,_options) * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -const spanClass="multilang-begin mceNonEditable",spanFixedAttrs='{mlang %lang}',spanMultilangEnd=spanFixedAttrs.replace("begin","end")+">{mlang}",isNull=a=>null==a,addVisualStyling=function(ed){let content=ed.getContent();if(-1!==content.indexOf(spanClass))return content;if(content=content.replace(new RegExp("{\\s*mlang\\s+([^}]+?)\\s*}","ig"),(function(match,p1){return spanMultilangBegin.replace(new RegExp("%lang","g"),p1)})),content=content.replace(new RegExp("{\\s*mlang\\s*}","ig"),spanMultilangEnd),(0,_options.mlangFilterExists)(ed)&&!(0,_options.isFallbackSpanTag)(ed))return content;const doc=(new DOMParser).parseFromString(content,"text/html");if(0===doc.children.length)return content;const nodes=doc.querySelectorAll("span.multilang");if(0===nodes.length)return content;for(const span of nodes){const newSpan=spanMultilangBegin.replace(new RegExp("%lang","g"),span.getAttribute("lang")).replace("mceNonEditable","mceNonEditable fallback")+span.innerHTML+spanMultilangEnd.replace("mceNonEditable","mceNonEditable fallback");span.insertAdjacentHTML("afterend",newSpan),span.remove()}return doc.getElementsByTagName("body")[0].innerHTML},removeVisualStyling=function(ed){["begin","end"].forEach((function(t){for(const span of ed.dom.select("span.multilang-"+t))if("begin"===t&&span.classList.contains("fallback")){let innerHTML="",end=span,toRemove=[];for(;end&&(end=end.nextSibling,!isNull(end));){if(!isNull(end.classList)&&end.classList.contains("multilang-end")){toRemove.push(end);break}3===end.nodeType?innerHTML+=end.nodeValue:1===end.nodeType&&(innerHTML+=end.outerHTML),toRemove.push(end)}if(!isNull(end)){const lang=span.innerHTML.match(new RegExp("{\\s*mlang\\s+([^}]+?)\\s*}","i")),rtlLanguages=(0,_options.getRTLLanguages)();if(lang){const langCode=lang[1],dir=rtlLanguages.includes(langCode)?"rtl":"ltr",newHTML=''+innerHTML+"";for(end of(ed.dom.setOuterHTML(span,newHTML),toRemove))ed.dom.remove(end)}}}else ed.dom.setOuterHTML(span,span.innerHTML.toLowerCase())}))},getHighlightNodeFromSelect=function(ed,search){let span;return ed.dom.getParents(ed.selection.getStart(),(elm=>{if(!isNull(elm.classList)){const pair="begin"===search?"end":"begin";if(elm.classList.contains("multilang-"+pair)){span=elm;do{span="begin"===search?span.previousSibling:span.nextSibling}while(!isNull(span)&&(isNull(span.classList)||!span.classList.contains("multilang-"+search)))}else elm.classList.contains("multilang-"+search)&&(span=elm)}})),span},hideContentToolbar=function(el){for(;!isNull(el);){if(el.nodeType===Node.ELEMENT_NODE&&!isNull(el.getAttribute("class"))&&-1!=el.getAttribute("class").indexOf("tox-pop-"))return void(el.style.display="none");el=el.parentNode}};_exports.onInit=function(ed){ed.setContent(addVisualStyling(ed)),(0,_options.isContentToHighlight)(ed)&&ed.dom.addStyle((0,_options.getHighlightCss)(ed))};_exports.onBeforeGetContent=function(ed,content){if(!isNull(content.source_view)&&!0===content.source_view){var onClose=function(ed){ed.off("close",onClose),ed.setContent(addVisualStyling(ed))};ed.on("CloseWindow",(()=>{onClose(ed)})),removeVisualStyling(ed)}};_exports.onSubmit=function(ed){removeVisualStyling(ed)};_exports.onDelete=function(ed,event){if(event.isComposing||isNull(event.clientX)&&46!==event.keyCode&&8!==event.keyCode)return;if(!isNull(event.clientX)&&(event.target.nodeType!==Node.ELEMENT_NODE||"path"!==event.target.nodeName&&"svg"!==event.target.nodeName))return;const begin=getHighlightNodeFromSelect(ed,"begin"),end=getHighlightNodeFromSelect(ed,"end");isNull(begin)||isNull(end)||(event.preventDefault(),ed.dom.remove(begin),ed.dom.remove(end),isNull(event.clientX)||hideContentToolbar(event.target))};_exports.applyLanguage=function(ed,iso,event){if(isNull(iso))return;if("remove"===iso){return void ed.contentDocument.body.querySelectorAll(".multilang-begin, .multilang-end").forEach((element=>{ed.dom.remove(element)}))}const regexLang=/%lang/g;let text=ed.selection.getContent();if(""===text.toString().replace(/^\s+/,"").replace(/\s+$/,"")){if(!isNull(event))return void hideContentToolbar(event.target);let newtext=spanMultilangBegin.replace(regexLang,iso)+" "+spanMultilangEnd;return(0,_options.mlangFilterExists)(ed)||(newtext=newtext.replaceAll("mceNonEditable","mceNonEditable fallback")),void ed.insertContent(newtext)}isNull(event)||hideContentToolbar(event.target);const span=getHighlightNodeFromSelect(ed,"begin");if(!isNull(span)){let replacement=spanMultilangBegin.replace(regexLang,iso);return span.classList.contains("fallback")&&(replacement=replacement.replace("mceNonEditable","mceNonEditable fallback")),void ed.dom.setOuterHTML(span,replacement)}const blockEl=function(text){const body=(new DOMParser).parseFromString(text,"text/html").body;return body.firstChild.nodeType!==Node.ELEMENT_NODE||body.children.length>1?null:-1!=["address","article","aside","blockquote","dd","div","dl","dt","figcaption","h1","h2","h3","h4","h5","h6","li","ol","p","pre","section","tfoot","ul"].indexOf(body.firstChild.tagName.toString().toLowerCase())?body.firstChild:null}(text);if(blockEl){let newtext=spanMultilangBegin.replace(regexLang,iso)+blockEl.innerHTML+spanMultilangEnd;return(0,_options.mlangFilterExists)(ed)||(newtext=newtext.replaceAll("mceNonEditable","mceNonEditable fallback")),blockEl.innerHTML=newtext,void ed.selection.setContent(blockEl.outerHTML)}let newtext=spanMultilangBegin.replace(regexLang,iso)+text+spanMultilangEnd;(0,_options.mlangFilterExists)(ed)||(newtext=newtext.replaceAll("mceNonEditable","mceNonEditable fallback")),ed.selection.setContent(newtext)}})); +const spanClass="multilang-begin mceNonEditable",spanFixedAttrs='{mlang %lang}',spanMultilangEnd=spanFixedAttrs.replace("begin","end")+">{mlang}",isNull=a=>null==a,addVisualStyling=function(ed){let content=ed.getContent();if(-1!==content.indexOf(spanClass))return content;if(content=content.replace(new RegExp("{\\s*mlang\\s+([^}]+?)\\s*}","ig"),(function(match,p1){return spanMultilangBegin.replace(new RegExp("%lang","g"),p1)})),content=content.replace(new RegExp("{\\s*mlang\\s*}","ig"),spanMultilangEnd),(0,_options.mlangFilterExists)(ed)&&!(0,_options.isFallbackSpanTag)(ed))return content;const doc=(new DOMParser).parseFromString(content,"text/html");if(0===doc.children.length)return content;const nodes=doc.querySelectorAll("span.multilang");if(0===nodes.length)return content;for(const span of nodes){const newSpan=spanMultilangBegin.replace(new RegExp("%lang","g"),span.getAttribute("lang")).replace("mceNonEditable","mceNonEditable fallback")+span.innerHTML+spanMultilangEnd.replace("mceNonEditable","mceNonEditable fallback");span.insertAdjacentHTML("afterend",newSpan),span.remove()}return doc.getElementsByTagName("body")[0].innerHTML},removeVisualStyling=function(ed){["begin","end"].forEach((function(t){for(const span of ed.dom.select("span.multilang-"+t))if("begin"===t&&span.classList.contains("fallback")){let innerHTML="",end=span,toRemove=[];for(;end&&(end=end.nextSibling,!isNull(end));){if(!isNull(end.classList)&&end.classList.contains("multilang-end")){toRemove.push(end);break}3===end.nodeType?innerHTML+=end.nodeValue:1===end.nodeType&&(innerHTML+=end.outerHTML),toRemove.push(end)}if(!isNull(end)){const lang=span.innerHTML.match(new RegExp("{\\s*mlang\\s+([^}]+?)\\s*}","i")),rtlLanguages=(0,_options.getRTLLanguages)();if(lang){const langCode=lang[1],dir=rtlLanguages.includes(langCode)?"rtl":"ltr",newHTML=''+innerHTML+"";for(end of(ed.dom.setOuterHTML(span,newHTML),toRemove))ed.dom.remove(end)}}}else ed.dom.setOuterHTML(span,span.innerHTML.toLowerCase())}))},getHighlightNodeFromSelect=function(ed,search){let span;return ed.dom.getParents(ed.selection.getStart(),(elm=>{if(!isNull(elm.classList)){const pair="begin"===search?"end":"begin";if(elm.classList.contains("multilang-"+pair)){span=elm;do{span="begin"===search?span.previousSibling:span.nextSibling}while(!isNull(span)&&(isNull(span.classList)||!span.classList.contains("multilang-"+search)))}else elm.classList.contains("multilang-"+search)&&(span=elm)}})),span},hideContentToolbar=function(el){for(;!isNull(el);){if(el.nodeType===Node.ELEMENT_NODE&&!isNull(el.getAttribute("class"))&&-1!=el.getAttribute("class").indexOf("tox-pop-"))return void(el.style.display="none");el=el.parentNode}};_exports.onInit=function(ed){ed.setContent(addVisualStyling(ed)),(0,_options.isContentToHighlight)(ed)&&ed.dom.addStyle((0,_options.getHighlightCss)(ed))};_exports.onBeforeGetContent=function(ed,content){if(!isNull(content.source_view)&&!0===content.source_view){var onClose=function(ed){ed.off("close",onClose),ed.setContent(addVisualStyling(ed))};ed.on("CloseWindow",(()=>{onClose(ed)})),removeVisualStyling(ed)}};_exports.onSetContent=function(ed){ed.setContent(addVisualStyling(ed),{no_events:!0})};_exports.onSubmit=function(ed){removeVisualStyling(ed)};_exports.onDelete=function(ed,event){if(event.isComposing||isNull(event.clientX)&&46!==event.keyCode&&8!==event.keyCode)return;if(!isNull(event.clientX)&&(event.target.nodeType!==Node.ELEMENT_NODE||"path"!==event.target.nodeName&&"svg"!==event.target.nodeName))return;const begin=getHighlightNodeFromSelect(ed,"begin"),end=getHighlightNodeFromSelect(ed,"end");isNull(begin)||isNull(end)||(event.preventDefault(),ed.dom.remove(begin),ed.dom.remove(end),isNull(event.clientX)||hideContentToolbar(event.target))};_exports.applyLanguage=function(ed,iso,event){if(isNull(iso))return;if("remove"===iso){return void ed.contentDocument.body.querySelectorAll(".multilang-begin, .multilang-end").forEach((element=>{ed.dom.remove(element)}))}const regexLang=/%lang/g;let text=ed.selection.getContent();if(""===text.toString().replace(/^\s+/,"").replace(/\s+$/,"")){if(!isNull(event))return void hideContentToolbar(event.target);let newtext=spanMultilangBegin.replace(regexLang,iso)+" "+spanMultilangEnd;return(0,_options.mlangFilterExists)(ed)||(newtext=newtext.replaceAll("mceNonEditable","mceNonEditable fallback")),void ed.insertContent(newtext)}isNull(event)||hideContentToolbar(event.target);const span=getHighlightNodeFromSelect(ed,"begin");if(!isNull(span)){let replacement=spanMultilangBegin.replace(regexLang,iso);return span.classList.contains("fallback")&&(replacement=replacement.replace("mceNonEditable","mceNonEditable fallback")),void ed.dom.setOuterHTML(span,replacement)}const blockEl=function(text){const body=(new DOMParser).parseFromString(text,"text/html").body;return body.firstChild.nodeType!==Node.ELEMENT_NODE||body.children.length>1?null:-1!=["address","article","aside","blockquote","dd","div","dl","dt","figcaption","h1","h2","h3","h4","h5","h6","li","ol","p","pre","section","tfoot","ul"].indexOf(body.firstChild.tagName.toString().toLowerCase())?body.firstChild:null}(text);if(blockEl){let newtext=spanMultilangBegin.replace(regexLang,iso)+blockEl.innerHTML+spanMultilangEnd;return(0,_options.mlangFilterExists)(ed)||(newtext=newtext.replaceAll("mceNonEditable","mceNonEditable fallback")),blockEl.innerHTML=newtext,void ed.selection.setContent(blockEl.outerHTML)}let newtext=spanMultilangBegin.replace(regexLang,iso)+text+spanMultilangEnd;(0,_options.mlangFilterExists)(ed)||(newtext=newtext.replaceAll("mceNonEditable","mceNonEditable fallback")),ed.selection.setContent(newtext)}})); //# 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 70935bb..f8e2928 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 - https://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 * Commands for the plugin logic of the Moodle tiny_multilang2 plugin.\n *\n * @module tiny_multilang2\n * @author Iñaki Arenaza \n * @author Stephan Robotta \n * @author Tai Le Tan \n * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getHighlightCss, isContentToHighlight, mlangFilterExists, isFallbackSpanTag, getRTLLanguages} from './options';\n\n// This class inside a identified the {mlang} tag that is encapsulated in a span.\nconst spanClass = 'multilang-begin mceNonEditable';\n// This is the element with the data attribute.\nconst spanFixedAttrs = '{mlang %lang}';\n// The end span doesn't need information about the used language.\nconst spanMultilangEnd = spanFixedAttrs.replace('begin', 'end') + '>{mlang}';\n// Helper functions\nconst trim = v => v.toString().replace(/^\\s+/, '').replace(/\\s+$/, '');\nconst isNull = a => a === null || a === undefined;\n\n/**\n * Convert {mlang xx} and {mlang} strings to spans, so we can style them visually.\n * Remove superflous whitespace while at it.\n * @param {tinymce.Editor} ed\n * @return {string}\n */\nconst addVisualStyling = function(ed) {\n\n let content = ed.getContent();\n\n // Do not use a variable whether text is already highlighted, do a check for the existing class\n // because this is safe for many tiny element windows at one page.\n if (content.indexOf(spanClass) !== -1) {\n return content;\n }\n\n // First look for any {mlang} tags in the content string and do a preg_replace with the corresponding\n // tag that encapsulated the {mlang} tag so that the {mlang} is highlighted.\n content = content.replace(new RegExp('{\\\\s*mlang\\\\s+([^}]+?)\\\\s*}', 'ig'), function(match, p1) {\n return spanMultilangBegin.replace(new RegExp('%lang', 'g'), p1);\n });\n content = content.replace(new RegExp('{\\\\s*mlang\\\\s*}', 'ig'), spanMultilangEnd);\n\n // If we have the multilang2 filter installed and wish not to check for the traditional\n // tags, then we are done here.\n if (mlangFilterExists(ed) && !isFallbackSpanTag(ed)) {\n return content;\n }\n // Any tag must be replaced with a {mlang XX}\n // and the corresponding closing must be replaced by {mlang}.\n // To handle this, we must convert the string into a DOMDocument so that any span.multilang tag can be searched\n // and replaced.\n const dom = new DOMParser();\n const doc = dom.parseFromString(content, 'text/html');\n if (doc.children.length === 0) { // Should not happen, but anyway, keep the check.\n return content;\n }\n const nodes = doc.querySelectorAll('span.multilang');\n if (nodes.length === 0) {\n return content;\n }\n for (const span of nodes) {\n const newSpan = spanMultilangBegin\n .replace(new RegExp('%lang', 'g'), span.getAttribute('lang'))\n .replace('mceNonEditable', 'mceNonEditable fallback')\n + span.innerHTML\n + spanMultilangEnd\n .replace('mceNonEditable', 'mceNonEditable fallback');\n // Insert the replacement string after the span tag itself by converting it into a html fragment.\n span.insertAdjacentHTML('afterend', newSpan);\n // Once the new tags are placed at the correct position, we can remove the original span tag.\n span.remove();\n }\n // Convert the DOMDocument into a string again.\n return doc.getElementsByTagName('body')[0].innerHTML;\n};\n\n/**\n * Remove the spans we added in _add_visual_styling() to leave only the {mlang xx} and {mlang} tags.\n * Also make sure we lowercase the multilang 'tags'\n * @param {tinymce.Editor} ed\n */\nconst removeVisualStyling = function(ed) {\n ['begin', 'end'].forEach(function(t) {\n for (const span of ed.dom.select('span.multilang-' + t)) {\n if (t === 'begin' && span.classList.contains('fallback')) {\n // This placeholder tag was created from an oldstyle tag.\n let innerHTML = '';\n let end = span;\n let toRemove = [];\n // Search the corresponding closing tag.\n while (end) {\n end = end.nextSibling;\n if (isNull(end)) { // Got a parent that does not exist. Stop here.\n break;\n }\n if (!isNull(end.classList) && end.classList.contains('multilang-end')) {\n // We found the multilang-end node, that needs to be removed, and also, we can stop here.\n toRemove.push(end);\n break;\n }\n // Sibling inside the tags need to be preserved, but moved to the innerHTML of the real\n // span tag. Therefore, collect the node content as string and remember the real nodes\n // to remove them later.\n if (end.nodeType === 3) {\n innerHTML += end.nodeValue;\n } else if (end.nodeType === 1) {\n innerHTML += end.outerHTML;\n }\n toRemove.push(end);\n }\n if (!isNull(end)) {\n // Extract the language from the {mlang XX} tag.\n const lang = span.innerHTML.match(new RegExp('{\\\\s*mlang\\\\s+([^}]+?)\\\\s*}', 'i'));\n // Right to left default languages.\n const rtlLanguages = getRTLLanguages();\n if (lang) {\n const langCode = lang[1];\n // Add dir=\"rtl\" to the html tag any time the overall document direction is right-to-left.\n const dir = rtlLanguages.includes(langCode) ? 'rtl' : 'ltr';\n const newHTML = '' + innerHTML + '';\n ed.dom.setOuterHTML(span, newHTML);\n // And remove the other siblings.\n for (end of toRemove) {\n ed.dom.remove(end);\n }\n }\n }\n } else {\n // Normal placeholder tag, just restore the innerHTML that is {mlang XX} or {mlang}-\n ed.dom.setOuterHTML(span, span.innerHTML.toLowerCase());\n }\n }\n });\n};\n\n/**\n * At the current selection lookup for the current node. If we are inside a special span that encapsulates\n * the {lang} tag, then look for the corresponding opening or closing tag, depending on what's set in the\n * search param.\n * @param {tinymce.Editor} ed\n * @param {string} search\n * @return {Node|null} The encapsulating span tag if found.\n */\nconst getHighlightNodeFromSelect = function(ed, search) {\n let span;\n ed.dom.getParents(ed.selection.getStart(), elm => {\n // Are we in a span that highlights the lang tag.\n if (!isNull(elm.classList)) {\n // If we are on an opening/closing lang tag, we need to search for the corresponding opening/closing tag.\n const pair = search === 'begin' ? 'end' : 'begin';\n if (elm.classList.contains('multilang-' + pair)) {\n span = elm;\n do {\n // If we look for begin, go back siblings, otherwise look fnext siblings until end is found.\n span = search === 'begin' ? span.previousSibling : span.nextSibling;\n } while (!isNull(span) && (isNull(span.classList) || !span.classList.contains('multilang-' + search)));\n } else if (elm.classList.contains('multilang-' + search)) {\n // We are already on the correct tag we search for\n span = elm;\n }\n }\n });\n return span;\n};\n\n/**\n * Return the block element node from the string, in case the text fragment is some parsable HTML.\n * @param {string} text\n * @return {Node|null}\n */\nconst getBlockElement = function(text) {\n const dom = new DOMParser();\n const body = dom.parseFromString(text, 'text/html').body;\n // We must have one child and a node element only, otherwise the selection may span via several paragraphs.\n if (body.firstChild.nodeType !== Node.ELEMENT_NODE || body.children.length > 1) {\n return null;\n }\n // These are not all block elements, we check for some only where the lang tags should be placed inside.\n const blockTags = ['address', 'article', 'aside', 'blockquote',\n 'dd', 'div', 'dl', 'dt', 'figcaption', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n 'li', 'ol', 'p', 'pre', 'section', 'tfoot', 'ul'];\n if (blockTags.indexOf(body.firstChild.tagName.toString().toLowerCase()) != -1) {\n return body.firstChild;\n }\n return null;\n};\n\n/**\n * Check for the parent hierarchy elements, if there's a context toolbar container, then hide it.\n * @param {Node} el\n */\nconst hideContentToolbar = function(el) {\n while (!isNull(el)) {\n if (el.nodeType === Node.ELEMENT_NODE &&\n !isNull(el.getAttribute('class')) &&\n el.getAttribute('class').indexOf('tox-pop-') != -1\n ) {\n el.style.display = 'none';\n return;\n }\n el = el.parentNode;\n }\n};\n\n/**\n * When loading the editor for the first time, add the spans for highlighting the content.\n * @param {tinymce.Editor} ed\n */\nconst onInit = function(ed) {\n ed.setContent(addVisualStyling(ed));\n if (isContentToHighlight(ed)) {\n ed.dom.addStyle(getHighlightCss(ed));\n }\n};\n\n/**\n * When the source code view dialogue is show, we must remove the highlight spans from the editor content\n * and also add them again when the dialogue is closed.\n * Since this event is also triggered when the editor data is saved, we use this function to remove the\n * highlighting content at that time.\n * @param {tinymce.Editor} ed\n * @param {object} content\n */\nconst onBeforeGetContent = function(ed, content) {\n if (!isNull(content.source_view) && content.source_view === true) {\n // If the user clicks on 'Cancel' or the close button on the html\n // source code dialog view, make sure we re-add the visual styling.\n var onClose = function(ed) {\n ed.off('close', onClose);\n ed.setContent(addVisualStyling(ed));\n };\n ed.on('CloseWindow', () => {\n onClose(ed);\n });\n removeVisualStyling(ed);\n }\n};\n\n/**\n * Fires when the form containing the editor is submitted.\n * @param {tinymce.Editor} ed\n */\nconst onSubmit = function(ed) {\n removeVisualStyling(ed);\n};\n\n/**\n * Check for key press when something is deleted. If that happens inside a highlight span\n * tag, then remove this tag and the corresponding that open/closes this lang tag.\n * @param {tinymce.Editor} ed\n * @param {Object} event\n */\nconst onDelete = function(ed, event) {\n // We are not in composing mode, have not clicked and key or was not pressed.\n if (event.isComposing || (isNull(event.clientX) && event.keyCode !== 46 && event.keyCode !== 8)) {\n return;\n }\n // In case we clicked, check that we clicked an icon (this must have been the trash icon in the context menu).\n if (!isNull(event.clientX) &&\n (event.target.nodeType !== Node.ELEMENT_NODE || (event.target.nodeName !== 'path' && event.target.nodeName !== 'svg'))) {\n return;\n }\n // Conditions match either key or was pressed, or an click on an svg icon was done.\n // Check if we are inside a span for the language tag.\n const begin = getHighlightNodeFromSelect(ed, 'begin');\n const end = getHighlightNodeFromSelect(ed, 'end');\n // Only if both, start and end tags are found, then delete the nodes here and prevent the default handling\n // because the stuff to be deleted is already gone.\n if (!isNull(begin) && !isNull(end)) {\n event.preventDefault();\n ed.dom.remove(begin);\n ed.dom.remove(end);\n if (!isNull(event.clientX)) {\n hideContentToolbar(event.target);\n }\n }\n};\n\n/**\n * The action when a language icon or menu entry is clicked. This adds the {mlang} tags at the current content\n * position or around the selection.\n * @param {tinymce.Editor} ed\n * @param {string} iso\n * @param {Event} event\n */\nconst applyLanguage = function(ed, iso, event) {\n if (isNull(iso)) {\n return;\n }\n if (iso === \"remove\") {\n const elements = ed.contentDocument.body;\n // Find all elements with the class \"multilang-begin\" or \"multilang-end\".\n const multiLangElements = elements.querySelectorAll('.multilang-begin, .multilang-end');\n multiLangElements.forEach(element => {\n ed.dom.remove(element);\n });\n return;\n }\n const regexLang = /%lang/g;\n let text = ed.selection.getContent();\n // Selection is empty, just insert the lang opening and closing tag\n // together with a space where the user may add the content.\n if (trim(text) === '') {\n // Event is set when the context menu was hit, here the editor lost the previously selected node. Therfore,\n // don't do anything.\n if (!isNull(event)) {\n hideContentToolbar(event.target);\n return;\n }\n let newtext = spanMultilangBegin.replace(regexLang, iso) + ' ' + spanMultilangEnd;\n if (!mlangFilterExists(ed)) {\n // No mlang filter, add the fallback class to the highlight spans so that these are translated\n // to the standard elements.\n newtext = newtext.replaceAll('mceNonEditable', 'mceNonEditable fallback');\n }\n ed.insertContent(newtext);\n return;\n }\n // Hide context toolbar, because at any subsequent call the node is not selected anymore.\n if (!isNull(event)) {\n hideContentToolbar(event.target);\n }\n // No matter if we have syntax highlighting enabled or not, the spans around the language tags exist\n // in the WYSIWYG mode. So check if we are on a special span that encapsulates the language tags. Search\n // for the start span tag.\n const span = getHighlightNodeFromSelect(ed, 'begin');\n // If we have a span, then it's the opening tag, and we just replace this one with the new iso.\n if (!isNull(span)) {\n let replacement = spanMultilangBegin.replace(regexLang, iso);\n if (span.classList.contains('fallback')) {\n replacement = replacement.replace('mceNonEditable', 'mceNonEditable fallback');\n }\n ed.dom.setOuterHTML(span, replacement);\n return;\n }\n const blockEl = getBlockElement(text);\n if (blockEl) {\n // We have a block element selected, such as a hX or p tag. Then keep this tag and place the\n // language tags inside but around the content of the block element.\n let newtext = spanMultilangBegin.replace(regexLang, iso) + blockEl.innerHTML + spanMultilangEnd;\n if (!mlangFilterExists(ed)) { // No mlang filter, add the fallback class to the highlight spans.\n newtext = newtext.replaceAll('mceNonEditable', 'mceNonEditable fallback');\n }\n blockEl.innerHTML = newtext;\n ed.selection.setContent(blockEl.outerHTML);\n return;\n }\n // Not inside a lang tag, insert a new opening and closing tag with the selection inside.\n let newtext = spanMultilangBegin.replace(regexLang, iso) + text + spanMultilangEnd;\n if (!mlangFilterExists(ed)) { // No mlang filter, add the fallback class to the highlight spans.\n newtext = newtext.replaceAll('mceNonEditable', 'mceNonEditable fallback');\n }\n ed.selection.setContent(newtext);\n};\n\nexport {\n onInit,\n onBeforeGetContent,\n onSubmit,\n onDelete,\n applyLanguage\n};\n"],"names":["spanClass","spanFixedAttrs","spanMultilangBegin","spanMultilangEnd","replace","isNull","a","addVisualStyling","ed","content","getContent","indexOf","RegExp","match","p1","doc","DOMParser","parseFromString","children","length","nodes","querySelectorAll","span","newSpan","getAttribute","innerHTML","insertAdjacentHTML","remove","getElementsByTagName","removeVisualStyling","forEach","t","dom","select","classList","contains","end","toRemove","nextSibling","push","nodeType","nodeValue","outerHTML","lang","rtlLanguages","langCode","dir","includes","newHTML","setOuterHTML","toLowerCase","getHighlightNodeFromSelect","search","getParents","selection","getStart","elm","pair","previousSibling","hideContentToolbar","el","Node","ELEMENT_NODE","style","display","parentNode","setContent","addStyle","source_view","onClose","off","on","event","isComposing","clientX","keyCode","target","nodeName","begin","preventDefault","iso","contentDocument","body","element","regexLang","text","toString","newtext","replaceAll","insertContent","replacement","blockEl","firstChild","tagName","getBlockElement"],"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 - https://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 * Commands for the plugin logic of the Moodle tiny_multilang2 plugin.\n *\n * @module tiny_multilang2\n * @author Iñaki Arenaza \n * @author Stephan Robotta \n * @author Tai Le Tan \n * @copyright 2015 onwards Iñaki Arenaza & Mondragon Unibertsitatea\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getHighlightCss, isContentToHighlight, mlangFilterExists, isFallbackSpanTag, getRTLLanguages} from './options';\n\n// This class inside a identified the {mlang} tag that is encapsulated in a span.\nconst spanClass = 'multilang-begin mceNonEditable';\n// This is the element with the data attribute.\nconst spanFixedAttrs = '{mlang %lang}';\n// The end span doesn't need information about the used language.\nconst spanMultilangEnd = spanFixedAttrs.replace('begin', 'end') + '>{mlang}';\n// Helper functions\nconst trim = v => v.toString().replace(/^\\s+/, '').replace(/\\s+$/, '');\nconst isNull = a => a === null || a === undefined;\n\n/**\n * Convert {mlang xx} and {mlang} strings to spans, so we can style them visually.\n * Remove superflous whitespace while at it.\n * @param {tinymce.Editor} ed\n * @return {string}\n */\nconst addVisualStyling = function(ed) {\n\n let content = ed.getContent();\n\n // Do not use a variable whether text is already highlighted, do a check for the existing class\n // because this is safe for many tiny element windows at one page.\n if (content.indexOf(spanClass) !== -1) {\n return content;\n }\n\n // First look for any {mlang} tags in the content string and do a preg_replace with the corresponding\n // tag that encapsulated the {mlang} tag so that the {mlang} is highlighted.\n content = content.replace(new RegExp('{\\\\s*mlang\\\\s+([^}]+?)\\\\s*}', 'ig'), function(match, p1) {\n return spanMultilangBegin.replace(new RegExp('%lang', 'g'), p1);\n });\n content = content.replace(new RegExp('{\\\\s*mlang\\\\s*}', 'ig'), spanMultilangEnd);\n\n // If we have the multilang2 filter installed and wish not to check for the traditional\n // tags, then we are done here.\n if (mlangFilterExists(ed) && !isFallbackSpanTag(ed)) {\n return content;\n }\n // Any tag must be replaced with a {mlang XX}\n // and the corresponding closing must be replaced by {mlang}.\n // To handle this, we must convert the string into a DOMDocument so that any span.multilang tag can be searched\n // and replaced.\n const dom = new DOMParser();\n const doc = dom.parseFromString(content, 'text/html');\n if (doc.children.length === 0) { // Should not happen, but anyway, keep the check.\n return content;\n }\n const nodes = doc.querySelectorAll('span.multilang');\n if (nodes.length === 0) {\n return content;\n }\n for (const span of nodes) {\n const newSpan = spanMultilangBegin\n .replace(new RegExp('%lang', 'g'), span.getAttribute('lang'))\n .replace('mceNonEditable', 'mceNonEditable fallback')\n + span.innerHTML\n + spanMultilangEnd\n .replace('mceNonEditable', 'mceNonEditable fallback');\n // Insert the replacement string after the span tag itself by converting it into a html fragment.\n span.insertAdjacentHTML('afterend', newSpan);\n // Once the new tags are placed at the correct position, we can remove the original span tag.\n span.remove();\n }\n // Convert the DOMDocument into a string again.\n return doc.getElementsByTagName('body')[0].innerHTML;\n};\n\n/**\n * Remove the spans we added in _add_visual_styling() to leave only the {mlang xx} and {mlang} tags.\n * Also make sure we lowercase the multilang 'tags'\n * @param {tinymce.Editor} ed\n */\nconst removeVisualStyling = function(ed) {\n ['begin', 'end'].forEach(function(t) {\n for (const span of ed.dom.select('span.multilang-' + t)) {\n if (t === 'begin' && span.classList.contains('fallback')) {\n // This placeholder tag was created from an oldstyle tag.\n let innerHTML = '';\n let end = span;\n let toRemove = [];\n // Search the corresponding closing tag.\n while (end) {\n end = end.nextSibling;\n if (isNull(end)) { // Got a parent that does not exist. Stop here.\n break;\n }\n if (!isNull(end.classList) && end.classList.contains('multilang-end')) {\n // We found the multilang-end node, that needs to be removed, and also, we can stop here.\n toRemove.push(end);\n break;\n }\n // Sibling inside the tags need to be preserved, but moved to the innerHTML of the real\n // span tag. Therefore, collect the node content as string and remember the real nodes\n // to remove them later.\n if (end.nodeType === 3) {\n innerHTML += end.nodeValue;\n } else if (end.nodeType === 1) {\n innerHTML += end.outerHTML;\n }\n toRemove.push(end);\n }\n if (!isNull(end)) {\n // Extract the language from the {mlang XX} tag.\n const lang = span.innerHTML.match(new RegExp('{\\\\s*mlang\\\\s+([^}]+?)\\\\s*}', 'i'));\n // Right to left default languages.\n const rtlLanguages = getRTLLanguages();\n if (lang) {\n const langCode = lang[1];\n // Add dir=\"rtl\" to the html tag any time the overall document direction is right-to-left.\n const dir = rtlLanguages.includes(langCode) ? 'rtl' : 'ltr';\n const newHTML = '' + innerHTML + '';\n ed.dom.setOuterHTML(span, newHTML);\n // And remove the other siblings.\n for (end of toRemove) {\n ed.dom.remove(end);\n }\n }\n }\n } else {\n // Normal placeholder tag, just restore the innerHTML that is {mlang XX} or {mlang}-\n ed.dom.setOuterHTML(span, span.innerHTML.toLowerCase());\n }\n }\n });\n};\n\n/**\n * At the current selection lookup for the current node. If we are inside a special span that encapsulates\n * the {lang} tag, then look for the corresponding opening or closing tag, depending on what's set in the\n * search param.\n * @param {tinymce.Editor} ed\n * @param {string} search\n * @return {Node|null} The encapsulating span tag if found.\n */\nconst getHighlightNodeFromSelect = function(ed, search) {\n let span;\n ed.dom.getParents(ed.selection.getStart(), elm => {\n // Are we in a span that highlights the lang tag.\n if (!isNull(elm.classList)) {\n // If we are on an opening/closing lang tag, we need to search for the corresponding opening/closing tag.\n const pair = search === 'begin' ? 'end' : 'begin';\n if (elm.classList.contains('multilang-' + pair)) {\n span = elm;\n do {\n // If we look for begin, go back siblings, otherwise look fnext siblings until end is found.\n span = search === 'begin' ? span.previousSibling : span.nextSibling;\n } while (!isNull(span) && (isNull(span.classList) || !span.classList.contains('multilang-' + search)));\n } else if (elm.classList.contains('multilang-' + search)) {\n // We are already on the correct tag we search for\n span = elm;\n }\n }\n });\n return span;\n};\n\n/**\n * Return the block element node from the string, in case the text fragment is some parsable HTML.\n * @param {string} text\n * @return {Node|null}\n */\nconst getBlockElement = function(text) {\n const dom = new DOMParser();\n const body = dom.parseFromString(text, 'text/html').body;\n // We must have one child and a node element only, otherwise the selection may span via several paragraphs.\n if (body.firstChild.nodeType !== Node.ELEMENT_NODE || body.children.length > 1) {\n return null;\n }\n // These are not all block elements, we check for some only where the lang tags should be placed inside.\n const blockTags = ['address', 'article', 'aside', 'blockquote',\n 'dd', 'div', 'dl', 'dt', 'figcaption', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n 'li', 'ol', 'p', 'pre', 'section', 'tfoot', 'ul'];\n if (blockTags.indexOf(body.firstChild.tagName.toString().toLowerCase()) != -1) {\n return body.firstChild;\n }\n return null;\n};\n\n/**\n * Check for the parent hierarchy elements, if there's a context toolbar container, then hide it.\n * @param {Node} el\n */\nconst hideContentToolbar = function(el) {\n while (!isNull(el)) {\n if (el.nodeType === Node.ELEMENT_NODE &&\n !isNull(el.getAttribute('class')) &&\n el.getAttribute('class').indexOf('tox-pop-') != -1\n ) {\n el.style.display = 'none';\n return;\n }\n el = el.parentNode;\n }\n};\n\n/**\n * When loading the editor for the first time, add the spans for highlighting the content.\n * @param {tinymce.Editor} ed\n */\nconst onInit = function(ed) {\n ed.setContent(addVisualStyling(ed));\n if (isContentToHighlight(ed)) {\n ed.dom.addStyle(getHighlightCss(ed));\n }\n};\n\n/**\n * When the source code view dialogue is show, we must remove the highlight spans from the editor content\n * and also add them again when the dialogue is closed.\n * Since this event is also triggered when the editor data is saved, we use this function to remove the\n * highlighting content at that time.\n * @param {tinymce.Editor} ed\n * @param {object} content\n */\nconst onBeforeGetContent = function(ed, content) {\n if (!isNull(content.source_view) && content.source_view === true) {\n // If the user clicks on 'Cancel' or the close button on the html\n // source code dialog view, make sure we re-add the visual styling.\n var onClose = function(ed) {\n ed.off('close', onClose);\n ed.setContent(addVisualStyling(ed));\n };\n ed.on('CloseWindow', () => {\n onClose(ed);\n });\n removeVisualStyling(ed);\n }\n};\n\n/**\n * Whenever a content modification happens, make sure that the styling for the language tags is added.\n * @param {tinymce.Editor} ed\n */\nconst onSetContent = function(ed) {\n ed.setContent(addVisualStyling(ed), {no_events: true});\n};\n\n/**\n * Fires when the form containing the editor is submitted.\n * @param {tinymce.Editor} ed\n */\nconst onSubmit = function(ed) {\n removeVisualStyling(ed);\n};\n\n/**\n * Check for key press when something is deleted. If that happens inside a highlight span\n * tag, then remove this tag and the corresponding that open/closes this lang tag.\n * @param {tinymce.Editor} ed\n * @param {Object} event\n */\nconst onDelete = function(ed, event) {\n // We are not in composing mode, have not clicked and key or was not pressed.\n if (event.isComposing || (isNull(event.clientX) && event.keyCode !== 46 && event.keyCode !== 8)) {\n return;\n }\n // In case we clicked, check that we clicked an icon (this must have been the trash icon in the context menu).\n if (!isNull(event.clientX) &&\n (event.target.nodeType !== Node.ELEMENT_NODE || (event.target.nodeName !== 'path' && event.target.nodeName !== 'svg'))) {\n return;\n }\n // Conditions match either key or was pressed, or an click on an svg icon was done.\n // Check if we are inside a span for the language tag.\n const begin = getHighlightNodeFromSelect(ed, 'begin');\n const end = getHighlightNodeFromSelect(ed, 'end');\n // Only if both, start and end tags are found, then delete the nodes here and prevent the default handling\n // because the stuff to be deleted is already gone.\n if (!isNull(begin) && !isNull(end)) {\n event.preventDefault();\n ed.dom.remove(begin);\n ed.dom.remove(end);\n if (!isNull(event.clientX)) {\n hideContentToolbar(event.target);\n }\n }\n};\n\n/**\n * The action when a language icon or menu entry is clicked. This adds the {mlang} tags at the current content\n * position or around the selection.\n * @param {tinymce.Editor} ed\n * @param {string} iso\n * @param {Event} event\n */\nconst applyLanguage = function(ed, iso, event) {\n if (isNull(iso)) {\n return;\n }\n if (iso === \"remove\") {\n const elements = ed.contentDocument.body;\n // Find all elements with the class \"multilang-begin\" or \"multilang-end\".\n const multiLangElements = elements.querySelectorAll('.multilang-begin, .multilang-end');\n multiLangElements.forEach(element => {\n ed.dom.remove(element);\n });\n return;\n }\n const regexLang = /%lang/g;\n let text = ed.selection.getContent();\n // Selection is empty, just insert the lang opening and closing tag\n // together with a space where the user may add the content.\n if (trim(text) === '') {\n // Event is set when the context menu was hit, here the editor lost the previously selected node. Therfore,\n // don't do anything.\n if (!isNull(event)) {\n hideContentToolbar(event.target);\n return;\n }\n let newtext = spanMultilangBegin.replace(regexLang, iso) + ' ' + spanMultilangEnd;\n if (!mlangFilterExists(ed)) {\n // No mlang filter, add the fallback class to the highlight spans so that these are translated\n // to the standard elements.\n newtext = newtext.replaceAll('mceNonEditable', 'mceNonEditable fallback');\n }\n ed.insertContent(newtext);\n return;\n }\n // Hide context toolbar, because at any subsequent call the node is not selected anymore.\n if (!isNull(event)) {\n hideContentToolbar(event.target);\n }\n // No matter if we have syntax highlighting enabled or not, the spans around the language tags exist\n // in the WYSIWYG mode. So check if we are on a special span that encapsulates the language tags. Search\n // for the start span tag.\n const span = getHighlightNodeFromSelect(ed, 'begin');\n // If we have a span, then it's the opening tag, and we just replace this one with the new iso.\n if (!isNull(span)) {\n let replacement = spanMultilangBegin.replace(regexLang, iso);\n if (span.classList.contains('fallback')) {\n replacement = replacement.replace('mceNonEditable', 'mceNonEditable fallback');\n }\n ed.dom.setOuterHTML(span, replacement);\n return;\n }\n const blockEl = getBlockElement(text);\n if (blockEl) {\n // We have a block element selected, such as a hX or p tag. Then keep this tag and place the\n // language tags inside but around the content of the block element.\n let newtext = spanMultilangBegin.replace(regexLang, iso) + blockEl.innerHTML + spanMultilangEnd;\n if (!mlangFilterExists(ed)) { // No mlang filter, add the fallback class to the highlight spans.\n newtext = newtext.replaceAll('mceNonEditable', 'mceNonEditable fallback');\n }\n blockEl.innerHTML = newtext;\n ed.selection.setContent(blockEl.outerHTML);\n return;\n }\n // Not inside a lang tag, insert a new opening and closing tag with the selection inside.\n let newtext = spanMultilangBegin.replace(regexLang, iso) + text + spanMultilangEnd;\n if (!mlangFilterExists(ed)) { // No mlang filter, add the fallback class to the highlight spans.\n newtext = newtext.replaceAll('mceNonEditable', 'mceNonEditable fallback');\n }\n ed.selection.setContent(newtext);\n};\n\nexport {\n onInit,\n onBeforeGetContent,\n onSetContent,\n onSubmit,\n onDelete,\n applyLanguage\n};\n"],"names":["spanClass","spanFixedAttrs","spanMultilangBegin","spanMultilangEnd","replace","isNull","a","addVisualStyling","ed","content","getContent","indexOf","RegExp","match","p1","doc","DOMParser","parseFromString","children","length","nodes","querySelectorAll","span","newSpan","getAttribute","innerHTML","insertAdjacentHTML","remove","getElementsByTagName","removeVisualStyling","forEach","t","dom","select","classList","contains","end","toRemove","nextSibling","push","nodeType","nodeValue","outerHTML","lang","rtlLanguages","langCode","dir","includes","newHTML","setOuterHTML","toLowerCase","getHighlightNodeFromSelect","search","getParents","selection","getStart","elm","pair","previousSibling","hideContentToolbar","el","Node","ELEMENT_NODE","style","display","parentNode","setContent","addStyle","source_view","onClose","off","on","no_events","event","isComposing","clientX","keyCode","target","nodeName","begin","preventDefault","iso","contentDocument","body","element","regexLang","text","toString","newtext","replaceAll","insertContent","replacement","blockEl","firstChild","tagName","getBlockElement"],"mappings":"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"} \ No newline at end of file diff --git a/amd/src/commands.js b/amd/src/commands.js index 37192a8..f4fbfe8 100644 --- a/amd/src/commands.js +++ b/amd/src/commands.js @@ -26,7 +26,7 @@ import {getLanguageList, showAllLanguages, isAddLanguage} from './options'; import {component} from './common'; import {get_strings as getStrings} from 'core/str'; -import {applyLanguage, onInit, onBeforeGetContent, onSubmit, onDelete} from './ui'; +import {applyLanguage, onInit, onBeforeGetContent, onSetContent, onSubmit, onDelete} from './ui'; /** * Get the setup function for the button and the menu entry. @@ -118,6 +118,12 @@ export const getSetup = async() => { editor.on('BeforeGetContent', (format) => { onBeforeGetContent(editor, format); }); + editor.on('focus', () => { + onSetContent(editor); + }); + editor.on('SetContent', () => { + onSetContent(editor); + }); editor.on('submit', () => { onSubmit(editor); }); diff --git a/amd/src/ui.js b/amd/src/ui.js index 74dc8cb..ba8f643 100644 --- a/amd/src/ui.js +++ b/amd/src/ui.js @@ -257,6 +257,14 @@ const onBeforeGetContent = function(ed, content) { } }; +/** + * Whenever a content modification happens, make sure that the styling for the language tags is added. + * @param {tinymce.Editor} ed + */ +const onSetContent = function(ed) { + ed.setContent(addVisualStyling(ed), {no_events: true}); +}; + /** * Fires when the form containing the editor is submitted. * @param {tinymce.Editor} ed @@ -377,6 +385,7 @@ const applyLanguage = function(ed, iso, event) { export { onInit, onBeforeGetContent, + onSetContent, onSubmit, onDelete, applyLanguage