From 603c50e1be27132925a800bedecff9a74433b69e Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 9 Aug 2022 15:02:15 +0800 Subject: [PATCH] MDL-75078 editor_tiny: Improve configuration specification Part of MDL-75966 --- lib/editor/tiny/amd/build/defaults.min.js | 11 ++ lib/editor/tiny/amd/build/defaults.min.js.map | 1 + lib/editor/tiny/amd/build/editor.min.js | 2 +- lib/editor/tiny/amd/build/editor.min.js.map | 2 +- lib/editor/tiny/amd/build/options.min.js | 3 + lib/editor/tiny/amd/build/options.min.js.map | 1 + lib/editor/tiny/amd/build/utils.min.js | 2 +- lib/editor/tiny/amd/build/utils.min.js.map | 2 +- lib/editor/tiny/amd/src/defaults.js | 177 ++++++++++++++++++ lib/editor/tiny/amd/src/editor.js | 145 +++++++------- lib/editor/tiny/amd/src/options.js | 99 ++++++++++ lib/editor/tiny/amd/src/utils.js | 92 +++++++-- lib/editor/tiny/classes/editor.php | 20 +- lib/editor/tiny/classes/manager.php | 34 +++- lib/editor/tiny/classes/plugin.php | 46 +++-- .../classes/plugin_with_configuration.php | 4 +- 16 files changed, 527 insertions(+), 114 deletions(-) create mode 100644 lib/editor/tiny/amd/build/defaults.min.js create mode 100644 lib/editor/tiny/amd/build/defaults.min.js.map create mode 100644 lib/editor/tiny/amd/build/options.min.js create mode 100644 lib/editor/tiny/amd/build/options.min.js.map create mode 100644 lib/editor/tiny/amd/src/defaults.js create mode 100644 lib/editor/tiny/amd/src/options.js diff --git a/lib/editor/tiny/amd/build/defaults.min.js b/lib/editor/tiny/amd/build/defaults.min.js new file mode 100644 index 00000000000..bf1be2de435 --- /dev/null +++ b/lib/editor/tiny/amd/build/defaults.min.js @@ -0,0 +1,11 @@ +define("editor_tiny/defaults",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getDefaultToolbar=_exports.getDefaultQuickbarsSelectionToolbar=_exports.getDefaultQuickbarsInsertToolbar=_exports.getDefaultQuickbarsImageToolbar=_exports.getDefaultMenu=_exports.getDefaultConfiguration=void 0; +/** + * TinyMCE Editor Upstream defaults. + * + * @module editor_tiny/editor + * @copyright 2022 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +const getDefaultMenu=()=>({file:{title:"File",items:"newdocument restoredraft | preview | export print | deleteallconversations"},edit:{title:"Edit",items:"undo redo | cut copy paste pastetext | selectall | searchreplace"},view:{title:"View",items:"code | visualaid visualchars visualblocks | spellchecker | preview fullscreen | showcomments"},insert:{title:"Insert",items:"image link media addcomment pageembed template codesample inserttable | charmap emoticons hr | pagebreak nonbreaking anchor tableofcontents | insertdatetime"},format:{title:"Format",items:"bold italic underline strikethrough superscript subscript codeformat | styles blocks fontfamily fontsize align lineheight | forecolor backcolor | language | removeformat"},tools:{title:"Tools",items:"spellchecker spellcheckerlanguage | a11ycheck code wordcount"},table:{title:"Table",items:"inserttable | cell row column | advtablesort | tableprops deletetable"},help:{title:"Help",items:"help"}});_exports.getDefaultMenu=getDefaultMenu;const getDefaultToolbar=()=>[{name:"content",items:[]},{name:"styles",items:["styles"]},{name:"formatting",items:["bold","italic"]},{name:"history",items:["undo","redo"]},{name:"alignment",items:["alignleft","aligncenter","alignright","alignjustify"]},{name:"indentation",items:["outdent","indent"]},{name:"comments",items:["addcomment"]}];_exports.getDefaultToolbar=getDefaultToolbar;const getDefaultQuickbarsSelectionToolbar=()=>"bold italic | quicklink h2 h3 blockquote";_exports.getDefaultQuickbarsSelectionToolbar=getDefaultQuickbarsSelectionToolbar;const getDefaultQuickbarsInsertToolbar=()=>"quickimage quicktable";_exports.getDefaultQuickbarsInsertToolbar=getDefaultQuickbarsInsertToolbar;const getDefaultQuickbarsImageToolbar=()=>"alignleft aligncenter alignright";_exports.getDefaultQuickbarsImageToolbar=getDefaultQuickbarsImageToolbar;_exports.getDefaultConfiguration=()=>({toolbar_mode:"sliding",toolbar:[{name:"content",items:[]},{name:"styles",items:["styles"]},{name:"formatting",items:["bold","italic"]},{name:"history",items:["undo","redo"]},{name:"alignment",items:["alignleft","aligncenter","alignright","alignjustify"]},{name:"indentation",items:["outdent","indent"]},{name:"comments",items:["addcomment"]}],quickbars_selection_toolbar:"bold italic | quicklink h2 h3 blockquote",quickbars_insert_toolbar:"quickimage quicktable",quickbars_image_toolbar:"alignleft aligncenter alignright",menu:{file:{title:"File",items:"newdocument restoredraft | preview | export print | deleteallconversations"},edit:{title:"Edit",items:"undo redo | cut copy paste pastetext | selectall | searchreplace"},view:{title:"View",items:"code | visualaid visualchars visualblocks | spellchecker | preview fullscreen | showcomments"},insert:{title:"Insert",items:"image link media addcomment pageembed template codesample inserttable | charmap emoticons hr | pagebreak nonbreaking anchor tableofcontents | insertdatetime"},format:{title:"Format",items:"bold italic underline strikethrough superscript subscript codeformat | styles blocks fontfamily fontsize align lineheight | forecolor backcolor | language | removeformat"},tools:{title:"Tools",items:"spellchecker spellcheckerlanguage | a11ycheck code wordcount"},table:{title:"Table",items:"inserttable | cell row column | advtablesort | tableprops deletetable"},help:{title:"Help",items:"help"}},skin:"oxide"})})); + +//# sourceMappingURL=defaults.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/amd/build/defaults.min.js.map b/lib/editor/tiny/amd/build/defaults.min.js.map new file mode 100644 index 00000000000..64f539a4a03 --- /dev/null +++ b/lib/editor/tiny/amd/build/defaults.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"defaults.min.js","sources":["../src/defaults.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/* eslint-disable max-len, */\n\n/**\n * TinyMCE Editor Upstream defaults.\n *\n * @module editor_tiny/editor\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * The upstream defaults for the TinyMCE Menu.\n *\n * This value is defined in the TinyMCE documentation, but not exported anywhere useful.\n * https://www.tiny.cloud/docs/tinymce/6/menus-configuration-options/#menu\n *\n * @returns {Object}\n */\nexport const getDefaultMenu = () => {\n return {\n file: {title: 'File', items: 'newdocument restoredraft | preview | export print | deleteallconversations'},\n edit: {title: 'Edit', items: 'undo redo | cut copy paste pastetext | selectall | searchreplace'},\n view: {title: 'View', items: 'code | visualaid visualchars visualblocks | spellchecker | preview fullscreen | showcomments'},\n insert: {title: 'Insert', items: 'image link media addcomment pageembed template codesample inserttable | charmap emoticons hr | pagebreak nonbreaking anchor tableofcontents | insertdatetime'},\n format: {title: 'Format', items: 'bold italic underline strikethrough superscript subscript codeformat | styles blocks fontfamily fontsize align lineheight | forecolor backcolor | language | removeformat'},\n tools: {title: 'Tools', items: 'spellchecker spellcheckerlanguage | a11ycheck code wordcount'},\n table: {title: 'Table', items: 'inserttable | cell row column | advtablesort | tableprops deletetable'},\n help: {title: 'Help', items: 'help'}\n };\n};\n\n/**\n * The default toolbar configuration to use.\n *\n * This is based upon the default value used if no toolbar is specified.\n *\n * https://www.tiny.cloud/docs/tinymce/6/menus-configuration-options/#menu\n *\n * @returns {Object}\n */\nexport const getDefaultToolbar = () => {\n return [\n {\n name: 'content',\n items: [],\n },\n {\n name: 'styles',\n items: ['styles']\n },\n {\n name: 'formatting',\n items: [\n 'bold',\n 'italic'\n ]\n },\n {\n name: 'history',\n items: [\n 'undo',\n 'redo'\n ]\n },\n {\n name: 'alignment',\n items: [\n 'alignleft',\n 'aligncenter',\n 'alignright',\n 'alignjustify'\n ]\n },\n {\n name: 'indentation',\n items: [\n 'outdent',\n 'indent'\n ]\n },\n {\n name: 'comments',\n items: ['addcomment']\n },\n ];\n};\n\n/**\n * The default quickbars_insert_toolbar configuration to use.\n *\n * This is based upon the default value used if no toolbar is specified.\n *\n * https://www.tiny.cloud/docs/tinymce/6/quickbars/#quickbars_selection_toolbar\n *\n * @returns {string}\n */\nexport const getDefaultQuickbarsSelectionToolbar = () => 'bold italic | quicklink h2 h3 blockquote';\n\n/**\n * The default quickbars_insert_toolbar configuration to use.\n *\n * This is based upon the default value used if no toolbar is specified.\n *\n * https://www.tiny.cloud/docs/tinymce/6/quickbars/#quickbars_insert_toolbar\n *\n * @returns {string}\n */\nexport const getDefaultQuickbarsInsertToolbar = () => 'quickimage quicktable';\n\n/**\n * The default quickbars_insert_toolbar configuration to use.\n *\n * This is based upon the default value used if no toolbar is specified.\n *\n * https://www.tiny.cloud/docs/tinymce/6/quickbars/#quickbars_image_toolbar\n *\n * @returns {string}\n */\nexport const getDefaultQuickbarsImageToolbar = () => 'alignleft aligncenter alignright';\n\n/**\n * Get the default configuration provided by TinyMCE.\n *\n * @returns {object}\n */\nexport const getDefaultConfiguration = () => ({\n // Toolbar configuration.\n // https://www.tiny.cloud/docs/tinymce/6/toolbar-configuration-options/\n // TODO: Move this configuration to a passed-in option.\n // eslint-disable-next-line camelcase\n toolbar_mode: 'sliding',\n toolbar: getDefaultToolbar(),\n\n // Quickbars Selection Toolbar configuration.\n // https://www.tiny.cloud/docs/tinymce/6/quickbars/#quickbars_selection_toolbar\n // eslint-disable-next-line camelcase\n quickbars_selection_toolbar: getDefaultQuickbarsSelectionToolbar(),\n\n // Quickbars Select Toolbar configuration.\n // https://www.tiny.cloud/docs/tinymce/6/quickbars/#quickbars_insert_toolbar\n // eslint-disable-next-line camelcase\n quickbars_insert_toolbar: getDefaultQuickbarsInsertToolbar(),\n\n // Quickbars Image Toolbar configuration.\n // https://www.tiny.cloud/docs/tinymce/6/quickbars/#quickbars_image_toolbar\n // eslint-disable-next-line camelcase\n quickbars_image_toolbar: getDefaultQuickbarsImageToolbar(),\n\n\n // Menu configuration.\n // https://www.tiny.cloud/docs/tinymce/6/menus-configuration-options/\n // TODO: Move this configuration to a passed-in option.\n menu: getDefaultMenu(),\n\n // TODO Add mobile configuration.\n // Mobile configuration.\n // https://www.tiny.cloud/docs/tinymce/6/tinymce-for-mobile/\n // This will include mobile-specific toolbar, and menu options.\n\n // Skins\n skin: 'oxide',\n});\n"],"names":["getDefaultMenu","file","title","items","edit","view","insert","format","tools","table","help","getDefaultToolbar","name","getDefaultQuickbarsSelectionToolbar","getDefaultQuickbarsInsertToolbar","getDefaultQuickbarsImageToolbar","toolbar_mode","toolbar","quickbars_selection_toolbar","quickbars_insert_toolbar","quickbars_image_toolbar","menu","skin"],"mappings":";;;;;;;;MAiCaA,eAAiB,KACnB,CACHC,KAAM,CAACC,MAAO,OAAQC,MAAO,8EAC7BC,KAAM,CAACF,MAAO,OAAQC,MAAO,oEAC7BE,KAAM,CAACH,MAAO,OAAQC,MAAO,gGAC7BG,OAAQ,CAACJ,MAAO,SAAUC,MAAO,gKACjCI,OAAQ,CAACL,MAAO,SAAUC,MAAO,6KACjCK,MAAO,CAACN,MAAO,QAASC,MAAO,gEAC/BM,MAAO,CAACP,MAAO,QAASC,MAAO,yEAC/BO,KAAM,CAACR,MAAO,OAAQC,MAAO,uDAaxBQ,kBAAoB,IACtB,CACH,CACIC,KAAM,UACNT,MAAO,IAEX,CACIS,KAAM,SACNT,MAAO,CAAC,WAEZ,CACIS,KAAM,aACNT,MAAO,CACH,OACA,WAGR,CACIS,KAAM,UACNT,MAAO,CACH,OACA,SAGR,CACIS,KAAM,YACNT,MAAO,CACH,YACA,cACA,aACA,iBAGR,CACIS,KAAM,cACNT,MAAO,CACH,UACA,WAGR,CACIS,KAAM,WACNT,MAAO,CAAC,mEAcPU,oCAAsC,IAAM,kIAW5CC,iCAAmC,IAAM,yGAWzCC,gCAAkC,IAAM,6IAOd,MAKnCC,aAAc,UACdC,QA1FO,CACH,CACIL,KAAM,UACNT,MAAO,IAEX,CACIS,KAAM,SACNT,MAAO,CAAC,WAEZ,CACIS,KAAM,aACNT,MAAO,CACH,OACA,WAGR,CACIS,KAAM,UACNT,MAAO,CACH,OACA,SAGR,CACIS,KAAM,YACNT,MAAO,CACH,YACA,cACA,aACA,iBAGR,CACIS,KAAM,cACNT,MAAO,CACH,UACA,WAGR,CACIS,KAAM,WACNT,MAAO,CAAC,gBAsDhBe,4BAxCqD,2CA6CrDC,yBAlCkD,wBAuClDC,wBA5BiD,mCAkCjDC,KArIO,CACHpB,KAAM,CAACC,MAAO,OAAQC,MAAO,8EAC7BC,KAAM,CAACF,MAAO,OAAQC,MAAO,oEAC7BE,KAAM,CAACH,MAAO,OAAQC,MAAO,gGAC7BG,OAAQ,CAACJ,MAAO,SAAUC,MAAO,gKACjCI,OAAQ,CAACL,MAAO,SAAUC,MAAO,6KACjCK,MAAO,CAACN,MAAO,QAASC,MAAO,gEAC/BM,MAAO,CAACP,MAAO,QAASC,MAAO,yEAC/BO,KAAM,CAACR,MAAO,OAAQC,MAAO,SAqIjCmB,KAAM"} \ No newline at end of file diff --git a/lib/editor/tiny/amd/build/editor.min.js b/lib/editor/tiny/amd/build/editor.min.js index 3403c3fb522..fb43f248647 100644 --- a/lib/editor/tiny/amd/build/editor.min.js +++ b/lib/editor/tiny/amd/build/editor.min.js @@ -1,3 +1,3 @@ -define("editor_tiny/editor",["exports","./loader","core/pending"],(function(_exports,_loader,_pending){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setupForTarget=_exports.setupForElementId=_exports.getInstanceForElementId=_exports.getInstanceForElement=_exports.getAllInstances=_exports.configureDefaultEditor=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};const instanceMap=new Map;let defaultOptions={};const importPluginList=async pluginList=>{const pluginHandlers=await Promise.all(pluginList.map((pluginPath=>-1===pluginPath.indexOf("/")?Promise.resolve(pluginPath):"function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([pluginPath],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(pluginPath)):Promise.resolve(_systemImportTransformerGlobalIdentifier[pluginPath])))),pluginNames=pluginHandlers.map((pluginConfig=>"string"==typeof pluginConfig?pluginConfig:Array.isArray(pluginConfig)?pluginConfig[0]:null)).filter((value=>value));return{pluginNames:pluginNames,pluginConfig:pluginHandlers.map((pluginConfig=>Array.isArray(pluginConfig)?pluginConfig[1]:null)).filter((value=>value))}};_exports.getAllInstances=()=>new Map(instanceMap.entries());_exports.getInstanceForElementId=elementId=>getInstanceForElement(document.getElementById(elementId));const getInstanceForElement=element=>{const instance=instanceMap.get(element);if(!instance||!instance.removed)return instance;instanceMap.remove(element)};_exports.getInstanceForElement=getInstanceForElement;_exports.setupForElementId=_ref=>{let{elementId:elementId,options:options}=_ref;const target=document.getElementById(elementId);return setupForTarget(target,options)};(async()=>{const lang=document.querySelector("html").lang,[tinyMCE,langData]=await Promise.all([(0,_loader.getTinyMCE)(),(language=lang,fetch("".concat(M.cfg.wwwroot,"/lib/editor/tiny/lang.php/").concat(M.cfg.langrev,"/").concat(language)).then((response=>response.json())))]);var language;tinyMCE.addI18n(lang,langData)})();const getPlugins=options=>options.plugins?options.plugins:defaultOptions.plugins?defaultOptions.plugins:{},getStandardConfig=(target,tinyMCE,options,plugins)=>{const lang=document.querySelector("html").lang;return{base_url:_loader.baseUrl,target:target,language:lang,content_css:[options.css],convert_urls:!1,a11y_advanced_options:!0,toolbar_mode:"sliding",toolbar:[{name:"history",items:["undo","redo"]},{name:"styles",items:["styles"]},{name:"formatting",items:["bold","italic"]},{name:"alignment",items:["alignleft","aligncenter","alignright","alignjustify"]},{name:"indentation",items:["outdent","indent"]},{name:"comments",items:["addcomment"]}],menu:{},plugins:[...plugins],skin:"oxide",promotion:!1}},setupForTarget=async function(target){let options=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const instance=getInstanceForElement(target);if(instance)return Promise.resolve(instance);const pendingPromise=new _pending.default("editor_tiny/editor:setupForTarget"),plugins=getPlugins(options),[tinyMCE,pluginValues]=await Promise.all([(0,_loader.getTinyMCE)(),importPluginList(Object.keys(plugins))]),{pluginNames:pluginNames,pluginConfig:pluginConfig}=pluginValues,instanceConfig=getStandardConfig(target,0,options,pluginNames);pluginConfig.forEach((pluginConfig=>{"function"==typeof pluginConfig.configure&&Object.assign(instanceConfig,pluginConfig.configure(instanceConfig))}));const[editor]=await tinyMCE.init(instanceConfig);return instanceMap.set(target,editor),editor.on("remove",(_ref2=>{let{target:target}=_ref2;instanceMap.delete(target.targetElm)})),editor.moodleOptions=options,pendingPromise.resolve(),editor};_exports.setupForTarget=setupForTarget;_exports.configureDefaultEditor=function(){let options=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};defaultOptions=options}})); +define("editor_tiny/editor",["exports","core/pending","./defaults","./loader","./options"],(function(_exports,_pending,_defaults,_loader,Options){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setupForTarget=_exports.setupForElementId=_exports.getInstanceForElementId=_exports.getInstanceForElement=_exports.getAllInstances=_exports.configureDefaultEditor=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj},Options=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Options);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}const instanceMap=new Map;let defaultOptions={};const importPluginList=async pluginList=>{const pluginHandlers=await Promise.all(pluginList.map((pluginPath=>-1===pluginPath.indexOf("/")?Promise.resolve(pluginPath):"function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([pluginPath],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(pluginPath)):Promise.resolve(_systemImportTransformerGlobalIdentifier[pluginPath])))),pluginNames=pluginHandlers.map((pluginConfig=>"string"==typeof pluginConfig?pluginConfig:Array.isArray(pluginConfig)?pluginConfig[0]:null)).filter((value=>value));return{pluginNames:pluginNames,pluginConfig:pluginHandlers.map((pluginConfig=>Array.isArray(pluginConfig)?pluginConfig[1]:null)).filter((value=>value))}};_exports.getAllInstances=()=>new Map(instanceMap.entries());_exports.getInstanceForElementId=elementId=>getInstanceForElement(document.getElementById(elementId));const getInstanceForElement=element=>{const instance=instanceMap.get(element);if(!instance||!instance.removed)return instance;instanceMap.remove(element)};_exports.getInstanceForElement=getInstanceForElement;_exports.setupForElementId=_ref=>{let{elementId:elementId,options:options}=_ref;const target=document.getElementById(elementId);return setupForTarget(target,options)};(async()=>{const lang=document.querySelector("html").lang,[tinyMCE,langData]=await Promise.all([(0,_loader.getTinyMCE)(),(language=lang,fetch("".concat(M.cfg.wwwroot,"/lib/editor/tiny/lang.php/").concat(M.cfg.langrev,"/").concat(language)).then((response=>response.json())))]);var language;tinyMCE.addI18n(lang,langData)})();const getPlugins=function(){let{plugins:plugins=null}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return plugins||(defaultOptions.plugins?defaultOptions.plugins:{})},getStandardConfig=(target,tinyMCE,options,plugins)=>{const lang=document.querySelector("html").lang;return Object.assign({},(0,_defaults.getDefaultConfiguration)(),{base_url:_loader.baseUrl,target:target,language:lang,content_css:[options.css],convert_urls:!1,a11y_advanced_options:!0,plugins:[...plugins],skin:"oxide",promotion:!1,setup:editor=>{Options.register(editor,options)}})},setupForTarget=async function(target){let options=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const instance=getInstanceForElement(target);if(instance)return Promise.resolve(instance);const pendingPromise=new _pending.default("editor_tiny/editor:setupForTarget"),plugins=getPlugins(options),[tinyMCE,pluginValues]=await Promise.all([(0,_loader.getTinyMCE)(),importPluginList(Object.keys(plugins))]),{pluginNames:pluginNames,pluginConfig:pluginConfig}=pluginValues,instanceConfig=getStandardConfig(target,0,options,pluginNames);pluginConfig.forEach((pluginConfig=>{"function"==typeof pluginConfig.configure&&Object.assign(instanceConfig,pluginConfig.configure(instanceConfig,options))})),Object.assign(instanceConfig,Options.getInitialPluginConfiguration(options));const[editor]=await tinyMCE.init(instanceConfig);return instanceMap.set(target,editor),editor.on("remove",(_ref2=>{let{target:target}=_ref2;instanceMap.delete(target.targetElm)})),pendingPromise.resolve(),editor};_exports.setupForTarget=setupForTarget;_exports.configureDefaultEditor=function(){let options=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};defaultOptions=options}})); //# sourceMappingURL=editor.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/amd/build/editor.min.js.map b/lib/editor/tiny/amd/build/editor.min.js.map index 58fe0a3123e..8b7325d5971 100644 --- a/lib/editor/tiny/amd/build/editor.min.js.map +++ b/lib/editor/tiny/amd/build/editor.min.js.map @@ -1 +1 @@ -{"version":3,"file":"editor.min.js","sources":["../src/editor.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 * Utility functions.\n *\n * @module editor_tiny/editor\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {\n getTinyMCE,\n baseUrl,\n} from './loader';\nimport Pending from 'core/pending';\n\n/**\n * Storage for the TinyMCE instances on the page.\n * @type {Map}\n */\nconst instanceMap = new Map();\n\n/**\n * The default editor configuration.\n * @type {Object}\n */\nlet defaultOptions = {};\n\n/**\n * Require the modules for the named set of TinyMCE plugins.\n *\n * @param {string[]} pluginList The list of plugins\n * @return {Promise[]} A matching set of Promises relating to the requested plugins\n */\nconst importPluginList = async(pluginList) => {\n const pluginHandlers = await Promise.all(pluginList.map(pluginPath => {\n if (pluginPath.indexOf('/') === -1) {\n // A standard TinyMCE Plugin.\n return Promise.resolve(pluginPath);\n }\n\n return import(pluginPath);\n }));\n\n const pluginNames = pluginHandlers.map((pluginConfig) => {\n if (typeof pluginConfig === 'string') {\n return pluginConfig;\n }\n if (Array.isArray(pluginConfig)) {\n return pluginConfig[0];\n }\n return null;\n }).filter((value) => value);\n\n const pluginConfig = pluginHandlers.map((pluginConfig) => {\n if (Array.isArray(pluginConfig)) {\n return pluginConfig[1];\n }\n return null;\n }).filter((value) => value);\n\n return {\n pluginNames,\n pluginConfig,\n };\n};\n\nconst fetchLanguage = (language) => fetch(\n `${M.cfg.wwwroot}/lib/editor/tiny/lang.php/${M.cfg.langrev}/${language}`\n).then(response => response.json());\n\nexport const getAllInstances = () => new Map(instanceMap.entries());\n\n/**\n * Get the TinyMCE instance for the specified Node ID.\n *\n * @param {string} elementId\n * @returns {TinyMCE|undefined}\n */\nexport const getInstanceForElementId = elementId => getInstanceForElement(document.getElementById(elementId));\n\n/*\n * Get the TinyMCE instance for the specified HTMLElement.\n *\n * @param {HTMLElement} element\n * @returns {TinyMCE|undefined}\n */\nexport const getInstanceForElement = element => {\n const instance = instanceMap.get(element);\n if (instance && instance.removed) {\n instanceMap.remove(element);\n return undefined;\n }\n return instance;\n};\n\n/**\n * Set up TinyMCE for the selector at the specified HTML Node id.\n *\n * @param {object} config The configuration required to setup the editor\n * @param {string} config.elementId The HTML Node ID\n * @param {Object} config.options The editor plugin configuration\n * @return {Promise} The TinyMCE instance\n */\nexport const setupForElementId = ({elementId, options}) => {\n const target = document.getElementById(elementId);\n return setupForTarget(target, options);\n};\n\nconst initialisePage = async() => {\n const lang = document.querySelector('html').lang;\n\n const [tinyMCE, langData] = await Promise.all([getTinyMCE(), fetchLanguage(lang)]);\n tinyMCE.addI18n(lang, langData);\n};\ninitialisePage();\n\nconst getPlugins = (options) => {\n if (options.plugins) {\n return options.plugins;\n }\n\n if (defaultOptions.plugins) {\n return defaultOptions.plugins;\n }\n\n return {};\n};\n\nconst getStandardConfig = (target, tinyMCE, options, plugins) => {\n const lang = document.querySelector('html').lang;\n return {\n base_url: baseUrl,\n\n // Set the editor target.\n // https://www.tiny.cloud/docs/tinymce/6/editor-important-options/#target\n target,\n\n // Set the language.\n // https://www.tiny.cloud/docs/tinymce/6/ui-localization/#language\n language: lang,\n\n // Load the editor stylesheet into the editor iframe.\n // https://www.tiny.cloud/docs/tinymce/6/add-css-options/\n content_css: [\n options.css,\n ],\n\n // Do not convert URLs to relative URLs.\n // https://www.tiny.cloud/docs/tinymce/6/url-handling/#convert_urls\n // eslint-disable-next-line camelcase\n convert_urls: false,\n\n // Enabled 'advanced' a11y options.\n // This includes allowing role=\"presentation\" from the image uploader.\n // https://www.tiny.cloud/docs/tinymce/6/accessibility/\n // eslint-disable-next-line camelcase\n a11y_advanced_options: true,\n\n // Toolbar configuration.\n // https://www.tiny.cloud/docs/tinymce/6/toolbar-configuration-options/\n // TODO: Move this configuration to a passed-in option.\n // eslint-disable-next-line camelcase\n toolbar_mode: 'sliding',\n toolbar: [\n {\n name: 'history',\n items: [\n 'undo',\n 'redo'\n ]\n },\n {\n name: 'styles',\n items: ['styles']\n },\n {\n name: 'formatting',\n items: [\n 'bold',\n 'italic'\n ]\n },\n {\n name: 'alignment',\n items: [\n 'alignleft',\n 'aligncenter',\n 'alignright',\n 'alignjustify'\n ]\n },\n {\n name: 'indentation',\n items: [\n 'outdent',\n 'indent'\n ]\n },\n {\n name: 'comments',\n items: ['addcomment']\n },\n ],\n\n // Menu configuration.\n // https://www.tiny.cloud/docs/tinymce/6/menus-configuration-options/\n // TODO: Move this configuration to a passed-in option.\n menu: {\n },\n\n // The list of plugins to include in the instance.\n // https://www.tiny.cloud/docs/tinymce/6/editor-important-options/#plugins\n plugins: [\n ...plugins,\n ],\n\n // TODO Add mobile configuration.\n // Mobile configuration.\n // https://www.tiny.cloud/docs/tinymce/6/tinymce-for-mobile/\n // This will include mobile-specific toolbar, and menu options.\n\n // Skins\n skin: 'oxide',\n\n // Remove the \"Upgrade\" link for Tiny.\n // https://www.tiny.cloud/docs/tinymce/6/editor-premium-upgrade-promotion/\n promotion: false,\n };\n};\n\n/**\n * Set up TinyMCE for the HTML Element.\n *\n * @param {HTMLElement} target\n * @param {Object} options The editor plugin configuration\n * @return {Promise} The TinyMCE instance\n */\nexport const setupForTarget = async(target, options = {}) => {\n const instance = getInstanceForElement(target);\n if (instance) {\n return Promise.resolve(instance);\n }\n\n const pendingPromise = new Pending('editor_tiny/editor:setupForTarget');\n\n const plugins = getPlugins(options);\n const [tinyMCE, pluginValues] = await Promise.all([\n getTinyMCE(),\n importPluginList(Object.keys(plugins)),\n ]);\n const {pluginNames, pluginConfig} = pluginValues;\n\n const instanceConfig = getStandardConfig(target, tinyMCE, options, pluginNames);\n pluginConfig.forEach((pluginConfig) => {\n if (typeof pluginConfig.configure === 'function') {\n Object.assign(instanceConfig, pluginConfig.configure(instanceConfig));\n }\n });\n const [editor] = await tinyMCE.init(instanceConfig);\n\n // Store the editor instance in the instanceMap and register its removal to remove it.\n instanceMap.set(target, editor);\n editor.on('remove', ({target}) => {\n // Handle removal of the editor from the map on destruction.\n instanceMap.delete(target.targetElm);\n });\n\n // Store the Moodle-specific options in the TinyMCE instance.\n // TODO: See if there is a more appropriate location for this config.\n // TinyMCE does support custom configuration options in its EditorOptions but these must be registered and spec'd.\n editor.moodleOptions = options;\n\n pendingPromise.resolve();\n return editor;\n};\n\nexport const configureDefaultEditor = (options = {}) => {\n defaultOptions = options;\n};\n"],"names":["instanceMap","Map","defaultOptions","importPluginList","async","pluginHandlers","Promise","all","pluginList","map","pluginPath","indexOf","resolve","pluginNames","pluginConfig","Array","isArray","filter","value","entries","elementId","getInstanceForElement","document","getElementById","element","instance","get","removed","remove","_ref","options","target","setupForTarget","lang","querySelector","tinyMCE","langData","language","fetch","M","cfg","wwwroot","langrev","then","response","json","addI18n","initialisePage","getPlugins","plugins","getStandardConfig","base_url","baseUrl","content_css","css","convert_urls","a11y_advanced_options","toolbar_mode","toolbar","name","items","menu","skin","promotion","pendingPromise","Pending","pluginValues","Object","keys","instanceConfig","forEach","configure","assign","editor","init","set","on","_ref2","delete","targetElm","moodleOptions"],"mappings":"4iBAgCMA,YAAc,IAAIC,QAMpBC,eAAiB,SAQfC,iBAAmBC,MAAAA,mBACfC,qBAAuBC,QAAQC,IAAIC,WAAWC,KAAIC,aACnB,IAA7BA,WAAWC,QAAQ,KAEZL,QAAQM,QAAQF,4NAGbA,4WAAAA,gBAGZG,YAAcR,eAAeI,KAAKK,cACR,iBAAjBA,aACAA,aAEPC,MAAMC,QAAQF,cACPA,aAAa,GAEjB,OACRG,QAAQC,OAAUA,cASd,CACHL,YAAAA,YACAC,aATiBT,eAAeI,KAAKK,cACjCC,MAAMC,QAAQF,cACPA,aAAa,GAEjB,OACRG,QAAQC,OAAUA,mCAYM,IAAM,IAAIjB,IAAID,YAAYmB,4CAQlBC,WAAaC,sBAAsBC,SAASC,eAAeH,kBAQrFC,sBAAwBG,gBAC3BC,SAAWzB,YAAY0B,IAAIF,aAC7BC,WAAYA,SAASE,eAIlBF,SAHHzB,YAAY4B,OAAOJ,0FAcMK,WAACT,UAACA,UAADU,QAAYA,oBACpCC,OAAST,SAASC,eAAeH,kBAChCY,eAAeD,OAAQD,UAGX1B,iBACb6B,KAAOX,SAASY,cAAc,QAAQD,MAErCE,QAASC,gBAAkB9B,QAAQC,IAAI,EAAC,yBA7C5B8B,SA6CwDJ,KA7C3CK,gBAC7BC,EAAEC,IAAIC,6CAAoCF,EAAEC,IAAIE,oBAAWL,WAChEM,MAAKC,UAAYA,SAASC,YAFLR,IAAAA,SA8CnBF,QAAQW,QAAQb,KAAMG,WAE1BW,SAEMC,WAAclB,SACZA,QAAQmB,QACDnB,QAAQmB,QAGf/C,eAAe+C,QACR/C,eAAe+C,QAGnB,GAGLC,kBAAoB,CAACnB,OAAQI,QAASL,QAASmB,iBAC3ChB,KAAOX,SAASY,cAAc,QAAQD,WACrC,CACHkB,SAAUC,gBAIVrB,OAAAA,OAIAM,SAAUJ,KAIVoB,YAAa,CACTvB,QAAQwB,KAMZC,cAAc,EAMdC,uBAAuB,EAMvBC,aAAc,UACdC,QAAS,CACL,CACIC,KAAM,UACNC,MAAO,CACH,OACA,SAGR,CACID,KAAM,SACNC,MAAO,CAAC,WAEZ,CACID,KAAM,aACNC,MAAO,CACH,OACA,WAGR,CACID,KAAM,YACNC,MAAO,CACH,YACA,cACA,aACA,iBAGR,CACID,KAAM,cACNC,MAAO,CACH,UACA,WAGR,CACID,KAAM,WACNC,MAAO,CAAC,gBAOhBC,KAAM,GAKNZ,QAAS,IACFA,SASPa,KAAM,QAINC,WAAW,IAWN/B,eAAiB5B,eAAM2B,YAAQD,+DAAU,SAC5CL,SAAWJ,sBAAsBU,WACnCN,gBACOnB,QAAQM,QAAQa,gBAGrBuC,eAAiB,IAAIC,iBAAQ,qCAE7BhB,QAAUD,WAAWlB,UACpBK,QAAS+B,oBAAsB5D,QAAQC,IAAI,EAC9C,wBACAJ,iBAAiBgE,OAAOC,KAAKnB,aAE3BpC,YAACA,YAADC,aAAcA,cAAgBoD,aAE9BG,eAAiBnB,kBAAkBnB,OAAQI,EAASL,QAASjB,aACnEC,aAAawD,SAASxD,eACoB,mBAA3BA,aAAayD,WACpBJ,OAAOK,OAAOH,eAAgBvD,aAAayD,UAAUF,0BAGtDI,cAAgBtC,QAAQuC,KAAKL,uBAGpCrE,YAAY2E,IAAI5C,OAAQ0C,QACxBA,OAAOG,GAAG,UAAUC,YAAC9C,OAACA,cAElB/B,YAAY8E,OAAO/C,OAAOgD,cAM9BN,OAAOO,cAAgBlD,QAEvBkC,eAAepD,UACR6D,+EAG2B,eAAC3C,+DAAU,GAC7C5B,eAAiB4B"} \ No newline at end of file +{"version":3,"file":"editor.min.js","sources":["../src/editor.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 * TinyMCE Editor Manager.\n *\n * @module editor_tiny/editor\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\nimport {getDefaultConfiguration} from './defaults';\nimport {getTinyMCE, baseUrl} from './loader';\nimport * as Options from './options';\n\n/**\n * Storage for the TinyMCE instances on the page.\n * @type {Map}\n */\nconst instanceMap = new Map();\n\n/**\n * The default editor configuration.\n * @type {Object}\n */\nlet defaultOptions = {};\n\n/**\n * Require the modules for the named set of TinyMCE plugins.\n *\n * @param {string[]} pluginList The list of plugins\n * @return {Promise[]} A matching set of Promises relating to the requested plugins\n */\nconst importPluginList = async(pluginList) => {\n // Fetch all of the plugins from the list of plugins.\n // If a plugin contains a '/' then it is assumed to be a Moodle AMD module to import.\n const pluginHandlers = await Promise.all(pluginList.map(pluginPath => {\n if (pluginPath.indexOf('/') === -1) {\n // A standard TinyMCE Plugin.\n return Promise.resolve(pluginPath);\n }\n\n return import(pluginPath);\n }));\n\n // Normalise the plugin data to a list of plugin names.\n // Two formats are supported:\n // - a string; and\n // - an array whose first element is the plugin name, and the second element is the plugin configuration.\n const pluginNames = pluginHandlers.map((pluginConfig) => {\n if (typeof pluginConfig === 'string') {\n return pluginConfig;\n }\n if (Array.isArray(pluginConfig)) {\n return pluginConfig[0];\n }\n return null;\n }).filter((value) => value);\n\n // Fetch the list of pluginConfig handlers.\n const pluginConfig = pluginHandlers.map((pluginConfig) => {\n if (Array.isArray(pluginConfig)) {\n return pluginConfig[1];\n }\n return null;\n }).filter((value) => value);\n\n return {\n pluginNames,\n pluginConfig,\n };\n};\n\n/**\n * Fetch the language data for the specified language.\n *\n * @param {string} language The language identifier\n * @returns {object}\n */\nconst fetchLanguage = (language) => fetch(\n `${M.cfg.wwwroot}/lib/editor/tiny/lang.php/${M.cfg.langrev}/${language}`\n).then(response => response.json());\n\n/**\n * Get a list of all Editors in a Map, keyed by the DOM Node that the Editor is associated with.\n *\n * @returns {Map}\n */\nexport const getAllInstances = () => new Map(instanceMap.entries());\n\n/**\n * Get the TinyMCE instance for the specified Node ID.\n *\n * @param {string} elementId\n * @returns {TinyMCE|undefined}\n */\nexport const getInstanceForElementId = elementId => getInstanceForElement(document.getElementById(elementId));\n\n/*\n * Get the TinyMCE instance for the specified HTMLElement.\n *\n * @param {HTMLElement} element\n * @returns {TinyMCE|undefined}\n */\nexport const getInstanceForElement = element => {\n const instance = instanceMap.get(element);\n if (instance && instance.removed) {\n instanceMap.remove(element);\n return undefined;\n }\n return instance;\n};\n\n/**\n * Set up TinyMCE for the selector at the specified HTML Node id.\n *\n * @param {object} config The configuration required to setup the editor\n * @param {string} config.elementId The HTML Node ID\n * @param {Object} config.options The editor plugin configuration\n * @return {Promise} The TinyMCE instance\n */\nexport const setupForElementId = ({elementId, options}) => {\n const target = document.getElementById(elementId);\n return setupForTarget(target, options);\n};\n\n/**\n * Initialise the page with standard TinyMCE requirements.\n *\n * Currently this includes the language taken from the HTML lang property.\n */\nconst initialisePage = async() => {\n const lang = document.querySelector('html').lang;\n\n const [tinyMCE, langData] = await Promise.all([getTinyMCE(), fetchLanguage(lang)]);\n tinyMCE.addI18n(lang, langData);\n};\ninitialisePage();\n\n/**\n * Get the list of plugins to load for the specified configuration.\n *\n * If the specified configuration does not include a plugin configuration, then return the default configuration.\n *\n * @param {object} options\n * @param {array} [options.plugins=null] The plugin list\n * @returns {object}\n */\nconst getPlugins = ({plugins = null} = {}) => {\n if (plugins) {\n return plugins;\n }\n\n if (defaultOptions.plugins) {\n return defaultOptions.plugins;\n }\n\n return {};\n};\n\n/**\n * Get the standard configuration for the specified options.\n *\n * @param {Node} target\n * @param {tinyMCE} tinyMCE\n * @param {object} options\n * @param {Array} plugins\n * @returns {object}\n */\nconst getStandardConfig = (target, tinyMCE, options, plugins) => {\n const lang = document.querySelector('html').lang;\n\n return Object.assign({}, getDefaultConfiguration(), {\n // eslint-disable-next-line camelcase\n base_url: baseUrl,\n\n // Set the editor target.\n // https://www.tiny.cloud/docs/tinymce/6/editor-important-options/#target\n target,\n\n // Set the language.\n // https://www.tiny.cloud/docs/tinymce/6/ui-localization/#language\n // eslint-disable-next-line camelcase\n language: lang,\n\n // Load the editor stylesheet into the editor iframe.\n // https://www.tiny.cloud/docs/tinymce/6/add-css-options/\n // eslint-disable-next-line camelcase\n content_css: [\n options.css,\n ],\n\n // Do not convert URLs to relative URLs.\n // https://www.tiny.cloud/docs/tinymce/6/url-handling/#convert_urls\n // eslint-disable-next-line camelcase\n convert_urls: false,\n\n // Enabled 'advanced' a11y options.\n // This includes allowing role=\"presentation\" from the image uploader.\n // https://www.tiny.cloud/docs/tinymce/6/accessibility/\n // eslint-disable-next-line camelcase\n a11y_advanced_options: true,\n\n // The list of plugins to include in the instance.\n // https://www.tiny.cloud/docs/tinymce/6/editor-important-options/#plugins\n plugins: [\n ...plugins,\n ],\n\n // TODO Add mobile configuration.\n // Mobile configuration.\n // https://www.tiny.cloud/docs/tinymce/6/tinymce-for-mobile/\n // This will include mobile-specific toolbar, and menu options.\n\n // Skins\n skin: 'oxide',\n\n // Remove the \"Upgrade\" link for Tiny.\n // https://www.tiny.cloud/docs/tinymce/6/editor-premium-upgrade-promotion/\n promotion: false,\n\n setup: (editor) => {\n Options.register(editor, options);\n },\n });\n};\n\n/**\n * Set up TinyMCE for the HTML Element.\n *\n * @param {HTMLElement} target\n * @param {Object} [options={}] The editor plugin configuration\n * @return {Promise} The TinyMCE instance\n */\nexport const setupForTarget = async(target, options = {}) => {\n const instance = getInstanceForElement(target);\n if (instance) {\n return Promise.resolve(instance);\n }\n\n // Register a new pending promise to ensure that Behat waits for the editor setup to complete before continuing.\n const pendingPromise = new Pending('editor_tiny/editor:setupForTarget');\n\n // Get the list of plugins.\n const plugins = getPlugins(options);\n\n // Fetch the tinyMCE API, and instantiate the plugins.\n const [tinyMCE, pluginValues] = await Promise.all([\n getTinyMCE(),\n importPluginList(Object.keys(plugins)),\n ]);\n const {pluginNames, pluginConfig} = pluginValues;\n\n // Allow plugins to modify the configuration.\n const instanceConfig = getStandardConfig(target, tinyMCE, options, pluginNames);\n pluginConfig.forEach((pluginConfig) => {\n if (typeof pluginConfig.configure === 'function') {\n Object.assign(instanceConfig, pluginConfig.configure(instanceConfig, options));\n }\n });\n Object.assign(instanceConfig, Options.getInitialPluginConfiguration(options));\n\n // Initialise the editor instance for the given configuration.\n const [editor] = await tinyMCE.init(instanceConfig);\n\n // Store the editor instance in the instanceMap and register its removal to remove it.\n instanceMap.set(target, editor);\n editor.on('remove', ({target}) => {\n // Handle removal of the editor from the map on destruction.\n instanceMap.delete(target.targetElm);\n });\n\n pendingPromise.resolve();\n return editor;\n};\n\n/**\n * Set the default editor configuration.\n *\n * This configuration is used when an editor is initialised without any configuration.\n *\n * @param {object} [options={}]\n */\nexport const configureDefaultEditor = (options = {}) => {\n defaultOptions = options;\n};\n"],"names":["instanceMap","Map","defaultOptions","importPluginList","async","pluginHandlers","Promise","all","pluginList","map","pluginPath","indexOf","resolve","pluginNames","pluginConfig","Array","isArray","filter","value","entries","elementId","getInstanceForElement","document","getElementById","element","instance","get","removed","remove","_ref","options","target","setupForTarget","lang","querySelector","tinyMCE","langData","language","fetch","M","cfg","wwwroot","langrev","then","response","json","addI18n","initialisePage","getPlugins","plugins","getStandardConfig","Object","assign","base_url","baseUrl","content_css","css","convert_urls","a11y_advanced_options","skin","promotion","setup","editor","Options","register","pendingPromise","Pending","pluginValues","keys","instanceConfig","forEach","configure","getInitialPluginConfiguration","init","set","on","_ref2","delete","targetElm"],"mappings":"wgDAgCMA,YAAc,IAAIC,QAMpBC,eAAiB,SAQfC,iBAAmBC,MAAAA,mBAGfC,qBAAuBC,QAAQC,IAAIC,WAAWC,KAAIC,aACnB,IAA7BA,WAAWC,QAAQ,KAEZL,QAAQM,QAAQF,4NAGbA,4WAAAA,gBAOZG,YAAcR,eAAeI,KAAKK,cACR,iBAAjBA,aACAA,aAEPC,MAAMC,QAAQF,cACPA,aAAa,GAEjB,OACRG,QAAQC,OAAUA,cAUd,CACHL,YAAAA,YACAC,aATiBT,eAAeI,KAAKK,cACjCC,MAAMC,QAAQF,cACPA,aAAa,GAEjB,OACRG,QAAQC,OAAUA,mCAuBM,IAAM,IAAIjB,IAAID,YAAYmB,4CAQlBC,WAAaC,sBAAsBC,SAASC,eAAeH,kBAQrFC,sBAAwBG,gBAC3BC,SAAWzB,YAAY0B,IAAIF,aAC7BC,WAAYA,SAASE,eAIlBF,SAHHzB,YAAY4B,OAAOJ,0FAcMK,WAACT,UAACA,UAADU,QAAYA,oBACpCC,OAAST,SAASC,eAAeH,kBAChCY,eAAeD,OAAQD,UAQX1B,iBACb6B,KAAOX,SAASY,cAAc,QAAQD,MAErCE,QAASC,gBAAkB9B,QAAQC,IAAI,EAAC,yBAvD5B8B,SAuDwDJ,KAvD3CK,gBAC7BC,EAAEC,IAAIC,6CAAoCF,EAAEC,IAAIE,oBAAWL,WAChEM,MAAKC,UAAYA,SAASC,YAFLR,IAAAA,SAwDnBF,QAAQW,QAAQb,KAAMG,WAE1BW,SAWMC,WAAa,eAACC,QAACA,QAAU,6DAAQ,UAC/BA,UAIA/C,eAAe+C,QACR/C,eAAe+C,QAGnB,KAYLC,kBAAoB,CAACnB,OAAQI,QAASL,QAASmB,iBAC3ChB,KAAOX,SAASY,cAAc,QAAQD,YAErCkB,OAAOC,OAAO,IAAI,uCAA2B,CAEhDC,SAAUC,gBAIVvB,OAAAA,OAKAM,SAAUJ,KAKVsB,YAAa,CACTzB,QAAQ0B,KAMZC,cAAc,EAMdC,uBAAuB,EAIvBT,QAAS,IACFA,SASPU,KAAM,QAINC,WAAW,EAEXC,MAAQC,SACJC,QAAQC,SAASF,OAAQhC,aAYxBE,eAAiB5B,eAAM2B,YAAQD,+DAAU,SAC5CL,SAAWJ,sBAAsBU,WACnCN,gBACOnB,QAAQM,QAAQa,gBAIrBwC,eAAiB,IAAIC,iBAAQ,qCAG7BjB,QAAUD,WAAWlB,UAGpBK,QAASgC,oBAAsB7D,QAAQC,IAAI,EAC9C,wBACAJ,iBAAiBgD,OAAOiB,KAAKnB,aAE3BpC,YAACA,YAADC,aAAcA,cAAgBqD,aAG9BE,eAAiBnB,kBAAkBnB,OAAQI,EAASL,QAASjB,aACnEC,aAAawD,SAASxD,eACoB,mBAA3BA,aAAayD,WACpBpB,OAAOC,OAAOiB,eAAgBvD,aAAayD,UAAUF,eAAgBvC,aAG7EqB,OAAOC,OAAOiB,eAAgBN,QAAQS,8BAA8B1C,gBAG7DgC,cAAgB3B,QAAQsC,KAAKJ,uBAGpCrE,YAAY0E,IAAI3C,OAAQ+B,QACxBA,OAAOa,GAAG,UAAUC,YAAC7C,OAACA,cAElB/B,YAAY6E,OAAO9C,OAAO+C,cAG9Bb,eAAerD,UACRkD,+EAU2B,eAAChC,+DAAU,GAC7C5B,eAAiB4B"} \ No newline at end of file diff --git a/lib/editor/tiny/amd/build/options.min.js b/lib/editor/tiny/amd/build/options.min.js new file mode 100644 index 00000000000..821fd17067c --- /dev/null +++ b/lib/editor/tiny/amd/build/options.min.js @@ -0,0 +1,3 @@ +define("editor_tiny/options",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=_exports.getPluginOptionName=_exports.getMoodleLang=_exports.getInitialPluginConfiguration=_exports.getFilepickers=_exports.getFilePicker=_exports.getDraftItemId=_exports.getCurrentLanguage=_exports.getContextId=void 0;_exports.register=(editor,options)=>{const registerOption=editor.options.register,setOption=editor.options.set;registerOption("moodle:contextid",{processor:"number",default:0}),setOption("moodle:contextid",options.context),registerOption("moodle:filepickers",{processor:"object",default:{}}),setOption("moodle:filepickers",options.filepicker),registerOption("moodle:draftitemid",{processor:"number",default:0}),setOption("moodle:draftitemid",options.draftitemid),registerOption("moodle:currentLanguage",{processor:"string",default:"en"}),setOption("moodle:currentLanguage",options.currentLanguage),registerOption("moodle:language",{processor:"object",default:{}}),setOption("moodle:language",options.language)};_exports.getContextId=editor=>editor.options.get("moodle:contextid");_exports.getDraftItemId=editor=>editor.options.get("moodle:draftitemid");const getFilepickers=editor=>editor.options.get("moodle:filepickers");_exports.getFilepickers=getFilepickers;_exports.getFilePicker=(editor,type)=>getFilepickers(editor)[type];_exports.getMoodleLang=editor=>editor.options.get("moodle:language");_exports.getCurrentLanguage=editor=>editor.options.get("moodle:currentLanguage");_exports.getInitialPluginConfiguration=options=>{const config={};return Object.entries(options.plugins).forEach((_ref=>{var _pluginConfig$config;let[pluginName,pluginConfig]=_ref;Object.entries(null!==(_pluginConfig$config=pluginConfig.config)&&void 0!==_pluginConfig$config?_pluginConfig$config:{}).forEach((_ref2=>{let[optionName,value]=_ref2;config[getPluginOptionName(pluginName,optionName)]=value}))})),config};const getPluginOptionName=(pluginName,optionName)=>"".concat(pluginName,":").concat(optionName);_exports.getPluginOptionName=getPluginOptionName})); + +//# sourceMappingURL=options.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/amd/build/options.min.js.map b/lib/editor/tiny/amd/build/options.min.js.map new file mode 100644 index 00000000000..64c1668b759 --- /dev/null +++ b/lib/editor/tiny/amd/build/options.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"options.min.js","sources":["../src/options.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 * Option helper for TinyMCE Editor Manager.\n *\n * @module editor_tiny/options\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst optionContextId = 'moodle:contextid';\nconst optionDraftItemId = 'moodle:draftitemid';\nconst filePickers = 'moodle:filepickers';\nconst optionsMoodleLang = 'moodle:language';\nconst currentLanguage = 'moodle:currentLanguage';\n\nexport const register = (editor, options) => {\n const registerOption = editor.options.register;\n const setOption = editor.options.set;\n\n registerOption(optionContextId, {\n processor: 'number',\n \"default\": 0,\n });\n setOption(optionContextId, options.context);\n\n registerOption(filePickers, {\n processor: 'object',\n \"default\": {},\n });\n setOption(filePickers, options.filepicker);\n\n registerOption(optionDraftItemId, {\n processor: 'number',\n \"default\": 0,\n });\n setOption(optionDraftItemId, options.draftitemid);\n\n registerOption(currentLanguage, {\n processor: 'string',\n \"default\": 'en',\n });\n setOption(currentLanguage, options.currentLanguage);\n\n // This is primarily used by the media plugin, but it may be re-used elsewhere so is included here as it is large.\n registerOption(optionsMoodleLang, {\n processor: 'object',\n \"default\": {},\n });\n setOption(optionsMoodleLang, options.language);\n};\n\nexport const getContextId = (editor) => editor.options.get(optionContextId);\nexport const getDraftItemId = (editor) => editor.options.get(optionDraftItemId);\nexport const getFilepickers = (editor) => editor.options.get(filePickers);\nexport const getFilePicker = (editor, type) => getFilepickers(editor)[type];\nexport const getMoodleLang = (editor) => editor.options.get(optionsMoodleLang);\nexport const getCurrentLanguage = (editor) => editor.options.get(currentLanguage);\n\n/**\n * Get a set of namespaced options for all defined plugins.\n *\n * @param {object} options\n * @returns {object}\n */\nexport const getInitialPluginConfiguration = (options) => {\n const config = {};\n\n Object.entries(options.plugins).forEach(([pluginName, pluginConfig]) => {\n const values = Object.entries(pluginConfig.config ?? {});\n values.forEach(([optionName, value]) => {\n config[getPluginOptionName(pluginName, optionName)] = value;\n });\n });\n\n return config;\n};\n\n/**\n * Get the namespaced option name for a plugin.\n *\n * @param {string} pluginName\n * @param {string} optionName\n * @returns {string}\n */\nexport const getPluginOptionName = (pluginName, optionName) => `${pluginName}:${optionName}`;\n"],"names":["editor","options","registerOption","register","setOption","set","processor","context","filepicker","draftitemid","currentLanguage","language","get","getFilepickers","type","config","Object","entries","plugins","forEach","_ref","pluginName","pluginConfig","_ref2","optionName","value","getPluginOptionName"],"mappings":"oXA6BwB,CAACA,OAAQC,iBACvBC,eAAiBF,OAAOC,QAAQE,SAChCC,UAAYJ,OAAOC,QAAQI,IAEjCH,eAVoB,mBAUY,CAC5BI,UAAW,iBACA,IAEfF,UAdoB,mBAcOH,QAAQM,SAEnCL,eAdgB,qBAcY,CACxBI,UAAW,iBACA,KAEfF,UAlBgB,qBAkBOH,QAAQO,YAE/BN,eArBsB,qBAqBY,CAC9BI,UAAW,iBACA,IAEfF,UAzBsB,qBAyBOH,QAAQQ,aAErCP,eAxBoB,yBAwBY,CAC5BI,UAAW,iBACA,OAEfF,UA5BoB,yBA4BOH,QAAQS,iBAGnCR,eAhCsB,kBAgCY,CAC9BI,UAAW,iBACA,KAEfF,UApCsB,kBAoCOH,QAAQU,iCAGZX,QAAWA,OAAOC,QAAQW,IA1C/B,4CA2COZ,QAAWA,OAAOC,QAAQW,IA1C/B,4BA2CbC,eAAkBb,QAAWA,OAAOC,QAAQW,IA1CrC,oFA2CS,CAACZ,OAAQc,OAASD,eAAeb,QAAQc,6BACxCd,QAAWA,OAAOC,QAAQW,IA3C9B,+CA4CSZ,QAAWA,OAAOC,QAAQW,IA3CrC,iEAmDsBX,gBACpCc,OAAS,UAEfC,OAAOC,QAAQhB,QAAQiB,SAASC,SAAQC,oCAAEC,WAAYC,mBACnCN,OAAOC,qCAAQK,aAAaP,4DAAU,IAC9CI,SAAQI,YAAEC,WAAYC,aACzBV,OAAOW,oBAAoBL,WAAYG,aAAeC,YAIvDV,cAUEW,oBAAsB,CAACL,WAAYG,uBAAkBH,uBAAcG"} \ No newline at end of file diff --git a/lib/editor/tiny/amd/build/utils.min.js b/lib/editor/tiny/amd/build/utils.min.js index ed1ae49a742..e2dd7333153 100644 --- a/lib/editor/tiny/amd/build/utils.min.js +++ b/lib/editor/tiny/amd/build/utils.min.js @@ -1,3 +1,3 @@ -define("editor_tiny/utils",["exports","core/templates"],(function(_exports,_templates){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getPluginConfiguration=_exports.getImagePath=_exports.getButtonImage=_exports.displayFilepicker=void 0;const getImagePath=function(identifier){let component=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"editor_tiny";return Promise.resolve(M.util.image_url(identifier,component))};_exports.getImagePath=getImagePath;_exports.getButtonImage=async function(identifier){let component=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"editor_tiny";return(0,_templates.renderForPromise)("editor_tiny/toolbar_button",{image:await getImagePath(identifier,component)})};_exports.getPluginConfiguration=(editor,plugin)=>{var _editor$moodleOptions;const config=null===(_editor$moodleOptions=editor.moodleOptions.plugins["tiny_".concat(plugin,"/plugin")])||void 0===_editor$moodleOptions?void 0:_editor$moodleOptions.config;return config||{}};_exports.displayFilepicker=(editor,filetype)=>new Promise(((resolve,reject)=>{if(editor.moodleOptions.filepicker[filetype]){const options={...editor.moodleOptions.filepicker[filetype],formcallback:resolve};M.core_filepicker.show(Y,options)}else reject("Unknown filetype ".concat(filetype))}))})); +define("editor_tiny/utils",["exports","core/templates","./options"],(function(_exports,_templates,_options){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getImagePath=_exports.getButtonImage=_exports.displayFilepicker=_exports.addToolbarButton=_exports.addMenubarItem=_exports.addContextmenuItem=void 0;const getImagePath=function(identifier){let component=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"editor_tiny";return Promise.resolve(M.util.image_url(identifier,component))};_exports.getImagePath=getImagePath;_exports.getButtonImage=async function(identifier){let component=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"editor_tiny";return(0,_templates.renderForPromise)("editor_tiny/toolbar_button",{image:await getImagePath(identifier,component)})};_exports.displayFilepicker=(editor,filetype)=>new Promise(((resolve,reject)=>{const configuration=(0,_options.getFilePicker)(editor,filetype);if(configuration){const options={...configuration,formcallback:resolve};M.core_filepicker.show(Y,options)}else reject("Unknown filetype ".concat(filetype))}));_exports.addToolbarButton=(toolbar,section,button)=>{if(!toolbar)return[{name:section,items:[button]}];return JSON.parse(JSON.stringify(toolbar)).map((item=>(item.name===section&&item.items.push(button),item)))};_exports.addMenubarItem=(menubar,section,menuitem)=>{if(!menubar){({})[section]={title:section,items:menuitem}}const mutatedMenubar=JSON.parse(JSON.stringify(menubar));return Array.from(Object.entries(mutatedMenubar)).forEach((_ref=>{let[name,menu]=_ref;name===section&&(menu.items="".concat(menu.items," ").concat(menuitem))})),mutatedMenubar};_exports.addContextmenuItem=function(contextmenu){const contextmenuItems=(null!=contextmenu?contextmenu:"").split(" ");for(var _len=arguments.length,menuitems=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++)menuitems[_key-1]=arguments[_key];return contextmenuItems.concat(menuitems).filter((item=>""!==item)).join(" ")}})); //# sourceMappingURL=utils.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/amd/build/utils.min.js.map b/lib/editor/tiny/amd/build/utils.min.js.map index 2d511e3ea06..9ac33bd0057 100644 --- a/lib/editor/tiny/amd/build/utils.min.js.map +++ b/lib/editor/tiny/amd/build/utils.min.js.map @@ -1 +1 @@ -{"version":3,"file":"utils.min.js","sources":["../src/utils.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\nimport {renderForPromise} from 'core/templates';\n\n/**\n * Get the image path for the specified image.\n *\n * @param {string} identifier The name of the image\n * @param {string} component The component name\n * @return {string} The image URL path\n */\nexport const getImagePath = (identifier, component = 'editor_tiny') => Promise.resolve(M.util.image_url(identifier, component));\n\nexport const getButtonImage = async(identifier, component = 'editor_tiny') => renderForPromise('editor_tiny/toolbar_button', {\n image: await getImagePath(identifier, component),\n});\n\n/**\n * Get the plugin configuration for the specified plugin.\n *\n * @param {TinyMCE} editor\n * @param {string} plugin\n * @returns {object} The plugin configuration\n */\nexport const getPluginConfiguration = (editor, plugin) => {\n const config = editor.moodleOptions.plugins[`tiny_${plugin}/plugin`]?.config;\n\n if (!config) {\n return {};\n }\n\n return config;\n};\n\n/**\n * Helper to display a filepicker and return a Promise.\n *\n * The Promise will resolve when a file is selected, or reject if the file type is not found.\n *\n * @param {TinyMCE} editor\n * @param {string} filetype\n * @returns {Promise} The file object returned by the filepicker\n */\nexport const displayFilepicker = (editor, filetype) => new Promise((resolve, reject) => {\n if (editor.moodleOptions.filepicker[filetype]) {\n const options = {\n ...editor.moodleOptions.filepicker[filetype],\n formcallback: resolve,\n };\n M.core_filepicker.show(Y, options);\n return;\n }\n reject(`Unknown filetype ${filetype}`);\n});\n"],"names":["getImagePath","identifier","component","Promise","resolve","M","util","image_url","async","image","editor","plugin","config","moodleOptions","plugins","_editor$moodleOptions","filetype","reject","filepicker","options","formcallback","core_filepicker","show","Y"],"mappings":"qQAwBaA,aAAe,SAACC,gBAAYC,iEAAY,qBAAkBC,QAAQC,QAAQC,EAAEC,KAAKC,UAAUN,WAAYC,wEAEtFM,eAAMP,gBAAYC,iEAAY,qBAAkB,+BAAiB,6BAA8B,CACzHO,YAAaT,aAAaC,WAAYC,8CAUJ,CAACQ,OAAQC,0CACrCC,qCAASF,OAAOG,cAAcC,uBAAgBH,2DAArCI,sBAAuDH,cAEjEA,QACM,+BAekB,CAACF,OAAQM,WAAa,IAAIb,SAAQ,CAACC,QAASa,aACrEP,OAAOG,cAAcK,WAAWF,iBAC1BG,QAAU,IACTT,OAAOG,cAAcK,WAAWF,UACnCI,aAAchB,SAElBC,EAAEgB,gBAAgBC,KAAKC,EAAGJ,cAG9BF,kCAA2BD"} \ No newline at end of file +{"version":3,"file":"utils.min.js","sources":["../src/utils.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\nimport {renderForPromise} from 'core/templates';\nimport {getFilePicker} from './options';\n\n/**\n * Get the image path for the specified image.\n *\n * @param {string} identifier The name of the image\n * @param {string} component The component name\n * @return {string} The image URL path\n */\nexport const getImagePath = (identifier, component = 'editor_tiny') => Promise.resolve(M.util.image_url(identifier, component));\n\nexport const getButtonImage = async(identifier, component = 'editor_tiny') => renderForPromise('editor_tiny/toolbar_button', {\n image: await getImagePath(identifier, component),\n});\n\n/**\n * Helper to display a filepicker and return a Promise.\n *\n * The Promise will resolve when a file is selected, or reject if the file type is not found.\n *\n * @param {TinyMCE} editor\n * @param {string} filetype\n * @returns {Promise} The file object returned by the filepicker\n */\nexport const displayFilepicker = (editor, filetype) => new Promise((resolve, reject) => {\n const configuration = getFilePicker(editor, filetype);\n if (configuration) {\n const options = {\n ...configuration,\n formcallback: resolve,\n };\n M.core_filepicker.show(Y, options);\n return;\n }\n reject(`Unknown filetype ${filetype}`);\n});\n\n/**\n * Given a TinyMCE Toolbar configuration, add the specified button to the named section.\n *\n * @param {object} toolbar\n * @param {string} section\n * @param {string} button\n * @returns {object} The toolbar configuration\n */\nexport const addToolbarButton = (toolbar, section, button) => {\n if (!toolbar) {\n return [{\n name: section,\n items: [button],\n }];\n }\n\n const mutatedToolbar = JSON.parse(JSON.stringify(toolbar));\n return mutatedToolbar.map((item) => {\n if (item.name === section) {\n item.items.push(button);\n }\n\n return item;\n });\n};\n\n/**\n * Given a TinyMCE Menubar configuration, add the specified button to the named section.\n *\n * @param {object} menubar\n * @param {string} section\n * @param {string} menuitem\n * @returns {object}\n */\nexport const addMenubarItem = (menubar, section, menuitem) => {\n if (!menubar) {\n const emptyMenubar = {};\n emptyMenubar[section] = {\n title: section,\n items: menuitem,\n };\n }\n\n const mutatedMenubar = JSON.parse(JSON.stringify(menubar));\n Array.from(Object.entries(mutatedMenubar)).forEach(([name, menu]) => {\n if (name === section) {\n menu.items = `${menu.items} ${menuitem}`;\n }\n });\n\n return mutatedMenubar;\n};\n\n/**\n * Given a TinyMCE contextmenu configuration, add the specified button to the end.\n *\n * @param {string} contextmenu\n * @param {string[]} menuitems\n * @returns {string}\n */\nexport const addContextmenuItem = (contextmenu, ...menuitems) => {\n const contextmenuItems = (contextmenu ?? '').split(' ');\n\n return contextmenuItems\n .concat(menuitems)\n .filter((item) => item !== '')\n .join(' ');\n};\n"],"names":["getImagePath","identifier","component","Promise","resolve","M","util","image_url","async","image","editor","filetype","reject","configuration","options","formcallback","core_filepicker","show","Y","toolbar","section","button","name","items","JSON","parse","stringify","map","item","push","menubar","menuitem","title","mutatedMenubar","Array","from","Object","entries","forEach","_ref","menu","contextmenu","contextmenuItems","split","menuitems","concat","filter","join"],"mappings":"wUAyBaA,aAAe,SAACC,gBAAYC,iEAAY,qBAAkBC,QAAQC,QAAQC,EAAEC,KAAKC,UAAUN,WAAYC,wEAEtFM,eAAMP,gBAAYC,iEAAY,qBAAkB,+BAAiB,6BAA8B,CACzHO,YAAaT,aAAaC,WAAYC,yCAYT,CAACQ,OAAQC,WAAa,IAAIR,SAAQ,CAACC,QAASQ,gBACnEC,eAAgB,0BAAcH,OAAQC,aACxCE,qBACMC,QAAU,IACTD,cACHE,aAAcX,SAElBC,EAAEW,gBAAgBC,KAAKC,EAAGJ,cAG9BF,kCAA2BD,wCAWC,CAACQ,QAASC,QAASC,cAC1CF,cACM,CAAC,CACJG,KAAMF,QACNG,MAAO,CAACF,iBAIOG,KAAKC,MAAMD,KAAKE,UAAUP,UAC3BQ,KAAKC,OACnBA,KAAKN,OAASF,SACdQ,KAAKL,MAAMM,KAAKR,QAGbO,iCAYe,CAACE,QAASV,QAASW,gBACxCD,QAAS,EACW,IACRV,SAAW,CACpBY,MAAOZ,QACPG,MAAOQ,gBAITE,eAAiBT,KAAKC,MAAMD,KAAKE,UAAUI,iBACjDI,MAAMC,KAAKC,OAAOC,QAAQJ,iBAAiBK,SAAQC,WAAEjB,KAAMkB,WACnDlB,OAASF,UACToB,KAAKjB,gBAAWiB,KAAKjB,kBAASQ,cAI/BE,4CAUuB,SAACQ,mBACzBC,kBAAoBD,MAAAA,YAAAA,YAAe,IAAIE,MAAM,mCADJC,6DAAAA,yCAGxCF,iBACFG,OAAOD,WACPE,QAAQlB,MAAkB,KAATA,OACjBmB,KAAK"} \ No newline at end of file diff --git a/lib/editor/tiny/amd/src/defaults.js b/lib/editor/tiny/amd/src/defaults.js new file mode 100644 index 00000000000..0bee358221e --- /dev/null +++ b/lib/editor/tiny/amd/src/defaults.js @@ -0,0 +1,177 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/* eslint-disable max-len, */ + +/** + * TinyMCE Editor Upstream defaults. + * + * @module editor_tiny/editor + * @copyright 2022 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * The upstream defaults for the TinyMCE Menu. + * + * This value is defined in the TinyMCE documentation, but not exported anywhere useful. + * https://www.tiny.cloud/docs/tinymce/6/menus-configuration-options/#menu + * + * @returns {Object} + */ +export const getDefaultMenu = () => { + return { + file: {title: 'File', items: 'newdocument restoredraft | preview | export print | deleteallconversations'}, + edit: {title: 'Edit', items: 'undo redo | cut copy paste pastetext | selectall | searchreplace'}, + view: {title: 'View', items: 'code | visualaid visualchars visualblocks | spellchecker | preview fullscreen | showcomments'}, + insert: {title: 'Insert', items: 'image link media addcomment pageembed template codesample inserttable | charmap emoticons hr | pagebreak nonbreaking anchor tableofcontents | insertdatetime'}, + format: {title: 'Format', items: 'bold italic underline strikethrough superscript subscript codeformat | styles blocks fontfamily fontsize align lineheight | forecolor backcolor | language | removeformat'}, + tools: {title: 'Tools', items: 'spellchecker spellcheckerlanguage | a11ycheck code wordcount'}, + table: {title: 'Table', items: 'inserttable | cell row column | advtablesort | tableprops deletetable'}, + help: {title: 'Help', items: 'help'} + }; +}; + +/** + * The default toolbar configuration to use. + * + * This is based upon the default value used if no toolbar is specified. + * + * https://www.tiny.cloud/docs/tinymce/6/menus-configuration-options/#menu + * + * @returns {Object} + */ +export const getDefaultToolbar = () => { + return [ + { + name: 'content', + items: [], + }, + { + name: 'styles', + items: ['styles'] + }, + { + name: 'formatting', + items: [ + 'bold', + 'italic' + ] + }, + { + name: 'history', + items: [ + 'undo', + 'redo' + ] + }, + { + name: 'alignment', + items: [ + 'alignleft', + 'aligncenter', + 'alignright', + 'alignjustify' + ] + }, + { + name: 'indentation', + items: [ + 'outdent', + 'indent' + ] + }, + { + name: 'comments', + items: ['addcomment'] + }, + ]; +}; + +/** + * The default quickbars_insert_toolbar configuration to use. + * + * This is based upon the default value used if no toolbar is specified. + * + * https://www.tiny.cloud/docs/tinymce/6/quickbars/#quickbars_selection_toolbar + * + * @returns {string} + */ +export const getDefaultQuickbarsSelectionToolbar = () => 'bold italic | quicklink h2 h3 blockquote'; + +/** + * The default quickbars_insert_toolbar configuration to use. + * + * This is based upon the default value used if no toolbar is specified. + * + * https://www.tiny.cloud/docs/tinymce/6/quickbars/#quickbars_insert_toolbar + * + * @returns {string} + */ +export const getDefaultQuickbarsInsertToolbar = () => 'quickimage quicktable'; + +/** + * The default quickbars_insert_toolbar configuration to use. + * + * This is based upon the default value used if no toolbar is specified. + * + * https://www.tiny.cloud/docs/tinymce/6/quickbars/#quickbars_image_toolbar + * + * @returns {string} + */ +export const getDefaultQuickbarsImageToolbar = () => 'alignleft aligncenter alignright'; + +/** + * Get the default configuration provided by TinyMCE. + * + * @returns {object} + */ +export const getDefaultConfiguration = () => ({ + // Toolbar configuration. + // https://www.tiny.cloud/docs/tinymce/6/toolbar-configuration-options/ + // TODO: Move this configuration to a passed-in option. + // eslint-disable-next-line camelcase + toolbar_mode: 'sliding', + toolbar: getDefaultToolbar(), + + // Quickbars Selection Toolbar configuration. + // https://www.tiny.cloud/docs/tinymce/6/quickbars/#quickbars_selection_toolbar + // eslint-disable-next-line camelcase + quickbars_selection_toolbar: getDefaultQuickbarsSelectionToolbar(), + + // Quickbars Select Toolbar configuration. + // https://www.tiny.cloud/docs/tinymce/6/quickbars/#quickbars_insert_toolbar + // eslint-disable-next-line camelcase + quickbars_insert_toolbar: getDefaultQuickbarsInsertToolbar(), + + // Quickbars Image Toolbar configuration. + // https://www.tiny.cloud/docs/tinymce/6/quickbars/#quickbars_image_toolbar + // eslint-disable-next-line camelcase + quickbars_image_toolbar: getDefaultQuickbarsImageToolbar(), + + + // Menu configuration. + // https://www.tiny.cloud/docs/tinymce/6/menus-configuration-options/ + // TODO: Move this configuration to a passed-in option. + menu: getDefaultMenu(), + + // TODO Add mobile configuration. + // Mobile configuration. + // https://www.tiny.cloud/docs/tinymce/6/tinymce-for-mobile/ + // This will include mobile-specific toolbar, and menu options. + + // Skins + skin: 'oxide', +}); diff --git a/lib/editor/tiny/amd/src/editor.js b/lib/editor/tiny/amd/src/editor.js index cce0fbf9bb7..99083d0eb0d 100644 --- a/lib/editor/tiny/amd/src/editor.js +++ b/lib/editor/tiny/amd/src/editor.js @@ -14,17 +14,17 @@ // along with Moodle. If not, see . /** - * Utility functions. + * TinyMCE Editor Manager. * * @module editor_tiny/editor * @copyright 2022 Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -import { - getTinyMCE, - baseUrl, -} from './loader'; + import Pending from 'core/pending'; +import {getDefaultConfiguration} from './defaults'; +import {getTinyMCE, baseUrl} from './loader'; +import * as Options from './options'; /** * Storage for the TinyMCE instances on the page. @@ -45,6 +45,8 @@ let defaultOptions = {}; * @return {Promise[]} A matching set of Promises relating to the requested plugins */ const importPluginList = async(pluginList) => { + // Fetch all of the plugins from the list of plugins. + // If a plugin contains a '/' then it is assumed to be a Moodle AMD module to import. const pluginHandlers = await Promise.all(pluginList.map(pluginPath => { if (pluginPath.indexOf('/') === -1) { // A standard TinyMCE Plugin. @@ -54,6 +56,10 @@ const importPluginList = async(pluginList) => { return import(pluginPath); })); + // Normalise the plugin data to a list of plugin names. + // Two formats are supported: + // - a string; and + // - an array whose first element is the plugin name, and the second element is the plugin configuration. const pluginNames = pluginHandlers.map((pluginConfig) => { if (typeof pluginConfig === 'string') { return pluginConfig; @@ -64,6 +70,7 @@ const importPluginList = async(pluginList) => { return null; }).filter((value) => value); + // Fetch the list of pluginConfig handlers. const pluginConfig = pluginHandlers.map((pluginConfig) => { if (Array.isArray(pluginConfig)) { return pluginConfig[1]; @@ -77,10 +84,21 @@ const importPluginList = async(pluginList) => { }; }; +/** + * Fetch the language data for the specified language. + * + * @param {string} language The language identifier + * @returns {object} + */ const fetchLanguage = (language) => fetch( `${M.cfg.wwwroot}/lib/editor/tiny/lang.php/${M.cfg.langrev}/${language}` ).then(response => response.json()); +/** + * Get a list of all Editors in a Map, keyed by the DOM Node that the Editor is associated with. + * + * @returns {Map} + */ export const getAllInstances = () => new Map(instanceMap.entries()); /** @@ -119,6 +137,11 @@ export const setupForElementId = ({elementId, options}) => { return setupForTarget(target, options); }; +/** + * Initialise the page with standard TinyMCE requirements. + * + * Currently this includes the language taken from the HTML lang property. + */ const initialisePage = async() => { const lang = document.querySelector('html').lang; @@ -127,9 +150,18 @@ const initialisePage = async() => { }; initialisePage(); -const getPlugins = (options) => { - if (options.plugins) { - return options.plugins; +/** + * Get the list of plugins to load for the specified configuration. + * + * If the specified configuration does not include a plugin configuration, then return the default configuration. + * + * @param {object} options + * @param {array} [options.plugins=null] The plugin list + * @returns {object} + */ +const getPlugins = ({plugins = null} = {}) => { + if (plugins) { + return plugins; } if (defaultOptions.plugins) { @@ -139,9 +171,20 @@ const getPlugins = (options) => { return {}; }; +/** + * Get the standard configuration for the specified options. + * + * @param {Node} target + * @param {tinyMCE} tinyMCE + * @param {object} options + * @param {Array} plugins + * @returns {object} + */ const getStandardConfig = (target, tinyMCE, options, plugins) => { const lang = document.querySelector('html').lang; - return { + + return Object.assign({}, getDefaultConfiguration(), { + // eslint-disable-next-line camelcase base_url: baseUrl, // Set the editor target. @@ -150,10 +193,12 @@ const getStandardConfig = (target, tinyMCE, options, plugins) => { // Set the language. // https://www.tiny.cloud/docs/tinymce/6/ui-localization/#language + // eslint-disable-next-line camelcase language: lang, // Load the editor stylesheet into the editor iframe. // https://www.tiny.cloud/docs/tinymce/6/add-css-options/ + // eslint-disable-next-line camelcase content_css: [ options.css, ], @@ -169,58 +214,6 @@ const getStandardConfig = (target, tinyMCE, options, plugins) => { // eslint-disable-next-line camelcase a11y_advanced_options: true, - // Toolbar configuration. - // https://www.tiny.cloud/docs/tinymce/6/toolbar-configuration-options/ - // TODO: Move this configuration to a passed-in option. - // eslint-disable-next-line camelcase - toolbar_mode: 'sliding', - toolbar: [ - { - name: 'history', - items: [ - 'undo', - 'redo' - ] - }, - { - name: 'styles', - items: ['styles'] - }, - { - name: 'formatting', - items: [ - 'bold', - 'italic' - ] - }, - { - name: 'alignment', - items: [ - 'alignleft', - 'aligncenter', - 'alignright', - 'alignjustify' - ] - }, - { - name: 'indentation', - items: [ - 'outdent', - 'indent' - ] - }, - { - name: 'comments', - items: ['addcomment'] - }, - ], - - // Menu configuration. - // https://www.tiny.cloud/docs/tinymce/6/menus-configuration-options/ - // TODO: Move this configuration to a passed-in option. - menu: { - }, - // The list of plugins to include in the instance. // https://www.tiny.cloud/docs/tinymce/6/editor-important-options/#plugins plugins: [ @@ -238,14 +231,18 @@ const getStandardConfig = (target, tinyMCE, options, plugins) => { // Remove the "Upgrade" link for Tiny. // https://www.tiny.cloud/docs/tinymce/6/editor-premium-upgrade-promotion/ promotion: false, - }; + + setup: (editor) => { + Options.register(editor, options); + }, + }); }; /** * Set up TinyMCE for the HTML Element. * * @param {HTMLElement} target - * @param {Object} options The editor plugin configuration + * @param {Object} [options={}] The editor plugin configuration * @return {Promise} The TinyMCE instance */ export const setupForTarget = async(target, options = {}) => { @@ -254,21 +251,29 @@ export const setupForTarget = async(target, options = {}) => { return Promise.resolve(instance); } + // Register a new pending promise to ensure that Behat waits for the editor setup to complete before continuing. const pendingPromise = new Pending('editor_tiny/editor:setupForTarget'); + // Get the list of plugins. const plugins = getPlugins(options); + + // Fetch the tinyMCE API, and instantiate the plugins. const [tinyMCE, pluginValues] = await Promise.all([ getTinyMCE(), importPluginList(Object.keys(plugins)), ]); const {pluginNames, pluginConfig} = pluginValues; + // Allow plugins to modify the configuration. const instanceConfig = getStandardConfig(target, tinyMCE, options, pluginNames); pluginConfig.forEach((pluginConfig) => { if (typeof pluginConfig.configure === 'function') { - Object.assign(instanceConfig, pluginConfig.configure(instanceConfig)); + Object.assign(instanceConfig, pluginConfig.configure(instanceConfig, options)); } }); + Object.assign(instanceConfig, Options.getInitialPluginConfiguration(options)); + + // Initialise the editor instance for the given configuration. const [editor] = await tinyMCE.init(instanceConfig); // Store the editor instance in the instanceMap and register its removal to remove it. @@ -278,15 +283,17 @@ export const setupForTarget = async(target, options = {}) => { instanceMap.delete(target.targetElm); }); - // Store the Moodle-specific options in the TinyMCE instance. - // TODO: See if there is a more appropriate location for this config. - // TinyMCE does support custom configuration options in its EditorOptions but these must be registered and spec'd. - editor.moodleOptions = options; - pendingPromise.resolve(); return editor; }; +/** + * Set the default editor configuration. + * + * This configuration is used when an editor is initialised without any configuration. + * + * @param {object} [options={}] + */ export const configureDefaultEditor = (options = {}) => { defaultOptions = options; }; diff --git a/lib/editor/tiny/amd/src/options.js b/lib/editor/tiny/amd/src/options.js new file mode 100644 index 00000000000..490aaa0f274 --- /dev/null +++ b/lib/editor/tiny/amd/src/options.js @@ -0,0 +1,99 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Option helper for TinyMCE Editor Manager. + * + * @module editor_tiny/options + * @copyright 2022 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +const optionContextId = 'moodle:contextid'; +const optionDraftItemId = 'moodle:draftitemid'; +const filePickers = 'moodle:filepickers'; +const optionsMoodleLang = 'moodle:language'; +const currentLanguage = 'moodle:currentLanguage'; + +export const register = (editor, options) => { + const registerOption = editor.options.register; + const setOption = editor.options.set; + + registerOption(optionContextId, { + processor: 'number', + "default": 0, + }); + setOption(optionContextId, options.context); + + registerOption(filePickers, { + processor: 'object', + "default": {}, + }); + setOption(filePickers, options.filepicker); + + registerOption(optionDraftItemId, { + processor: 'number', + "default": 0, + }); + setOption(optionDraftItemId, options.draftitemid); + + registerOption(currentLanguage, { + processor: 'string', + "default": 'en', + }); + setOption(currentLanguage, options.currentLanguage); + + // This is primarily used by the media plugin, but it may be re-used elsewhere so is included here as it is large. + registerOption(optionsMoodleLang, { + processor: 'object', + "default": {}, + }); + setOption(optionsMoodleLang, options.language); +}; + +export const getContextId = (editor) => editor.options.get(optionContextId); +export const getDraftItemId = (editor) => editor.options.get(optionDraftItemId); +export const getFilepickers = (editor) => editor.options.get(filePickers); +export const getFilePicker = (editor, type) => getFilepickers(editor)[type]; +export const getMoodleLang = (editor) => editor.options.get(optionsMoodleLang); +export const getCurrentLanguage = (editor) => editor.options.get(currentLanguage); + +/** + * Get a set of namespaced options for all defined plugins. + * + * @param {object} options + * @returns {object} + */ +export const getInitialPluginConfiguration = (options) => { + const config = {}; + + Object.entries(options.plugins).forEach(([pluginName, pluginConfig]) => { + const values = Object.entries(pluginConfig.config ?? {}); + values.forEach(([optionName, value]) => { + config[getPluginOptionName(pluginName, optionName)] = value; + }); + }); + + return config; +}; + +/** + * Get the namespaced option name for a plugin. + * + * @param {string} pluginName + * @param {string} optionName + * @returns {string} + */ +export const getPluginOptionName = (pluginName, optionName) => `${pluginName}:${optionName}`; diff --git a/lib/editor/tiny/amd/src/utils.js b/lib/editor/tiny/amd/src/utils.js index 9ec6cb69ed6..c4239e84a22 100644 --- a/lib/editor/tiny/amd/src/utils.js +++ b/lib/editor/tiny/amd/src/utils.js @@ -14,6 +14,7 @@ // along with Moodle. If not, see . import {renderForPromise} from 'core/templates'; +import {getFilePicker} from './options'; /** * Get the image path for the specified image. @@ -28,23 +29,6 @@ export const getButtonImage = async(identifier, component = 'editor_tiny') => re image: await getImagePath(identifier, component), }); -/** - * Get the plugin configuration for the specified plugin. - * - * @param {TinyMCE} editor - * @param {string} plugin - * @returns {object} The plugin configuration - */ -export const getPluginConfiguration = (editor, plugin) => { - const config = editor.moodleOptions.plugins[`tiny_${plugin}/plugin`]?.config; - - if (!config) { - return {}; - } - - return config; -}; - /** * Helper to display a filepicker and return a Promise. * @@ -55,9 +39,10 @@ export const getPluginConfiguration = (editor, plugin) => { * @returns {Promise} The file object returned by the filepicker */ export const displayFilepicker = (editor, filetype) => new Promise((resolve, reject) => { - if (editor.moodleOptions.filepicker[filetype]) { + const configuration = getFilePicker(editor, filetype); + if (configuration) { const options = { - ...editor.moodleOptions.filepicker[filetype], + ...configuration, formcallback: resolve, }; M.core_filepicker.show(Y, options); @@ -65,3 +50,72 @@ export const displayFilepicker = (editor, filetype) => new Promise((resolve, rej } reject(`Unknown filetype ${filetype}`); }); + +/** + * Given a TinyMCE Toolbar configuration, add the specified button to the named section. + * + * @param {object} toolbar + * @param {string} section + * @param {string} button + * @returns {object} The toolbar configuration + */ +export const addToolbarButton = (toolbar, section, button) => { + if (!toolbar) { + return [{ + name: section, + items: [button], + }]; + } + + const mutatedToolbar = JSON.parse(JSON.stringify(toolbar)); + return mutatedToolbar.map((item) => { + if (item.name === section) { + item.items.push(button); + } + + return item; + }); +}; + +/** + * Given a TinyMCE Menubar configuration, add the specified button to the named section. + * + * @param {object} menubar + * @param {string} section + * @param {string} menuitem + * @returns {object} + */ +export const addMenubarItem = (menubar, section, menuitem) => { + if (!menubar) { + const emptyMenubar = {}; + emptyMenubar[section] = { + title: section, + items: menuitem, + }; + } + + const mutatedMenubar = JSON.parse(JSON.stringify(menubar)); + Array.from(Object.entries(mutatedMenubar)).forEach(([name, menu]) => { + if (name === section) { + menu.items = `${menu.items} ${menuitem}`; + } + }); + + return mutatedMenubar; +}; + +/** + * Given a TinyMCE contextmenu configuration, add the specified button to the end. + * + * @param {string} contextmenu + * @param {string[]} menuitems + * @returns {string} + */ +export const addContextmenuItem = (contextmenu, ...menuitems) => { + const contextmenuItems = (contextmenu ?? '').split(' '); + + return contextmenuItems + .concat(menuitems) + .filter((item) => item !== '') + .join(' '); +}; diff --git a/lib/editor/tiny/classes/editor.php b/lib/editor/tiny/classes/editor.php index ac963468bee..81b66a1c47f 100644 --- a/lib/editor/tiny/classes/editor.php +++ b/lib/editor/tiny/classes/editor.php @@ -171,10 +171,28 @@ class editor extends \texteditor { // File picker options. 'filepicker' => $fpoptions, + 'currentLanguage' => current_language(), + + // Language options. + 'language' => [ + 'currentlang' => current_language(), + 'installed' => get_string_manager()->get_list_of_translations(true), + 'available' => get_string_manager()->get_list_of_languages() + ], + // Plugin configuration. - 'plugins' => $this->manager->get_plugin_configuration($context, $options, $fpoptions), + 'plugins' => $this->manager->get_plugin_configuration($context, $options, $fpoptions, $this), ]; + foreach ($fpoptions as $fp) { + // Guess the draftitemid for the editor. + // Note: This is the best we can do at the moment. + if (!empty($fp->itemid)) { + $config->draftitemid = $fp->itemid; + break; + } + } + $configoptions = json_encode(convert_to_array($config)); // Note: This is not ideal but the editor does not have control over any HTML output. diff --git a/lib/editor/tiny/classes/manager.php b/lib/editor/tiny/classes/manager.php index 65bade5fb5a..0681e5addd0 100644 --- a/lib/editor/tiny/classes/manager.php +++ b/lib/editor/tiny/classes/manager.php @@ -27,46 +27,62 @@ use context; */ class manager { + /** + * Get the configuration for all plugins. + * + * @param context $context The context that the editor is used within + * @param array $options The options passed in when requesting the editor + * @param array $fpoptions The filepicker options passed in when requesting the editor + * @param editor $editor The editor instance in which the plugin is initialised + */ public function get_plugin_configuration( context $context, array $options = [], - array $fpoptions = [] + array $fpoptions = [], + ?editor $editor = null ): array { $disabledplugins = $this->get_disabled_plugins(); + // Get the list of plugins. + // Note: Disabled plugins are already removed from this list. $plugins = $this->get_shipped_plugins(); // Fetch configuration for Moodle plugins. $moodleplugins = \core_component::get_plugin_list_with_class('tiny', 'plugininfo'); foreach ($moodleplugins as $plugin => $classname) { - if (in_array($plugin, $disabledplugins)) { + if (in_array($plugin, $disabledplugins) || in_array("{$plugin}/plugin", $disabledplugins)) { // Skip getting data for disabled plugins. continue; } if (!is_a($classname, plugin::class, true)) { + // Skip plugins that do not implement the plugin interface. + debugging("Plugin {$plugin} does not implement the plugin interface", DEBUG_DEVELOPER); continue; } - $plugininfo = $classname::get_plugin_info(); + if (!$classname::is_enabled($context, $options, $fpoptions, $editor)) { + // This plugin has disabled itself for some reason. + // This is typical for media plugins where there is no file storage. + continue; + } - $config = $classname::get_plugin_configuration_for_context( + // Get the plugin information, which includes the list of buttons, menu items, and configuration. + $plugininfo = $classname::get_plugin_info( $context, $options, - $fpoptions + $fpoptions, + $editor ); if (!empty($config)) { $plugininfo['config'] = $config; } + // We suffix the plugin name for Moodle plugins with /plugin to avoid conflicts with Tiny plugins. $plugins["{$plugin}/plugin"] = $plugininfo; } - $plugins = array_filter($plugins, function ($plugin) use ($disabledplugins) { - return !in_array($plugin, $disabledplugins); - }, ARRAY_FILTER_USE_KEY); - return $plugins; } diff --git a/lib/editor/tiny/classes/plugin.php b/lib/editor/tiny/classes/plugin.php index 754663a7ef8..678907eedb1 100644 --- a/lib/editor/tiny/classes/plugin.php +++ b/lib/editor/tiny/classes/plugin.php @@ -33,7 +33,39 @@ use context; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class plugin { - public static function get_plugin_info(): array { + /** + * Whether the plugin is enabled + * + * @param context $context The context that the editor is used within + * @param array $options The options passed in when requesting the editor + * @param array $fpoptions The filepicker options passed in when requesting the editor + * @param editor $editor The editor instance in which the plugin is initialised + * @return boolean + */ + public static function is_enabled( + context $context, + array $options, + array $fpoptions, + ?editor $editor = null + ): bool { + return true; + } + + /** + * Get the plugin information for the plugin. + * + * @param context $context The context that the editor is used within + * @param array $options The options passed in when requesting the editor + * @param array $fpoptions The filepicker options passed in when requesting the editor + * @param editor $editor The editor instance in which the plugin is initialised + * @return array + */ + final public static function get_plugin_info( + context $context, + array $options, + array $fpoptions, + ?editor $editor = null + ): array { $plugindata = []; if (is_a(static::class, plugin_with_buttons::class, true)) { @@ -44,18 +76,10 @@ abstract class plugin { $plugindata['menuitems'] = static::get_available_menuitems(); } - return $plugindata; - } - - public static function get_plugin_configuration_for_context( - context $context, - array $options, - array $fpoptions - ): array { if (is_a(static::class, plugin_with_configuration::class, true)) { - return static::get_plugin_configuration_for_context($context, $options, $fpoptions); + $plugindata['config'] = static::get_plugin_configuration_for_context($context, $options, $fpoptions, $editor); } - return []; + return $plugindata; } } diff --git a/lib/editor/tiny/classes/plugin_with_configuration.php b/lib/editor/tiny/classes/plugin_with_configuration.php index f5c78e83ab7..bc28e827bf8 100644 --- a/lib/editor/tiny/classes/plugin_with_configuration.php +++ b/lib/editor/tiny/classes/plugin_with_configuration.php @@ -32,11 +32,13 @@ interface plugin_with_configuration { * @param context $context The context that the editor is used within * @param array $options The options passed in when requesting the editor * @param array $fpoptions The filepicker options passed in when requesting the editor + * @param editor $editor The editor instance in which the plugin is initialised * @return array */ public static function get_plugin_configuration_for_context( context $context, array $options, - array $fpoptions + array $fpoptions, + ?editor $editor = null ): array; }