From d949243af7818b0ff06fa56c518f58d6d5950850 Mon Sep 17 00:00:00 2001 From: Kevin Percy Date: Thu, 27 Jul 2023 17:30:37 +0800 Subject: [PATCH 1/2] MDL-78436 gradebook: Require override to add feedback --- grade/edit/tree/grade_form.php | 1 + 1 file changed, 1 insertion(+) diff --git a/grade/edit/tree/grade_form.php b/grade/edit/tree/grade_form.php index 00e5296f337..c51576fd925 100644 --- a/grade/edit/tree/grade_form.php +++ b/grade/edit/tree/grade_form.php @@ -106,6 +106,7 @@ class edit_grade_form extends moodleform { $mform->addElement('editor', 'feedback', get_string('feedback', 'grades'), null, $feedbackoptions); $mform->addHelpButton('feedback', 'feedback', 'grades'); $mform->setType('text', PARAM_RAW); // to be cleaned before display, no XSS risk + $mform->disabledIf('feedback', 'overridden'); // hidden params $mform->addElement('hidden', 'oldgrade'); From 7db066a82351d4487e0d018bd04908711ded0dae Mon Sep 17 00:00:00 2001 From: Kevin Percy Date: Thu, 27 Jul 2023 17:33:19 +0800 Subject: [PATCH 2/2] MDL-78436 editor_tiny: mform disabledIf works with TinyMCE --- lib/editor/tiny/amd/build/editor.min.js | 2 +- lib/editor/tiny/amd/build/editor.min.js.map | 2 +- lib/editor/tiny/amd/build/utils.min.js | 2 +- lib/editor/tiny/amd/build/utils.min.js.map | 2 +- lib/editor/tiny/amd/src/editor.js | 8 +++++++- lib/editor/tiny/amd/src/utils.js | 14 ++++++++++++++ 6 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/editor/tiny/amd/build/editor.min.js b/lib/editor/tiny/amd/build/editor.min.js index 1cfb533c9ec..8bbff622338 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","jquery","core/pending","./defaults","./loader","./options","./utils"],(function(_exports,_jquery,_pending,_defaults,_loader,Options,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setupForTarget=_exports.setupForElementId=_exports.getInstanceForElementId=_exports.getInstanceForElement=_exports.getAllInstances=_exports.configureDefaultEditor=void 0,_jquery=_interopRequireDefault(_jquery),_pending=_interopRequireDefault(_pending),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)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}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,config=Object.assign({},(0,_defaults.getDefaultConfiguration)(),{base_url:_loader.baseUrl,target:target,min_height:175,height:target.clientHeight||"calc(".concat(target.rows," * ").concat(window.getComputedStyle(target).lineHeight||"22px",")"),language:lang,content_css:[options.css],convert_urls:!1,a11y_advanced_options:!0,extended_valid_elements:"script[*]",xss_sanitization:!1,quickbars_insert_toolbar:"",block_formats:"Paragraph=p; Heading 3=h3; Heading 4=h4; Heading 5=h5; Heading 6=h6;",plugins:[...plugins],skin:"oxide",promotion:!1,branding:options.branding,table_header_type:"sectionCells",setup:editor=>{Options.register(editor,options),editor.on("PreInit",(function(){this.contentWindow=this.iframeElement.contentWindow})),editor.on("init",(function(){(0,_utils.removeSubmenuItem)(editor,"align","tiny:justify")})),editor.on("PostRender",(function(){options.nestedmenu&&(editor=>{const container=editor.getContainer(),menuContainer=document.querySelector("body > .tox.tox-tinymce-aux");container.parentNode.appendChild(menuContainer)})(editor)})),editor.on("FullscreenStateChanged",(function(e){(params=>{params.open&&(document.querySelector(".tox-fullscreen").style.overflow="unset");const pageWithDrawers=document.querySelector("#page.drawers");pageWithDrawers&&(pageWithDrawers.style.overflow=params.open?"unset":"")})({open:e.state})})),editor.on("OpenWindow CloseWindow",(function(e){(params=>{if(params.browser.isSafari()&&!params.fsplugin.isFullscreen()){const pageWithDrawers=document.querySelector("#page.drawers");pageWithDrawers&&(pageWithDrawers.style.overflow=params.open?"unset":"")}})({browser:tinyMCE.Env.browser,fsplugin:editor.plugins.fullscreen,open:"openwindow"==e.type})}))}});return config.toolbar=(0,_utils.addToolbarSection)(config.toolbar,"content","formatting",!0),config.toolbar=(0,_utils.addToolbarButton)(config.toolbar,"content","link"),config.toolbar=(0,_utils.addToolbarSection)(config.toolbar,"directionality","alignment",!0),config.toolbar=(0,_utils.addToolbarButtons)(config.toolbar,"directionality",["ltr","rtl"]),config.toolbar=(0,_utils.removeToolbarButton)(config.toolbar,"alignment","alignjustify"),config},getEditorConfiguration=(target,tinyMCE,options,pluginValues)=>{const{pluginNames:pluginNames,pluginConfig:pluginConfig}=pluginValues,instanceConfig=getStandardConfig(target,tinyMCE,options,pluginNames);return instanceConfig.menu.file&&(instanceConfig.menu.file.items=""),instanceConfig.menu.format&&(instanceConfig.menu.format.items=instanceConfig.menu.format.items.replace(/forecolor ?/,"").replace(/backcolor ?/,"").replace(/fontfamily ?/,"").replace(/fontsize ?/,"").replace(/styles ?/,"").replaceAll(/\| *\|/g,"|")),instanceConfig.quickbars_selection_toolbar=instanceConfig.quickbars_selection_toolbar.replace("h2 h3","h3 h4 h5 h6"),pluginConfig.filter((pluginConfig=>"function"==typeof pluginConfig.configure)).forEach((pluginConfig=>{const pluginInstanceOverride=pluginConfig.configure(instanceConfig,options);Object.assign(instanceConfig,pluginInstanceOverride)})),Object.assign(instanceConfig,Options.getInitialPluginConfiguration(options)),instanceConfig},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))]);tinyMCE.get().filter((editor=>!editor.getElement().isConnected)).forEach((editor=>{editor.remove()}));const existingEditor=tinyMCE.EditorManager.get(target.id);if(existingEditor){if(existingEditor.getElement()===target)return pendingPromise.resolve(),Promise.resolve(existingEditor);throw pendingPromise.resolve(),new Error("TinyMCE instance already exists for different target with same ID")}const instanceConfig=getEditorConfiguration(target,tinyMCE,options,pluginValues),[editor]=await tinyMCE.init(instanceConfig);return instanceMap.set(target,editor),editor.on("remove",(_ref2=>{let{target:target}=_ref2;instanceMap.delete(target.targetElm)})),target.form&&(0,_jquery.default)(target.form).on("submit",(()=>{editor.save()})),editor.on("blur",(()=>{editor.save()})),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","jquery","core/pending","./defaults","./loader","./options","./utils"],(function(_exports,_jquery,_pending,_defaults,_loader,Options,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setupForTarget=_exports.setupForElementId=_exports.getInstanceForElementId=_exports.getInstanceForElement=_exports.getAllInstances=_exports.configureDefaultEditor=void 0,_jquery=_interopRequireDefault(_jquery),_pending=_interopRequireDefault(_pending),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)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}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,config=Object.assign({},(0,_defaults.getDefaultConfiguration)(),{base_url:_loader.baseUrl,target:target,min_height:175,height:target.clientHeight||"calc(".concat(target.rows," * ").concat(window.getComputedStyle(target).lineHeight||"22px",")"),language:lang,content_css:[options.css],convert_urls:!1,a11y_advanced_options:!0,extended_valid_elements:"script[*]",xss_sanitization:!1,quickbars_insert_toolbar:"",block_formats:"Paragraph=p; Heading 3=h3; Heading 4=h4; Heading 5=h5; Heading 6=h6;",plugins:[...plugins],skin:"oxide",promotion:!1,branding:options.branding,table_header_type:"sectionCells",setup:editor=>{Options.register(editor,options),editor.on("PreInit",(function(){this.contentWindow=this.iframeElement.contentWindow})),editor.on("init",(function(){(0,_utils.removeSubmenuItem)(editor,"align","tiny:justify")})),editor.on("PostRender",(function(){options.nestedmenu&&(editor=>{const container=editor.getContainer(),menuContainer=document.querySelector("body > .tox.tox-tinymce-aux");container.parentNode.appendChild(menuContainer)})(editor)})),editor.on("FullscreenStateChanged",(function(e){(params=>{params.open&&(document.querySelector(".tox-fullscreen").style.overflow="unset");const pageWithDrawers=document.querySelector("#page.drawers");pageWithDrawers&&(pageWithDrawers.style.overflow=params.open?"unset":"")})({open:e.state})})),editor.on("OpenWindow CloseWindow",(function(e){(params=>{if(params.browser.isSafari()&&!params.fsplugin.isFullscreen()){const pageWithDrawers=document.querySelector("#page.drawers");pageWithDrawers&&(pageWithDrawers.style.overflow=params.open?"unset":"")}})({browser:tinyMCE.Env.browser,fsplugin:editor.plugins.fullscreen,open:"openwindow"==e.type})})),target.addEventListener("form:editorUpdated",(function(){(0,_utils.updateEditorState)(editor,target)})),target.dispatchEvent(new Event("form:editorUpdated"))}});return config.toolbar=(0,_utils.addToolbarSection)(config.toolbar,"content","formatting",!0),config.toolbar=(0,_utils.addToolbarButton)(config.toolbar,"content","link"),config.toolbar=(0,_utils.addToolbarSection)(config.toolbar,"directionality","alignment",!0),config.toolbar=(0,_utils.addToolbarButtons)(config.toolbar,"directionality",["ltr","rtl"]),config.toolbar=(0,_utils.removeToolbarButton)(config.toolbar,"alignment","alignjustify"),config},getEditorConfiguration=(target,tinyMCE,options,pluginValues)=>{const{pluginNames:pluginNames,pluginConfig:pluginConfig}=pluginValues,instanceConfig=getStandardConfig(target,tinyMCE,options,pluginNames);return instanceConfig.menu.file&&(instanceConfig.menu.file.items=""),instanceConfig.menu.format&&(instanceConfig.menu.format.items=instanceConfig.menu.format.items.replace(/forecolor ?/,"").replace(/backcolor ?/,"").replace(/fontfamily ?/,"").replace(/fontsize ?/,"").replace(/styles ?/,"").replaceAll(/\| *\|/g,"|")),instanceConfig.quickbars_selection_toolbar=instanceConfig.quickbars_selection_toolbar.replace("h2 h3","h3 h4 h5 h6"),pluginConfig.filter((pluginConfig=>"function"==typeof pluginConfig.configure)).forEach((pluginConfig=>{const pluginInstanceOverride=pluginConfig.configure(instanceConfig,options);Object.assign(instanceConfig,pluginInstanceOverride)})),Object.assign(instanceConfig,Options.getInitialPluginConfiguration(options)),instanceConfig},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))]);tinyMCE.get().filter((editor=>!editor.getElement().isConnected)).forEach((editor=>{editor.remove()}));const existingEditor=tinyMCE.EditorManager.get(target.id);if(existingEditor){if(existingEditor.getElement()===target)return pendingPromise.resolve(),Promise.resolve(existingEditor);throw pendingPromise.resolve(),new Error("TinyMCE instance already exists for different target with same ID")}const instanceConfig=getEditorConfiguration(target,tinyMCE,options,pluginValues),[editor]=await tinyMCE.init(instanceConfig);return instanceMap.set(target,editor),editor.on("remove",(_ref2=>{let{target:target}=_ref2;instanceMap.delete(target.targetElm)})),target.form&&(0,_jquery.default)(target.form).on("submit",(()=>{editor.save()})),editor.on("blur",(()=>{editor.save()})),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 9d6d951c252..faf3099389d 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 * 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 jQuery from 'jquery';\nimport Pending from 'core/pending';\nimport {getDefaultConfiguration} from './defaults';\nimport {getTinyMCE, baseUrl} from './loader';\nimport * as Options from './options';\nimport {addToolbarButton, addToolbarButtons, addToolbarSection,\n removeToolbarButton, removeSubmenuItem} from './utils';\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 * Nest the dropdown menu inside the parent DOM.\n *\n * The TinyMCE menu has a significant issue with the Overflow style,\n * and the Boost theme heavily uses Overflow for drawer navigation.\n * Moving the menu container into the parent editor container makes it work correctly.\n *\n * @param {object} editor\n */\n const nestMenu = (editor) => {\n const container = editor.getContainer();\n const menuContainer = document.querySelector('body > .tox.tox-tinymce-aux');\n container.parentNode.appendChild(menuContainer);\n};\n\n/**\n * Fix the Tiny menu position if the editor is in fullscreen mode on the Boost theme.\n *\n * The boost theme makes the TinyMCE editor rendered in a scrollable container,\n * scrolling the editor’s container will cause TinyMCE UI elements to be detached from the anchor.\n * Therefore, to keep the tinyMCE menu in the correct position,\n * adjustments must be made on the page drawers style.\n *\n * @param {object} params\n * @param {Boolean} params.open True if editor in fullscreen mode, otherwise false.\n */\nconst fixMenuPositionIfInFullsreen = (params) => {\n if (params.open) {\n // Keep the menu remains visible and properly aligned.\n document.querySelector('.tox-fullscreen').style.overflow = 'unset';\n }\n\n const pageWithDrawers = document.querySelector('#page.drawers');\n if (pageWithDrawers) {\n pageWithDrawers.style.overflow = params.open ? \"unset\" : \"\";\n }\n};\n\n/**\n * Fix the dialogue window positioning issue of TinyMCE editor in Safari browsers.\n *\n * When using TinyMCE editor in Safari browsers, a problem may occur where the dialogue\n * windows (such as modal dialogs) overlap with page drawers due to a specific behavior\n * in Safari's rendering. This function addresses the issue by adjusting the CSS overflow\n * property of the page drawers, ensuring they do not obscure the dialogue windows.\n *\n * @param {object} params\n * @param {object} params.browser Browser environment.\n * @param {object} params.fsplugin Fullscreen plugin.\n * @param {Boolean} params.open True if the dialogue window opens, otherwise false.\n */\nconst fixDialoguePositionIfOpen = (params) => {\n // To avoid modification the existing overflow value that has been set fixMenuPositionIfInFullsreen(),\n // the fix only applied if the editor not in fullscreen mode.\n if (params.browser.isSafari() && !params.fsplugin.isFullscreen()) {\n const pageWithDrawers = document.querySelector('#page.drawers');\n if (pageWithDrawers) {\n pageWithDrawers.style.overflow = params.open ? \"unset\" : \"\";\n }\n }\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 const config = 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 // https://www.tiny.cloud/docs/tinymce/6/customize-ui/#set-maximum-and-minimum-heights-and-widths\n // Set the minimum height to the smallest height that we can fit the Menu bar, Tool bar, Status bar and the text area.\n // eslint-disable-next-line camelcase\n min_height: 175,\n\n // Base the height on the size of the text area. Account for lack of height value when the editor is initially hidden,\n // in which case use CSS calc() to approximate the same based on number of rows and target line height.\n height: target.clientHeight || `calc(${target.rows} * ${window.getComputedStyle(target).lineHeight || '22px'})`,\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 // Ensure that scripts are recognized as valid elements.\n // eslint-disable-next-line camelcase\n extended_valid_elements: 'script[*]',\n\n // Disable XSS Sanitisation.\n // We do this in PHP.\n // https://www.tiny.cloud/docs/tinymce/6/security/#turning-dompurify-off\n // Note: This feature has been backported from TinyMCE 6.4.0.\n // eslint-disable-next-line camelcase\n xss_sanitization: false,\n\n // Disable quickbars entirely.\n // The UI is not ideal and we'll wait for it to improve in future before we enable it in Moodle.\n // eslint-disable-next-line camelcase\n quickbars_insert_toolbar: '',\n\n // Override the standard block formats property (removing h1 & h2).\n // https://www.tiny.cloud/docs/tinymce/6/user-formatting-options/#block_formats\n // eslint-disable-next-line camelcase\n block_formats: 'Paragraph=p; Heading 3=h3; Heading 4=h4; Heading 5=h5; Heading 6=h6;',\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 // 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 // Allow the administrator to disable branding.\n // https://www.tiny.cloud/docs/tinymce/6/statusbar-configuration-options/#branding\n branding: options.branding,\n\n // Put th cells in a thead element.\n // https://www.tiny.cloud/docs/tinymce/6/table-options/#table_header_type\n // eslint-disable-next-line camelcase\n table_header_type: 'sectionCells',\n\n setup: (editor) => {\n Options.register(editor, options);\n\n editor.on('PreInit', function() {\n // Work around a bug in TinyMCE with Firefox.\n // When an editor is removed, and replaced with an identically attributed editor (same ID),\n // and the Firefox window is freshly opened (e.g. Behat, Private browsing), the wrong contentWindow\n // is assigned to the editor instance leading to an NS_ERROR_UNEXPECTED error in Firefox.\n // This is a workaround for that issue.\n this.contentWindow = this.iframeElement.contentWindow;\n });\n editor.on('init', function() {\n // Hide justify alignment sub-menu.\n removeSubmenuItem(editor, 'align', 'tiny:justify');\n });\n\n editor.on('PostRender', function() {\n // Nest menu if set.\n if (options.nestedmenu) {\n nestMenu(editor);\n }\n });\n\n // The Boost and Classic theme cause issues if editor is in fullscreen mode.\n // The problem was resolved by changing the overflow value to related elements.\n editor.on('FullscreenStateChanged', function(e) {\n fixMenuPositionIfInFullsreen({\n open: e.state\n });\n });\n\n // The Boost theme uses Overflow=auto in the course index drawer,\n // it causes the dialogue window to be not correctly displayed in Safari browser.\n // The problem was resolved by changing the overflow value to the drawer.\n editor.on('OpenWindow CloseWindow', function(e) {\n fixDialoguePositionIfOpen({\n browser: tinyMCE.Env.browser,\n fsplugin: editor.plugins.fullscreen,\n open: e.type == \"openwindow\"\n });\n });\n },\n });\n\n config.toolbar = addToolbarSection(config.toolbar, 'content', 'formatting', true);\n config.toolbar = addToolbarButton(config.toolbar, 'content', 'link');\n\n // Add directionality plugins, always.\n config.toolbar = addToolbarSection(config.toolbar, 'directionality', 'alignment', true);\n config.toolbar = addToolbarButtons(config.toolbar, 'directionality', ['ltr', 'rtl']);\n\n // Remove the align justify button from the toolbar.\n config.toolbar = removeToolbarButton(config.toolbar, 'alignment', 'alignjustify');\n\n return config;\n};\n\n/**\n * Fetch the TinyMCE configuration for this editor instance.\n *\n * @param {HTMLElement} target\n * @param {TinyMCE} tinyMCE The TinyMCE API\n * @param {Object} options The editor plugin configuration\n * @param {object} pluginValues\n * @param {object} pluginValues.pluginConfig The list of plugin configuration\n * @param {object} pluginValues.pluginNames The list of plugins to load\n * @returns {object} The TinyMCE Configuration\n */\nconst getEditorConfiguration = (target, tinyMCE, options, pluginValues) => {\n const {\n pluginNames,\n pluginConfig,\n } = pluginValues;\n\n // Allow plugins to modify the configuration.\n // This seems a little strange, but we must double-process the config slightly.\n\n // First we fetch the standard configuration.\n const instanceConfig = getStandardConfig(target, tinyMCE, options, pluginNames);\n\n // Next we make any standard changes.\n // Here we remove the file menu, as it doesn't offer any useful functionality.\n // We only empty the items list so that a plugin may choose to add to it themselves later if they wish.\n if (instanceConfig.menu.file) {\n instanceConfig.menu.file.items = '';\n }\n\n // We disable the styles, backcolor, and forecolor plugins from the format menu.\n // These are not useful for Moodle and we don't want to encourage their use.\n if (instanceConfig.menu.format) {\n instanceConfig.menu.format.items = instanceConfig.menu.format.items\n // Remove forecolor and backcolor.\n .replace(/forecolor ?/, '')\n .replace(/backcolor ?/, '')\n\n // Remove fontfamily for now.\n .replace(/fontfamily ?/, '')\n\n // Remove fontsize for now.\n .replace(/fontsize ?/, '')\n\n // Remove styles - it just duplicates the format menu in a way which does not respect configuration\n .replace(/styles ?/, '')\n\n // Remove any duplicate separators.\n .replaceAll(/\\| *\\|/g, '|');\n }\n\n // eslint-disable-next-line camelcase\n instanceConfig.quickbars_selection_toolbar = instanceConfig.quickbars_selection_toolbar.replace('h2 h3', 'h3 h4 h5 h6');\n\n // Next we call the `configure` function for any plugin which defines it.\n // We pass the current instanceConfig in here, to allow them to make certain changes to the global configuration.\n // For example, to add themselves to any menu, toolbar, and so on.\n // Any plugin which wishes to have configuration options must register those options here.\n pluginConfig.filter((pluginConfig) => typeof pluginConfig.configure === 'function').forEach((pluginConfig) => {\n const pluginInstanceOverride = pluginConfig.configure(instanceConfig, options);\n Object.assign(instanceConfig, pluginInstanceOverride);\n });\n\n // Next we convert the plugin configuration into a format that TinyMCE understands.\n Object.assign(instanceConfig, Options.getInitialPluginConfiguration(options));\n\n return instanceConfig;\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\n // TinyMCE uses the element ID as a map key internally, even if the target has changed.\n // In the case where we have an editor in a modal form which has been detached from the DOM, but the editor not removed,\n // we need to manually destroy the editor.\n // We could theoretically do this with a Mutation Observer, but in some cases the Node may be moved,\n // or added back elsewhere in the DOM.\n\n // First remove any detached editors.\n tinyMCE.get().filter((editor) => !editor.getElement().isConnected).forEach((editor) => {\n editor.remove();\n });\n\n // Now check for any existing editor which shares the same ID.\n const existingEditor = tinyMCE.EditorManager.get(target.id);\n if (existingEditor) {\n if (existingEditor.getElement() === target) {\n pendingPromise.resolve();\n return Promise.resolve(existingEditor);\n } else {\n pendingPromise.resolve();\n throw new Error('TinyMCE instance already exists for different target with same ID');\n }\n }\n\n // Get the editor configuration for this editor.\n const instanceConfig = getEditorConfiguration(target, tinyMCE, options, pluginValues);\n\n // Initialise the editor instance for the given configuration.\n // At this point any plugin which has configuration options registered will have them applied for this instance.\n const [editor] = await tinyMCE.init(instanceConfig);\n\n // Store the editor instance in the instanceMap and register a listener on removal to remove it from the map.\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 // If the editor is part of a form, also listen to the jQuery submit event.\n // The jQuery submit event will not trigger the native submit event, and therefore the content will not be saved.\n // We cannot rely on listening to the bubbled submit event on the document because other events on child nodes may\n // consume the data before it is saved.\n if (target.form) {\n jQuery(target.form).on('submit', () => {\n editor.save();\n });\n }\n\n // Save the editor content to the textarea when the editor is blurred.\n editor.on('blur', () => {\n editor.save();\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","config","Object","assign","base_url","baseUrl","min_height","height","clientHeight","rows","window","getComputedStyle","lineHeight","content_css","css","convert_urls","a11y_advanced_options","extended_valid_elements","xss_sanitization","quickbars_insert_toolbar","block_formats","skin","promotion","branding","table_header_type","setup","editor","Options","register","on","contentWindow","this","iframeElement","nestedmenu","container","getContainer","menuContainer","parentNode","appendChild","nestMenu","e","params","open","style","overflow","pageWithDrawers","fixMenuPositionIfInFullsreen","state","browser","isSafari","fsplugin","isFullscreen","fixDialoguePositionIfOpen","Env","fullscreen","type","toolbar","getEditorConfiguration","pluginValues","instanceConfig","menu","file","items","format","replace","replaceAll","quickbars_selection_toolbar","configure","forEach","pluginInstanceOverride","getInitialPluginConfiguration","pendingPromise","Pending","keys","getElement","isConnected","existingEditor","EditorManager","id","Error","init","set","_ref2","delete","targetElm","form","save"],"mappings":"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"} \ 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 jQuery from 'jquery';\nimport Pending from 'core/pending';\nimport {getDefaultConfiguration} from './defaults';\nimport {getTinyMCE, baseUrl} from './loader';\nimport * as Options from './options';\nimport {addToolbarButton, addToolbarButtons, addToolbarSection,\n removeToolbarButton, removeSubmenuItem, updateEditorState} from './utils';\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 * Nest the dropdown menu inside the parent DOM.\n *\n * The TinyMCE menu has a significant issue with the Overflow style,\n * and the Boost theme heavily uses Overflow for drawer navigation.\n * Moving the menu container into the parent editor container makes it work correctly.\n *\n * @param {object} editor\n */\n const nestMenu = (editor) => {\n const container = editor.getContainer();\n const menuContainer = document.querySelector('body > .tox.tox-tinymce-aux');\n container.parentNode.appendChild(menuContainer);\n};\n\n/**\n * Fix the Tiny menu position if the editor is in fullscreen mode on the Boost theme.\n *\n * The boost theme makes the TinyMCE editor rendered in a scrollable container,\n * scrolling the editor’s container will cause TinyMCE UI elements to be detached from the anchor.\n * Therefore, to keep the tinyMCE menu in the correct position,\n * adjustments must be made on the page drawers style.\n *\n * @param {object} params\n * @param {Boolean} params.open True if editor in fullscreen mode, otherwise false.\n */\nconst fixMenuPositionIfInFullsreen = (params) => {\n if (params.open) {\n // Keep the menu remains visible and properly aligned.\n document.querySelector('.tox-fullscreen').style.overflow = 'unset';\n }\n\n const pageWithDrawers = document.querySelector('#page.drawers');\n if (pageWithDrawers) {\n pageWithDrawers.style.overflow = params.open ? \"unset\" : \"\";\n }\n};\n\n/**\n * Fix the dialogue window positioning issue of TinyMCE editor in Safari browsers.\n *\n * When using TinyMCE editor in Safari browsers, a problem may occur where the dialogue\n * windows (such as modal dialogs) overlap with page drawers due to a specific behavior\n * in Safari's rendering. This function addresses the issue by adjusting the CSS overflow\n * property of the page drawers, ensuring they do not obscure the dialogue windows.\n *\n * @param {object} params\n * @param {object} params.browser Browser environment.\n * @param {object} params.fsplugin Fullscreen plugin.\n * @param {Boolean} params.open True if the dialogue window opens, otherwise false.\n */\nconst fixDialoguePositionIfOpen = (params) => {\n // To avoid modification the existing overflow value that has been set fixMenuPositionIfInFullsreen(),\n // the fix only applied if the editor not in fullscreen mode.\n if (params.browser.isSafari() && !params.fsplugin.isFullscreen()) {\n const pageWithDrawers = document.querySelector('#page.drawers');\n if (pageWithDrawers) {\n pageWithDrawers.style.overflow = params.open ? \"unset\" : \"\";\n }\n }\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 const config = 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 // https://www.tiny.cloud/docs/tinymce/6/customize-ui/#set-maximum-and-minimum-heights-and-widths\n // Set the minimum height to the smallest height that we can fit the Menu bar, Tool bar, Status bar and the text area.\n // eslint-disable-next-line camelcase\n min_height: 175,\n\n // Base the height on the size of the text area. Account for lack of height value when the editor is initially hidden,\n // in which case use CSS calc() to approximate the same based on number of rows and target line height.\n height: target.clientHeight || `calc(${target.rows} * ${window.getComputedStyle(target).lineHeight || '22px'})`,\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 // Ensure that scripts are recognized as valid elements.\n // eslint-disable-next-line camelcase\n extended_valid_elements: 'script[*]',\n\n // Disable XSS Sanitisation.\n // We do this in PHP.\n // https://www.tiny.cloud/docs/tinymce/6/security/#turning-dompurify-off\n // Note: This feature has been backported from TinyMCE 6.4.0.\n // eslint-disable-next-line camelcase\n xss_sanitization: false,\n\n // Disable quickbars entirely.\n // The UI is not ideal and we'll wait for it to improve in future before we enable it in Moodle.\n // eslint-disable-next-line camelcase\n quickbars_insert_toolbar: '',\n\n // Override the standard block formats property (removing h1 & h2).\n // https://www.tiny.cloud/docs/tinymce/6/user-formatting-options/#block_formats\n // eslint-disable-next-line camelcase\n block_formats: 'Paragraph=p; Heading 3=h3; Heading 4=h4; Heading 5=h5; Heading 6=h6;',\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 // 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 // Allow the administrator to disable branding.\n // https://www.tiny.cloud/docs/tinymce/6/statusbar-configuration-options/#branding\n branding: options.branding,\n\n // Put th cells in a thead element.\n // https://www.tiny.cloud/docs/tinymce/6/table-options/#table_header_type\n // eslint-disable-next-line camelcase\n table_header_type: 'sectionCells',\n\n setup: (editor) => {\n Options.register(editor, options);\n\n editor.on('PreInit', function() {\n // Work around a bug in TinyMCE with Firefox.\n // When an editor is removed, and replaced with an identically attributed editor (same ID),\n // and the Firefox window is freshly opened (e.g. Behat, Private browsing), the wrong contentWindow\n // is assigned to the editor instance leading to an NS_ERROR_UNEXPECTED error in Firefox.\n // This is a workaround for that issue.\n this.contentWindow = this.iframeElement.contentWindow;\n });\n editor.on('init', function() {\n // Hide justify alignment sub-menu.\n removeSubmenuItem(editor, 'align', 'tiny:justify');\n });\n\n editor.on('PostRender', function() {\n // Nest menu if set.\n if (options.nestedmenu) {\n nestMenu(editor);\n }\n });\n\n // The Boost and Classic theme cause issues if editor is in fullscreen mode.\n // The problem was resolved by changing the overflow value to related elements.\n editor.on('FullscreenStateChanged', function(e) {\n fixMenuPositionIfInFullsreen({\n open: e.state\n });\n });\n\n // The Boost theme uses Overflow=auto in the course index drawer,\n // it causes the dialogue window to be not correctly displayed in Safari browser.\n // The problem was resolved by changing the overflow value to the drawer.\n editor.on('OpenWindow CloseWindow', function(e) {\n fixDialoguePositionIfOpen({\n browser: tinyMCE.Env.browser,\n fsplugin: editor.plugins.fullscreen,\n open: e.type == \"openwindow\"\n });\n });\n\n target.addEventListener('form:editorUpdated', function() {\n updateEditorState(editor, target);\n });\n\n target.dispatchEvent(new Event('form:editorUpdated'));\n },\n });\n\n config.toolbar = addToolbarSection(config.toolbar, 'content', 'formatting', true);\n config.toolbar = addToolbarButton(config.toolbar, 'content', 'link');\n\n // Add directionality plugins, always.\n config.toolbar = addToolbarSection(config.toolbar, 'directionality', 'alignment', true);\n config.toolbar = addToolbarButtons(config.toolbar, 'directionality', ['ltr', 'rtl']);\n\n // Remove the align justify button from the toolbar.\n config.toolbar = removeToolbarButton(config.toolbar, 'alignment', 'alignjustify');\n\n return config;\n};\n\n/**\n * Fetch the TinyMCE configuration for this editor instance.\n *\n * @param {HTMLElement} target\n * @param {TinyMCE} tinyMCE The TinyMCE API\n * @param {Object} options The editor plugin configuration\n * @param {object} pluginValues\n * @param {object} pluginValues.pluginConfig The list of plugin configuration\n * @param {object} pluginValues.pluginNames The list of plugins to load\n * @returns {object} The TinyMCE Configuration\n */\nconst getEditorConfiguration = (target, tinyMCE, options, pluginValues) => {\n const {\n pluginNames,\n pluginConfig,\n } = pluginValues;\n\n // Allow plugins to modify the configuration.\n // This seems a little strange, but we must double-process the config slightly.\n\n // First we fetch the standard configuration.\n const instanceConfig = getStandardConfig(target, tinyMCE, options, pluginNames);\n\n // Next we make any standard changes.\n // Here we remove the file menu, as it doesn't offer any useful functionality.\n // We only empty the items list so that a plugin may choose to add to it themselves later if they wish.\n if (instanceConfig.menu.file) {\n instanceConfig.menu.file.items = '';\n }\n\n // We disable the styles, backcolor, and forecolor plugins from the format menu.\n // These are not useful for Moodle and we don't want to encourage their use.\n if (instanceConfig.menu.format) {\n instanceConfig.menu.format.items = instanceConfig.menu.format.items\n // Remove forecolor and backcolor.\n .replace(/forecolor ?/, '')\n .replace(/backcolor ?/, '')\n\n // Remove fontfamily for now.\n .replace(/fontfamily ?/, '')\n\n // Remove fontsize for now.\n .replace(/fontsize ?/, '')\n\n // Remove styles - it just duplicates the format menu in a way which does not respect configuration\n .replace(/styles ?/, '')\n\n // Remove any duplicate separators.\n .replaceAll(/\\| *\\|/g, '|');\n }\n\n // eslint-disable-next-line camelcase\n instanceConfig.quickbars_selection_toolbar = instanceConfig.quickbars_selection_toolbar.replace('h2 h3', 'h3 h4 h5 h6');\n\n // Next we call the `configure` function for any plugin which defines it.\n // We pass the current instanceConfig in here, to allow them to make certain changes to the global configuration.\n // For example, to add themselves to any menu, toolbar, and so on.\n // Any plugin which wishes to have configuration options must register those options here.\n pluginConfig.filter((pluginConfig) => typeof pluginConfig.configure === 'function').forEach((pluginConfig) => {\n const pluginInstanceOverride = pluginConfig.configure(instanceConfig, options);\n Object.assign(instanceConfig, pluginInstanceOverride);\n });\n\n // Next we convert the plugin configuration into a format that TinyMCE understands.\n Object.assign(instanceConfig, Options.getInitialPluginConfiguration(options));\n\n return instanceConfig;\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\n // TinyMCE uses the element ID as a map key internally, even if the target has changed.\n // In the case where we have an editor in a modal form which has been detached from the DOM, but the editor not removed,\n // we need to manually destroy the editor.\n // We could theoretically do this with a Mutation Observer, but in some cases the Node may be moved,\n // or added back elsewhere in the DOM.\n\n // First remove any detached editors.\n tinyMCE.get().filter((editor) => !editor.getElement().isConnected).forEach((editor) => {\n editor.remove();\n });\n\n // Now check for any existing editor which shares the same ID.\n const existingEditor = tinyMCE.EditorManager.get(target.id);\n if (existingEditor) {\n if (existingEditor.getElement() === target) {\n pendingPromise.resolve();\n return Promise.resolve(existingEditor);\n } else {\n pendingPromise.resolve();\n throw new Error('TinyMCE instance already exists for different target with same ID');\n }\n }\n\n // Get the editor configuration for this editor.\n const instanceConfig = getEditorConfiguration(target, tinyMCE, options, pluginValues);\n\n // Initialise the editor instance for the given configuration.\n // At this point any plugin which has configuration options registered will have them applied for this instance.\n const [editor] = await tinyMCE.init(instanceConfig);\n\n // Store the editor instance in the instanceMap and register a listener on removal to remove it from the map.\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 // If the editor is part of a form, also listen to the jQuery submit event.\n // The jQuery submit event will not trigger the native submit event, and therefore the content will not be saved.\n // We cannot rely on listening to the bubbled submit event on the document because other events on child nodes may\n // consume the data before it is saved.\n if (target.form) {\n jQuery(target.form).on('submit', () => {\n editor.save();\n });\n }\n\n // Save the editor content to the textarea when the editor is blurred.\n editor.on('blur', () => {\n editor.save();\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","config","Object","assign","base_url","baseUrl","min_height","height","clientHeight","rows","window","getComputedStyle","lineHeight","content_css","css","convert_urls","a11y_advanced_options","extended_valid_elements","xss_sanitization","quickbars_insert_toolbar","block_formats","skin","promotion","branding","table_header_type","setup","editor","Options","register","on","contentWindow","this","iframeElement","nestedmenu","container","getContainer","menuContainer","parentNode","appendChild","nestMenu","e","params","open","style","overflow","pageWithDrawers","fixMenuPositionIfInFullsreen","state","browser","isSafari","fsplugin","isFullscreen","fixDialoguePositionIfOpen","Env","fullscreen","type","addEventListener","dispatchEvent","Event","toolbar","getEditorConfiguration","pluginValues","instanceConfig","menu","file","items","format","replace","replaceAll","quickbars_selection_toolbar","configure","forEach","pluginInstanceOverride","getInitialPluginConfiguration","pendingPromise","Pending","keys","getElement","isConnected","existingEditor","EditorManager","id","Error","init","set","_ref2","delete","targetElm","form","save"],"mappings":"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"} \ 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 dc8f0cd5751..a672847db29 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","./options","core/str"],(function(_exports,_templates,_options,_str){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.removeToolbarButtons=_exports.removeToolbarButton=_exports.removeSubmenuItem=_exports.removeMenubarItems=_exports.removeMenubarItem=_exports.getPluginMetadata=_exports.getImagePath=_exports.getDocumentationLink=_exports.getButtonImage=_exports.ensureEditorIsValid=_exports.displayFilepicker=_exports.addToolbarSection=_exports.addToolbarButtons=_exports.addToolbarButton=_exports.addQuickbarsToolbarItem=_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.addToolbarButtons=(toolbar,section,buttons)=>{if(!toolbar)return[{name:section,items:buttons}];return JSON.parse(JSON.stringify(toolbar)).map((item=>(item.name===section&&buttons.forEach((button=>item.items.push(button))),item)))};_exports.addToolbarSection=function(toolbar,name,relativeTo){let append=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];const newSection={name:name,items:[]},sectionInserted=toolbar.some(((section,index)=>section.name===relativeTo&&(append?toolbar.splice(index+1,0,newSection):toolbar.splice(index,0,newSection),!0)));return sectionInserted||(append?toolbar.push(newSection):toolbar.unshift(newSection)),toolbar};_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(" ")};_exports.addQuickbarsToolbarItem=function(toolbar){return toolbar};const getDocumentationLink=pluginName=>"https://docs.moodle.org/en/editor_tiny/".concat(pluginName);_exports.getDocumentationLink=getDocumentationLink;_exports.getPluginMetadata=async function(component,pluginName){let url=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const name=await(0,_str.get_string)("helplinktext",component);return{getMetadata:()=>({name:name,url:null!=url?url:getDocumentationLink(pluginName)})}};_exports.ensureEditorIsValid=editor=>editor.getElement().isConnected?editor:null;_exports.removeToolbarButton=(toolbar,section,button)=>{if(!toolbar)return[{name:section,items:[button]}];return JSON.parse(JSON.stringify(toolbar)).map((item=>(item.name===section&&item.items.splice(item.items.indexOf(button),1),item)))};_exports.removeToolbarButtons=(toolbar,section,buttons)=>{if(!toolbar)return[{name:section,items:buttons}];return JSON.parse(JSON.stringify(toolbar)).map((item=>(item.name===section&&buttons.forEach((button=>item.items.splice(item.items.indexOf(button),1))),item)))};_exports.removeSubmenuItem=async(editor,section,submenuitem)=>{const menuItems=editor.ui.registry.getAll().menuItems[section],submenuitemtitle=await(0,_str.get_string)(submenuitem,"editor_tiny");menuItems&&editor.ui.registry.addNestedMenuItem(section,{text:menuItems.text,getSubmenuItems:()=>{let newSubmenu=[];return menuItems.getSubmenuItems().forEach((item=>{item.text.trim()!=submenuitemtitle&&newSubmenu.push(item)})),newSubmenu}})};_exports.removeMenubarItem=(menubar,section,menuitem)=>(menubar[section].items=menubar[section].items.replace(menuitem,""),menubar);_exports.removeMenubarItems=(menubar,section,menuitems)=>{const regexPattern=new RegExp(menuitems.join("|"),"ig");return menubar[section].items=menubar[section].items.replace(regexPattern,""),menubar}})); +define("editor_tiny/utils",["exports","core/templates","./options","core/str"],(function(_exports,_templates,_options,_str){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.updateEditorState=_exports.removeToolbarButtons=_exports.removeToolbarButton=_exports.removeSubmenuItem=_exports.removeMenubarItems=_exports.removeMenubarItem=_exports.getPluginMetadata=_exports.getImagePath=_exports.getDocumentationLink=_exports.getButtonImage=_exports.ensureEditorIsValid=_exports.displayFilepicker=_exports.addToolbarSection=_exports.addToolbarButtons=_exports.addToolbarButton=_exports.addQuickbarsToolbarItem=_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.addToolbarButtons=(toolbar,section,buttons)=>{if(!toolbar)return[{name:section,items:buttons}];return JSON.parse(JSON.stringify(toolbar)).map((item=>(item.name===section&&buttons.forEach((button=>item.items.push(button))),item)))};_exports.addToolbarSection=function(toolbar,name,relativeTo){let append=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];const newSection={name:name,items:[]},sectionInserted=toolbar.some(((section,index)=>section.name===relativeTo&&(append?toolbar.splice(index+1,0,newSection):toolbar.splice(index,0,newSection),!0)));return sectionInserted||(append?toolbar.push(newSection):toolbar.unshift(newSection)),toolbar};_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(" ")};_exports.addQuickbarsToolbarItem=function(toolbar){return toolbar};const getDocumentationLink=pluginName=>"https://docs.moodle.org/en/editor_tiny/".concat(pluginName);_exports.getDocumentationLink=getDocumentationLink;_exports.getPluginMetadata=async function(component,pluginName){let url=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const name=await(0,_str.get_string)("helplinktext",component);return{getMetadata:()=>({name:name,url:null!=url?url:getDocumentationLink(pluginName)})}};_exports.ensureEditorIsValid=editor=>editor.getElement().isConnected?editor:null;_exports.removeToolbarButton=(toolbar,section,button)=>{if(!toolbar)return[{name:section,items:[button]}];return JSON.parse(JSON.stringify(toolbar)).map((item=>(item.name===section&&item.items.splice(item.items.indexOf(button),1),item)))};_exports.removeToolbarButtons=(toolbar,section,buttons)=>{if(!toolbar)return[{name:section,items:buttons}];return JSON.parse(JSON.stringify(toolbar)).map((item=>(item.name===section&&buttons.forEach((button=>item.items.splice(item.items.indexOf(button),1))),item)))};_exports.removeSubmenuItem=async(editor,section,submenuitem)=>{const menuItems=editor.ui.registry.getAll().menuItems[section],submenuitemtitle=await(0,_str.get_string)(submenuitem,"editor_tiny");menuItems&&editor.ui.registry.addNestedMenuItem(section,{text:menuItems.text,getSubmenuItems:()=>{let newSubmenu=[];return menuItems.getSubmenuItems().forEach((item=>{item.text.trim()!=submenuitemtitle&&newSubmenu.push(item)})),newSubmenu}})};_exports.removeMenubarItem=(menubar,section,menuitem)=>(menubar[section].items=menubar[section].items.replace(menuitem,""),menubar);_exports.removeMenubarItems=(menubar,section,menuitems)=>{const regexPattern=new RegExp(menuitems.join("|"),"ig");return menubar[section].items=menubar[section].items.replace(regexPattern,""),menubar};_exports.updateEditorState=(editor,target)=>{target.hasAttribute("readonly")?editor.mode.set("readonly"):editor.mode.set("design")}})); //# 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 2523ae174d4..d40d9b35255 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';\nimport {getFilePicker} from './options';\nimport {get_string as getString} from 'core/str';\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 Toolbar configuration, add the specified buttons to the named section.\n *\n * @param {object} toolbar\n * @param {string} section\n * @param {Array} buttons\n * @returns {object} The toolbar configuration\n */\nexport const addToolbarButtons = (toolbar, section, buttons) => {\n if (!toolbar) {\n return [{\n name: section,\n items: buttons,\n }];\n }\n\n const mutatedToolbar = JSON.parse(JSON.stringify(toolbar));\n return mutatedToolbar.map((item) => {\n if (item.name === section) {\n buttons.forEach(button => item.items.push(button));\n }\n\n return item;\n });\n};\n\n/**\n * Insert a new section into the toolbar.\n *\n * @param {array} toolbar The TinyMCE.editor.settings.toolbar configuration\n * @param {string} name The new section name to add\n * @param {string} relativeTo Insert relative to this section name\n * @param {boolean} append Append or Prepend\n * @returns {array}\n */\nexport const addToolbarSection = (toolbar, name, relativeTo, append = true) => {\n const newSection = {\n name,\n items: [],\n };\n const sectionInserted = toolbar.some((section, index) => {\n if (section.name === relativeTo) {\n if (append) {\n toolbar.splice(index + 1, 0, newSection);\n } else {\n toolbar.splice(index, 0, newSection);\n }\n return true;\n }\n return false;\n });\n\n if (!sectionInserted) {\n // Relative section not found.\n if (append) {\n toolbar.push(newSection);\n } else {\n toolbar.unshift(newSection);\n }\n }\n\n return toolbar;\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\n/**\n * Given a TinyMCE quickbars configuration, add items to the meun.\n *\n * @param {string} toolbar\n * @param {string[]} menuitems\n * @returns {string}\n */\n// eslint-disable-next-line no-unused-vars\nexport const addQuickbarsToolbarItem = (toolbar, ...menuitems) => {\n // For the moment we have disabled use of this menu.\n // The configuration is left in place to allow plugins to declare that they would like to support it in the future.\n return toolbar;\n};\n\n/**\n * Get the link to the user documentation for the named plugin.\n *\n * @param {string} pluginName\n * @returns {string}\n */\nexport const getDocumentationLink = (pluginName) => `https://docs.moodle.org/en/editor_tiny/${pluginName}`;\n\n/**\n * Get the default plugin metadata for the named plugin.\n * If no URL is provided, then a URL is generated pointing to the standard Moodle Documentation.\n *\n * @param {string} component The component name\n * @param {string} pluginName The plugin name\n * @param {string|null} [url=null] An optional URL to the plugin documentation\n * @returns {object}\n */\nexport const getPluginMetadata = async(component, pluginName, url = null) => {\n const name = await getString('helplinktext', component);\n return {\n getMetadata: () => ({\n name,\n url: url ?? getDocumentationLink(pluginName),\n }),\n };\n};\n\n/**\n * Ensure that the editor is still in the DOM, removing it if it is not.\n *\n * @param {TinyMCE} editor\n * @returns {TinyMCE|null}\n */\nexport const ensureEditorIsValid = (editor) => {\n // TinyMCE uses the element ID as a map key internally, even if the target has changed.\n // In cases such as where an editor is in a modal form which has been detached from the DOM, but the editor not removed,\n // we need to manually destroy the editor.\n // We could theoretically do this with a Mutation Observer, but in some cases the Node may be moved,\n // or added back elsewhere in the DOM.\n if (!editor.getElement().isConnected) {\n return null;\n }\n\n return editor;\n};\n\n/**\n * Given a TinyMCE Toolbar configuration, remove the specified button from the named section.\n *\n * @param {object} toolbar\n * @param {string} section\n * @param {string} button\n * @returns {object} The toolbar configuration\n */\n export const removeToolbarButton = (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.splice(item.items.indexOf(button), 1);\n }\n\n return item;\n });\n};\n\n/**\n * Given a TinyMCE Toolbar configuration, remove the specified buttons from the named section.\n *\n * @param {object} toolbar\n * @param {string} section\n * @param {Array} buttons\n * @returns {object} The toolbar configuration\n */\n export const removeToolbarButtons = (toolbar, section, buttons) => {\n if (!toolbar) {\n return [{\n name: section,\n items: buttons,\n }];\n }\n\n const mutatedToolbar = JSON.parse(JSON.stringify(toolbar));\n return mutatedToolbar.map((item) => {\n if (item.name === section) {\n buttons.forEach(button => item.items.splice(item.items.indexOf(button), 1));\n }\n\n return item;\n });\n};\n\n/**\n * Remove the specified sub-menu item from the named section.\n * Recreate a menu with the same sub-menu items but remove the specified item.\n *\n * @param {TinyMCE} editor\n * @param {string} section\n * @param {string} submenuitem The text of sub-menu that we want to removed\n */\nexport const removeSubmenuItem = async(editor, section, submenuitem) => {\n // Get menu items.\n const menuItems = editor.ui.registry.getAll().menuItems[section];\n\n // Because we will match between title strings,\n // we make sure no problems arise while applying multi-language.\n const submenuitemtitle = await getString(submenuitem, 'editor_tiny');\n\n // Overriding the menu items,\n // by recreating them but excluding the specified sub-menu.\n if (menuItems) {\n editor.ui.registry.addNestedMenuItem(\n section,\n {\n text: menuItems.text,\n getSubmenuItems: () => {\n let newSubmenu = [];\n menuItems.getSubmenuItems().forEach((item) => {\n // Need to trim the text because some of the sub-menus use space to replace an icon.\n if (item.text.trim() != submenuitemtitle) {\n newSubmenu.push(item);\n }\n });\n return newSubmenu;\n }\n }\n );\n }\n};\n\n/**\n * Given a TinyMCE Menubar configuration, remove the specified menu from the named section.\n *\n * @param {string} menubar\n * @param {string} section\n * @param {string} menuitem\n * @returns {object}\n */\nexport const removeMenubarItem = (menubar, section, menuitem) => {\n menubar[section].items = menubar[section].items\n .replace(menuitem, '');\n\n return menubar;\n};\n\n/**\n * Given a TinyMCE Menubar configuration, remove the specified menu from the named section.\n *\n * @param {string} menubar\n * @param {string} section\n * @param {Array} menuitems\n * @returns {object}\n */\nexport const removeMenubarItems = (menubar, section, menuitems) => {\n // Create RegExp pattern.\n const regexPattern = new RegExp(menuitems.join('|'), \"ig\");\n\n // Remove menuitems.\n menubar[section].items = menubar[section].items.replace(regexPattern, '');\n\n return menubar;\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","buttons","forEach","relativeTo","append","newSection","sectionInserted","some","index","splice","unshift","menubar","menuitem","title","mutatedMenubar","Array","from","Object","entries","_ref","menu","contextmenu","contextmenuItems","split","menuitems","concat","filter","join","getDocumentationLink","pluginName","url","getMetadata","getElement","isConnected","indexOf","submenuitem","menuItems","ui","registry","getAll","submenuitemtitle","addNestedMenuItem","text","getSubmenuItems","newSubmenu","trim","replace","regexPattern","RegExp"],"mappings":"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"} \ 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';\nimport {get_string as getString} from 'core/str';\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 Toolbar configuration, add the specified buttons to the named section.\n *\n * @param {object} toolbar\n * @param {string} section\n * @param {Array} buttons\n * @returns {object} The toolbar configuration\n */\nexport const addToolbarButtons = (toolbar, section, buttons) => {\n if (!toolbar) {\n return [{\n name: section,\n items: buttons,\n }];\n }\n\n const mutatedToolbar = JSON.parse(JSON.stringify(toolbar));\n return mutatedToolbar.map((item) => {\n if (item.name === section) {\n buttons.forEach(button => item.items.push(button));\n }\n\n return item;\n });\n};\n\n/**\n * Insert a new section into the toolbar.\n *\n * @param {array} toolbar The TinyMCE.editor.settings.toolbar configuration\n * @param {string} name The new section name to add\n * @param {string} relativeTo Insert relative to this section name\n * @param {boolean} append Append or Prepend\n * @returns {array}\n */\nexport const addToolbarSection = (toolbar, name, relativeTo, append = true) => {\n const newSection = {\n name,\n items: [],\n };\n const sectionInserted = toolbar.some((section, index) => {\n if (section.name === relativeTo) {\n if (append) {\n toolbar.splice(index + 1, 0, newSection);\n } else {\n toolbar.splice(index, 0, newSection);\n }\n return true;\n }\n return false;\n });\n\n if (!sectionInserted) {\n // Relative section not found.\n if (append) {\n toolbar.push(newSection);\n } else {\n toolbar.unshift(newSection);\n }\n }\n\n return toolbar;\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\n/**\n * Given a TinyMCE quickbars configuration, add items to the meun.\n *\n * @param {string} toolbar\n * @param {string[]} menuitems\n * @returns {string}\n */\n// eslint-disable-next-line no-unused-vars\nexport const addQuickbarsToolbarItem = (toolbar, ...menuitems) => {\n // For the moment we have disabled use of this menu.\n // The configuration is left in place to allow plugins to declare that they would like to support it in the future.\n return toolbar;\n};\n\n/**\n * Get the link to the user documentation for the named plugin.\n *\n * @param {string} pluginName\n * @returns {string}\n */\nexport const getDocumentationLink = (pluginName) => `https://docs.moodle.org/en/editor_tiny/${pluginName}`;\n\n/**\n * Get the default plugin metadata for the named plugin.\n * If no URL is provided, then a URL is generated pointing to the standard Moodle Documentation.\n *\n * @param {string} component The component name\n * @param {string} pluginName The plugin name\n * @param {string|null} [url=null] An optional URL to the plugin documentation\n * @returns {object}\n */\nexport const getPluginMetadata = async(component, pluginName, url = null) => {\n const name = await getString('helplinktext', component);\n return {\n getMetadata: () => ({\n name,\n url: url ?? getDocumentationLink(pluginName),\n }),\n };\n};\n\n/**\n * Ensure that the editor is still in the DOM, removing it if it is not.\n *\n * @param {TinyMCE} editor\n * @returns {TinyMCE|null}\n */\nexport const ensureEditorIsValid = (editor) => {\n // TinyMCE uses the element ID as a map key internally, even if the target has changed.\n // In cases such as where an editor is in a modal form which has been detached from the DOM, but the editor not removed,\n // we need to manually destroy the editor.\n // We could theoretically do this with a Mutation Observer, but in some cases the Node may be moved,\n // or added back elsewhere in the DOM.\n if (!editor.getElement().isConnected) {\n return null;\n }\n\n return editor;\n};\n\n/**\n * Given a TinyMCE Toolbar configuration, remove the specified button from the named section.\n *\n * @param {object} toolbar\n * @param {string} section\n * @param {string} button\n * @returns {object} The toolbar configuration\n */\n export const removeToolbarButton = (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.splice(item.items.indexOf(button), 1);\n }\n\n return item;\n });\n};\n\n/**\n * Given a TinyMCE Toolbar configuration, remove the specified buttons from the named section.\n *\n * @param {object} toolbar\n * @param {string} section\n * @param {Array} buttons\n * @returns {object} The toolbar configuration\n */\n export const removeToolbarButtons = (toolbar, section, buttons) => {\n if (!toolbar) {\n return [{\n name: section,\n items: buttons,\n }];\n }\n\n const mutatedToolbar = JSON.parse(JSON.stringify(toolbar));\n return mutatedToolbar.map((item) => {\n if (item.name === section) {\n buttons.forEach(button => item.items.splice(item.items.indexOf(button), 1));\n }\n\n return item;\n });\n};\n\n/**\n * Remove the specified sub-menu item from the named section.\n * Recreate a menu with the same sub-menu items but remove the specified item.\n *\n * @param {TinyMCE} editor\n * @param {string} section\n * @param {string} submenuitem The text of sub-menu that we want to removed\n */\nexport const removeSubmenuItem = async(editor, section, submenuitem) => {\n // Get menu items.\n const menuItems = editor.ui.registry.getAll().menuItems[section];\n\n // Because we will match between title strings,\n // we make sure no problems arise while applying multi-language.\n const submenuitemtitle = await getString(submenuitem, 'editor_tiny');\n\n // Overriding the menu items,\n // by recreating them but excluding the specified sub-menu.\n if (menuItems) {\n editor.ui.registry.addNestedMenuItem(\n section,\n {\n text: menuItems.text,\n getSubmenuItems: () => {\n let newSubmenu = [];\n menuItems.getSubmenuItems().forEach((item) => {\n // Need to trim the text because some of the sub-menus use space to replace an icon.\n if (item.text.trim() != submenuitemtitle) {\n newSubmenu.push(item);\n }\n });\n return newSubmenu;\n }\n }\n );\n }\n};\n\n/**\n * Given a TinyMCE Menubar configuration, remove the specified menu from the named section.\n *\n * @param {string} menubar\n * @param {string} section\n * @param {string} menuitem\n * @returns {object}\n */\nexport const removeMenubarItem = (menubar, section, menuitem) => {\n menubar[section].items = menubar[section].items\n .replace(menuitem, '');\n\n return menubar;\n};\n\n/**\n * Given a TinyMCE Menubar configuration, remove the specified menu from the named section.\n *\n * @param {string} menubar\n * @param {string} section\n * @param {Array} menuitems\n * @returns {object}\n */\nexport const removeMenubarItems = (menubar, section, menuitems) => {\n // Create RegExp pattern.\n const regexPattern = new RegExp(menuitems.join('|'), \"ig\");\n\n // Remove menuitems.\n menubar[section].items = menubar[section].items.replace(regexPattern, '');\n\n return menubar;\n};\n\n/**\n * Updates the state of the editor.\n *\n * @param {TinyMCE} editor\n * @param {HTMLElement} target\n */\nexport const updateEditorState = (editor, target) => {\n if (target.hasAttribute('readonly')) {\n editor.mode.set(\"readonly\");\n } else {\n editor.mode.set(\"design\");\n }\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","buttons","forEach","relativeTo","append","newSection","sectionInserted","some","index","splice","unshift","menubar","menuitem","title","mutatedMenubar","Array","from","Object","entries","_ref","menu","contextmenu","contextmenuItems","split","menuitems","concat","filter","join","getDocumentationLink","pluginName","url","getMetadata","getElement","isConnected","indexOf","submenuitem","menuItems","ui","registry","getAll","submenuitemtitle","addNestedMenuItem","text","getSubmenuItems","newSubmenu","trim","replace","regexPattern","RegExp","target","hasAttribute","mode","set"],"mappings":"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"} \ No newline at end of file diff --git a/lib/editor/tiny/amd/src/editor.js b/lib/editor/tiny/amd/src/editor.js index 05980f3ac45..695f8db966c 100644 --- a/lib/editor/tiny/amd/src/editor.js +++ b/lib/editor/tiny/amd/src/editor.js @@ -27,7 +27,7 @@ import {getDefaultConfiguration} from './defaults'; import {getTinyMCE, baseUrl} from './loader'; import * as Options from './options'; import {addToolbarButton, addToolbarButtons, addToolbarSection, - removeToolbarButton, removeSubmenuItem} from './utils'; + removeToolbarButton, removeSubmenuItem, updateEditorState} from './utils'; /** * Storage for the TinyMCE instances on the page. @@ -372,6 +372,12 @@ const getStandardConfig = (target, tinyMCE, options, plugins) => { open: e.type == "openwindow" }); }); + + target.addEventListener('form:editorUpdated', function() { + updateEditorState(editor, target); + }); + + target.dispatchEvent(new Event('form:editorUpdated')); }, }); diff --git a/lib/editor/tiny/amd/src/utils.js b/lib/editor/tiny/amd/src/utils.js index 0113a92c1ad..ebaeb78be4a 100644 --- a/lib/editor/tiny/amd/src/utils.js +++ b/lib/editor/tiny/amd/src/utils.js @@ -367,3 +367,17 @@ export const removeMenubarItems = (menubar, section, menuitems) => { return menubar; }; + +/** + * Updates the state of the editor. + * + * @param {TinyMCE} editor + * @param {HTMLElement} target + */ +export const updateEditorState = (editor, target) => { + if (target.hasAttribute('readonly')) { + editor.mode.set("readonly"); + } else { + editor.mode.set("design"); + } +};