From c9517f21808d07707cd7dc84bca01164a06e31a3 Mon Sep 17 00:00:00 2001 From: meirzamoodle Date: Mon, 4 Dec 2023 15:54:09 +0700 Subject: [PATCH 1/2] MDL-80257 editory_tiny: Remove tox-tinymce-aux z-index Remove .tox-tinymce-aux from style.css because z-index is already defined in TinyMCE skin.css --- lib/editor/tiny/styles.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/editor/tiny/styles.css b/lib/editor/tiny/styles.css index bbca493c270..0f4058e1ebe 100644 --- a/lib/editor/tiny/styles.css +++ b/lib/editor/tiny/styles.css @@ -1,7 +1,3 @@ -.jsenabled .tox-tinymce-aux { - z-index: 1000; -} - .jsenabled .tox-shadowhost.tox-fullscreen, .jsenabled .tox.tox-tinymce.tox-fullscreen { z-index: 1050; From f456416318821879f22cbe671e77d129713afef2 Mon Sep 17 00:00:00 2001 From: meirzamoodle Date: Mon, 4 Dec 2023 15:54:33 +0700 Subject: [PATCH 2/2] MDL-80257 editory_tiny: Implement ui_mode: split Since version 6.4 and later, TinyMCE had a feature `ui_mode: split`. It enables support for editors in scrollable containers, and Moodle has some pages, especially the course pages, that are using the scrollable containers. Therefore, all workaround that is addressed should be replaced by the feature to avoid future problems regarding the overflow and the z-index issues. --- lib/editor/tiny/amd/build/editor.min.js | 2 +- lib/editor/tiny/amd/build/editor.min.js.map | 2 +- lib/editor/tiny/amd/build/options.min.js | 2 +- lib/editor/tiny/amd/build/options.min.js.map | 2 +- lib/editor/tiny/amd/src/editor.js | 93 ++------------------ lib/editor/tiny/amd/src/options.js | 8 -- lib/editor/tiny/classes/editor.php | 3 - 7 files changed, 9 insertions(+), 103 deletions(-) diff --git a/lib/editor/tiny/amd/build/editor.min.js b/lib/editor/tiny/amd/build/editor.min.js index 5bf8f160d96..83e5d49a222 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||"auto",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",entity_encoding:"raw",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,target)=>{let expectedEditingAreaHeight=0;expectedEditingAreaHeight=target.clientHeight?target.clientHeight:target.rows*(parseFloat(window.getComputedStyle(target).lineHeight)||22),editor.getContainer().querySelector(".tox-sidebar-wrap").clientHeight{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 target.dataset.fieldtype="editor",instanceMap.set(target,editor),editor.on("remove",(_ref2=>{let{target:target}=_ref2;instanceMap.delete(target.targetElm),target.targetElm.dataset.fieldtype=null})),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||"auto",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",entity_encoding:"raw",ui_mode:"split",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,target)=>{let expectedEditingAreaHeight=0;expectedEditingAreaHeight=target.clientHeight?target.clientHeight:target.rows*(parseFloat(window.getComputedStyle(target).lineHeight)||22),editor.getContainer().querySelector(".tox-sidebar-wrap").clientHeight{const{pluginNames:pluginNames,pluginConfig:pluginConfig}=pluginValues,instanceConfig=getStandardConfig(target,0,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,0,options,pluginValues),[editor]=await tinyMCE.init(instanceConfig);return target.dataset.fieldtype="editor",instanceMap.set(target,editor),editor.on("remove",(_ref2=>{let{target:target}=_ref2;instanceMap.delete(target.targetElm),target.targetElm.dataset.fieldtype=null})),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 0459b7ed314..3109560abe8 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, 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 * Adjust the editor size base on the target element.\n *\n * @param {TinyMCE} editor TinyMCE editor\n * @param {Node} target Target element\n */\nconst adjustEditorSize = (editor, target) => {\n let expectedEditingAreaHeight = 0;\n if (target.clientHeight) {\n expectedEditingAreaHeight = target.clientHeight;\n } else {\n // If the target element is hidden, we cannot get the lineHeight of the target element.\n // We don't have a proper way to retrieve the general lineHeight of the theme, so we use 22 here, it's equivalent to 1.5em.\n expectedEditingAreaHeight = target.rows * (parseFloat(window.getComputedStyle(target).lineHeight) || 22);\n }\n const currentEditingAreaHeight = editor.getContainer().querySelector('.tox-sidebar-wrap').clientHeight;\n if (currentEditingAreaHeight < expectedEditingAreaHeight) {\n // Change the height based on the target element's height.\n editor.getContainer().querySelector('.tox-sidebar-wrap').style.height = `${expectedEditingAreaHeight}px`;\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.\n // In some cases, E.g.: The target is an advanced element, it will be hidden. We cannot get the height at this time.\n // So set the height to auto, and adjust it later by adjustEditorSize().\n height: target.clientHeight || 'auto',\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 // Stored text in non-entity form.\n // https://www.tiny.cloud/docs/tinymce/6/content-filtering/#entity_encoding\n // eslint-disable-next-line camelcase\n entity_encoding: \"raw\",\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 // Adjust the editor size.\n adjustEditorSize(editor, target);\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 // Update the textarea when the editor to set the field type for Behat.\n target.dataset.fieldtype = 'editor';\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 target.targetElm.dataset.fieldtype = null;\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","content_css","css","convert_urls","a11y_advanced_options","extended_valid_elements","xss_sanitization","quickbars_insert_toolbar","block_formats","skin","promotion","branding","table_header_type","entity_encoding","setup","editor","Options","register","on","contentWindow","this","iframeElement","expectedEditingAreaHeight","rows","parseFloat","window","getComputedStyle","lineHeight","getContainer","style","adjustEditorSize","nestedmenu","container","menuContainer","parentNode","appendChild","nestMenu","e","params","open","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","dataset","fieldtype","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 * Adjust the editor size base on the target element.\n *\n * @param {TinyMCE} editor TinyMCE editor\n * @param {Node} target Target element\n */\nconst adjustEditorSize = (editor, target) => {\n let expectedEditingAreaHeight = 0;\n if (target.clientHeight) {\n expectedEditingAreaHeight = target.clientHeight;\n } else {\n // If the target element is hidden, we cannot get the lineHeight of the target element.\n // We don't have a proper way to retrieve the general lineHeight of the theme, so we use 22 here, it's equivalent to 1.5em.\n expectedEditingAreaHeight = target.rows * (parseFloat(window.getComputedStyle(target).lineHeight) || 22);\n }\n const currentEditingAreaHeight = editor.getContainer().querySelector('.tox-sidebar-wrap').clientHeight;\n if (currentEditingAreaHeight < expectedEditingAreaHeight) {\n // Change the height based on the target element's height.\n editor.getContainer().querySelector('.tox-sidebar-wrap').style.height = `${expectedEditingAreaHeight}px`;\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.\n // In some cases, E.g.: The target is an advanced element, it will be hidden. We cannot get the height at this time.\n // So set the height to auto, and adjust it later by adjustEditorSize().\n height: target.clientHeight || 'auto',\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 // Stored text in non-entity form.\n // https://www.tiny.cloud/docs/tinymce/6/content-filtering/#entity_encoding\n // eslint-disable-next-line camelcase\n entity_encoding: \"raw\",\n\n // Enable support for editors in scrollable containers.\n // https://www.tiny.cloud/docs/tinymce/6/ui-mode-configuration-options/#ui_mode\n // eslint-disable-next-line camelcase\n ui_mode: 'split',\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 // Adjust the editor size.\n adjustEditorSize(editor, target);\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 // Update the textarea when the editor to set the field type for Behat.\n target.dataset.fieldtype = 'editor';\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 target.targetElm.dataset.fieldtype = null;\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","content_css","css","convert_urls","a11y_advanced_options","extended_valid_elements","xss_sanitization","quickbars_insert_toolbar","block_formats","skin","promotion","branding","table_header_type","entity_encoding","ui_mode","setup","editor","Options","register","on","contentWindow","this","iframeElement","expectedEditingAreaHeight","rows","parseFloat","window","getComputedStyle","lineHeight","getContainer","style","adjustEditorSize","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","dataset","fieldtype","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/options.min.js b/lib/editor/tiny/amd/build/options.min.js index 278d8a231cc..2012ae7f7c9 100644 --- a/lib/editor/tiny/amd/build/options.min.js +++ b/lib/editor/tiny/amd/build/options.min.js @@ -1,3 +1,3 @@ -define("editor_tiny/options",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.registerPlaceholderSelectors=_exports.register=_exports.getPluginOptionName=_exports.getPlaceholderSelectors=_exports.getNestedMenu=_exports.getMoodleLang=_exports.getInitialPluginConfiguration=_exports.getFilepickers=_exports.getFilePicker=_exports.getDraftItemId=_exports.getCurrentLanguage=_exports.getContextId=void 0;_exports.register=(editor,options)=>{const registerOption=editor.options.register,setOption=editor.options.set;registerOption("moodle:contextid",{processor:"number",default:0}),setOption("moodle:contextid",options.context),registerOption("moodle:filepickers",{processor:"object",default:{}}),setOption("moodle:filepickers",options.filepicker),registerOption("moodle:draftitemid",{processor:"number",default:0}),setOption("moodle:draftitemid",options.draftitemid),registerOption("moodle:currentLanguage",{processor:"string",default:"en"}),setOption("moodle:currentLanguage",options.currentLanguage),registerOption("moodle:language",{processor:"object",default:{}}),setOption("moodle:language",options.language),registerOption("moodle:placeholderSelectors",{processor:"array",default:[]}),setOption("moodle:placeholderSelectors",options.placeholderSelectors),registerOption("moodle:nestedmenu",{processor:"boolean",default:!1}),setOption("moodle:nestedmenu",options.nestedmenu)};_exports.getContextId=editor=>editor.options.get("moodle:contextid");_exports.getDraftItemId=editor=>editor.options.get("moodle:draftitemid");const getFilepickers=editor=>editor.options.get("moodle:filepickers");_exports.getFilepickers=getFilepickers;_exports.getFilePicker=(editor,type)=>getFilepickers(editor)[type];_exports.getMoodleLang=editor=>editor.options.get("moodle:language");_exports.getCurrentLanguage=editor=>editor.options.get("moodle:currentLanguage");_exports.getNestedMenu=editor=>editor.options.get("moodle:nestedmenu");_exports.getInitialPluginConfiguration=options=>{const config={};return Object.entries(options.plugins).forEach((_ref=>{var _pluginConfig$config;let[pluginName,pluginConfig]=_ref;Object.entries(null!==(_pluginConfig$config=pluginConfig.config)&&void 0!==_pluginConfig$config?_pluginConfig$config:{}).forEach((_ref2=>{let[optionName,value]=_ref2;config[getPluginOptionName(pluginName,optionName)]=value}))})),config};const getPluginOptionName=(pluginName,optionName)=>"".concat(pluginName,":").concat(optionName);_exports.getPluginOptionName=getPluginOptionName;const getPlaceholderSelectors=editor=>editor.options.get("moodle:placeholderSelectors");_exports.getPlaceholderSelectors=getPlaceholderSelectors;_exports.registerPlaceholderSelectors=(editor,selectors)=>{if(selectors.length){let existingData=getPlaceholderSelectors(editor);existingData=existingData.concat(selectors),editor.options.set("moodle:placeholderSelectors",existingData)}}})); +define("editor_tiny/options",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.registerPlaceholderSelectors=_exports.register=_exports.getPluginOptionName=_exports.getPlaceholderSelectors=_exports.getMoodleLang=_exports.getInitialPluginConfiguration=_exports.getFilepickers=_exports.getFilePicker=_exports.getDraftItemId=_exports.getCurrentLanguage=_exports.getContextId=void 0;_exports.register=(editor,options)=>{const registerOption=editor.options.register,setOption=editor.options.set;registerOption("moodle:contextid",{processor:"number",default:0}),setOption("moodle:contextid",options.context),registerOption("moodle:filepickers",{processor:"object",default:{}}),setOption("moodle:filepickers",options.filepicker),registerOption("moodle:draftitemid",{processor:"number",default:0}),setOption("moodle:draftitemid",options.draftitemid),registerOption("moodle:currentLanguage",{processor:"string",default:"en"}),setOption("moodle:currentLanguage",options.currentLanguage),registerOption("moodle:language",{processor:"object",default:{}}),setOption("moodle:language",options.language),registerOption("moodle:placeholderSelectors",{processor:"array",default:[]}),setOption("moodle:placeholderSelectors",options.placeholderSelectors)};_exports.getContextId=editor=>editor.options.get("moodle:contextid");_exports.getDraftItemId=editor=>editor.options.get("moodle:draftitemid");const getFilepickers=editor=>editor.options.get("moodle:filepickers");_exports.getFilepickers=getFilepickers;_exports.getFilePicker=(editor,type)=>getFilepickers(editor)[type];_exports.getMoodleLang=editor=>editor.options.get("moodle:language");_exports.getCurrentLanguage=editor=>editor.options.get("moodle:currentLanguage");_exports.getInitialPluginConfiguration=options=>{const config={};return Object.entries(options.plugins).forEach((_ref=>{var _pluginConfig$config;let[pluginName,pluginConfig]=_ref;Object.entries(null!==(_pluginConfig$config=pluginConfig.config)&&void 0!==_pluginConfig$config?_pluginConfig$config:{}).forEach((_ref2=>{let[optionName,value]=_ref2;config[getPluginOptionName(pluginName,optionName)]=value}))})),config};const getPluginOptionName=(pluginName,optionName)=>"".concat(pluginName,":").concat(optionName);_exports.getPluginOptionName=getPluginOptionName;const getPlaceholderSelectors=editor=>editor.options.get("moodle:placeholderSelectors");_exports.getPlaceholderSelectors=getPlaceholderSelectors;_exports.registerPlaceholderSelectors=(editor,selectors)=>{if(selectors.length){let existingData=getPlaceholderSelectors(editor);existingData=existingData.concat(selectors),editor.options.set("moodle:placeholderSelectors",existingData)}}})); //# sourceMappingURL=options.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/amd/build/options.min.js.map b/lib/editor/tiny/amd/build/options.min.js.map index c59dfdc7d08..1c56d2067d9 100644 --- a/lib/editor/tiny/amd/build/options.min.js.map +++ b/lib/editor/tiny/amd/build/options.min.js.map @@ -1 +1 @@ -{"version":3,"file":"options.min.js","sources":["../src/options.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Option helper for TinyMCE Editor Manager.\n *\n * @module editor_tiny/options\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst optionContextId = 'moodle:contextid';\nconst optionDraftItemId = 'moodle:draftitemid';\nconst filePickers = 'moodle:filepickers';\nconst optionsMoodleLang = 'moodle:language';\nconst currentLanguage = 'moodle:currentLanguage';\nconst optionPlaceholderSelectors = 'moodle:placeholderSelectors';\nconst nestedMenu = 'moodle:nestedmenu';\n\nexport const register = (editor, options) => {\n const registerOption = editor.options.register;\n const setOption = editor.options.set;\n\n registerOption(optionContextId, {\n processor: 'number',\n \"default\": 0,\n });\n setOption(optionContextId, options.context);\n\n registerOption(filePickers, {\n processor: 'object',\n \"default\": {},\n });\n setOption(filePickers, options.filepicker);\n\n registerOption(optionDraftItemId, {\n processor: 'number',\n \"default\": 0,\n });\n setOption(optionDraftItemId, options.draftitemid);\n\n registerOption(currentLanguage, {\n processor: 'string',\n \"default\": 'en',\n });\n setOption(currentLanguage, options.currentLanguage);\n\n // This is primarily used by the media plugin, but it may be re-used elsewhere so is included here as it is large.\n registerOption(optionsMoodleLang, {\n processor: 'object',\n \"default\": {},\n });\n setOption(optionsMoodleLang, options.language);\n\n registerOption(optionPlaceholderSelectors, {\n processor: 'array',\n \"default\": [],\n });\n setOption(optionPlaceholderSelectors, options.placeholderSelectors);\n\n registerOption(nestedMenu, {\n processor: 'boolean',\n \"default\": false,\n });\n setOption(nestedMenu, options.nestedmenu);\n};\n\nexport const getContextId = (editor) => editor.options.get(optionContextId);\nexport const getDraftItemId = (editor) => editor.options.get(optionDraftItemId);\nexport const getFilepickers = (editor) => editor.options.get(filePickers);\nexport const getFilePicker = (editor, type) => getFilepickers(editor)[type];\nexport const getMoodleLang = (editor) => editor.options.get(optionsMoodleLang);\nexport const getCurrentLanguage = (editor) => editor.options.get(currentLanguage);\nexport const getNestedMenu = (editor) => editor.options.get(nestedMenu);\n\n/**\n * Get a set of namespaced options for all defined plugins.\n *\n * @param {object} options\n * @returns {object}\n */\nexport const getInitialPluginConfiguration = (options) => {\n const config = {};\n\n Object.entries(options.plugins).forEach(([pluginName, pluginConfig]) => {\n const values = Object.entries(pluginConfig.config ?? {});\n values.forEach(([optionName, value]) => {\n config[getPluginOptionName(pluginName, optionName)] = value;\n });\n });\n\n return config;\n};\n\n/**\n * Get the namespaced option name for a plugin.\n *\n * @param {string} pluginName\n * @param {string} optionName\n * @returns {string}\n */\nexport const getPluginOptionName = (pluginName, optionName) => `${pluginName}:${optionName}`;\n\n/**\n * Get the placeholder selectors.\n *\n * @param {TinyMCE} editor\n * @returns {array}\n */\nexport const getPlaceholderSelectors = (editor) => editor.options.get(optionPlaceholderSelectors);\n\n/**\n * Register placeholder selectos.\n *\n * @param {TinyMCE} editor\n * @param {array} selectors\n */\nexport const registerPlaceholderSelectors = (editor, selectors) => {\n if (selectors.length) {\n let existingData = getPlaceholderSelectors(editor);\n existingData = existingData.concat(selectors);\n editor.options.set(optionPlaceholderSelectors, existingData);\n }\n};\n"],"names":["editor","options","registerOption","register","setOption","set","processor","context","filepicker","draftitemid","currentLanguage","language","placeholderSelectors","nestedmenu","get","getFilepickers","type","config","Object","entries","plugins","forEach","_ref","pluginName","pluginConfig","_ref2","optionName","value","getPluginOptionName","getPlaceholderSelectors","selectors","length","existingData","concat"],"mappings":"kdA+BwB,CAACA,OAAQC,iBACvBC,eAAiBF,OAAOC,QAAQE,SAChCC,UAAYJ,OAAOC,QAAQI,IAEjCH,eAZoB,mBAYY,CAC5BI,UAAW,iBACA,IAEfF,UAhBoB,mBAgBOH,QAAQM,SAEnCL,eAhBgB,qBAgBY,CACxBI,UAAW,iBACA,KAEfF,UApBgB,qBAoBOH,QAAQO,YAE/BN,eAvBsB,qBAuBY,CAC9BI,UAAW,iBACA,IAEfF,UA3BsB,qBA2BOH,QAAQQ,aAErCP,eA1BoB,yBA0BY,CAC5BI,UAAW,iBACA,OAEfF,UA9BoB,yBA8BOH,QAAQS,iBAGnCR,eAlCsB,kBAkCY,CAC9BI,UAAW,iBACA,KAEfF,UAtCsB,kBAsCOH,QAAQU,UAErCT,eAtC+B,8BAsCY,CACvCI,UAAW,gBACA,KAEfF,UA1C+B,8BA0COH,QAAQW,sBAE9CV,eA3Ce,oBA2CY,CACvBI,UAAW,mBACA,IAEfF,UA/Ce,oBA+COH,QAAQY,mCAGLb,QAAWA,OAAOC,QAAQa,IAxD/B,4CAyDOd,QAAWA,OAAOC,QAAQa,IAxD/B,4BAyDbC,eAAkBf,QAAWA,OAAOC,QAAQa,IAxDrC,oFAyDS,CAACd,OAAQgB,OAASD,eAAef,QAAQgB,6BACxChB,QAAWA,OAAOC,QAAQa,IAzD9B,+CA0DSd,QAAWA,OAAOC,QAAQa,IAzDrC,iDA0DMd,QAAWA,OAAOC,QAAQa,IAxDrC,4DAgE2Bb,gBACpCgB,OAAS,UAEfC,OAAOC,QAAQlB,QAAQmB,SAASC,SAAQC,oCAAEC,WAAYC,mBACnCN,OAAOC,qCAAQK,aAAaP,4DAAU,IAC9CI,SAAQI,YAAEC,WAAYC,aACzBV,OAAOW,oBAAoBL,WAAYG,aAAeC,YAIvDV,cAUEW,oBAAsB,CAACL,WAAYG,uBAAkBH,uBAAcG,mEAQnEG,wBAA2B7B,QAAWA,OAAOC,QAAQa,IA7F/B,8HAqGS,CAACd,OAAQ8B,gBAC7CA,UAAUC,OAAQ,KACdC,aAAeH,wBAAwB7B,QAC3CgC,aAAeA,aAAaC,OAAOH,WACnC9B,OAAOC,QAAQI,IAzGY,8BAyGoB2B"} \ No newline at end of file +{"version":3,"file":"options.min.js","sources":["../src/options.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Option helper for TinyMCE Editor Manager.\n *\n * @module editor_tiny/options\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst optionContextId = 'moodle:contextid';\nconst optionDraftItemId = 'moodle:draftitemid';\nconst filePickers = 'moodle:filepickers';\nconst optionsMoodleLang = 'moodle:language';\nconst currentLanguage = 'moodle:currentLanguage';\nconst optionPlaceholderSelectors = 'moodle:placeholderSelectors';\n\nexport const register = (editor, options) => {\n const registerOption = editor.options.register;\n const setOption = editor.options.set;\n\n registerOption(optionContextId, {\n processor: 'number',\n \"default\": 0,\n });\n setOption(optionContextId, options.context);\n\n registerOption(filePickers, {\n processor: 'object',\n \"default\": {},\n });\n setOption(filePickers, options.filepicker);\n\n registerOption(optionDraftItemId, {\n processor: 'number',\n \"default\": 0,\n });\n setOption(optionDraftItemId, options.draftitemid);\n\n registerOption(currentLanguage, {\n processor: 'string',\n \"default\": 'en',\n });\n setOption(currentLanguage, options.currentLanguage);\n\n // This is primarily used by the media plugin, but it may be re-used elsewhere so is included here as it is large.\n registerOption(optionsMoodleLang, {\n processor: 'object',\n \"default\": {},\n });\n setOption(optionsMoodleLang, options.language);\n\n registerOption(optionPlaceholderSelectors, {\n processor: 'array',\n \"default\": [],\n });\n setOption(optionPlaceholderSelectors, options.placeholderSelectors);\n};\n\nexport const getContextId = (editor) => editor.options.get(optionContextId);\nexport const getDraftItemId = (editor) => editor.options.get(optionDraftItemId);\nexport const getFilepickers = (editor) => editor.options.get(filePickers);\nexport const getFilePicker = (editor, type) => getFilepickers(editor)[type];\nexport const getMoodleLang = (editor) => editor.options.get(optionsMoodleLang);\nexport const getCurrentLanguage = (editor) => editor.options.get(currentLanguage);\n\n/**\n * Get a set of namespaced options for all defined plugins.\n *\n * @param {object} options\n * @returns {object}\n */\nexport const getInitialPluginConfiguration = (options) => {\n const config = {};\n\n Object.entries(options.plugins).forEach(([pluginName, pluginConfig]) => {\n const values = Object.entries(pluginConfig.config ?? {});\n values.forEach(([optionName, value]) => {\n config[getPluginOptionName(pluginName, optionName)] = value;\n });\n });\n\n return config;\n};\n\n/**\n * Get the namespaced option name for a plugin.\n *\n * @param {string} pluginName\n * @param {string} optionName\n * @returns {string}\n */\nexport const getPluginOptionName = (pluginName, optionName) => `${pluginName}:${optionName}`;\n\n/**\n * Get the placeholder selectors.\n *\n * @param {TinyMCE} editor\n * @returns {array}\n */\nexport const getPlaceholderSelectors = (editor) => editor.options.get(optionPlaceholderSelectors);\n\n/**\n * Register placeholder selectos.\n *\n * @param {TinyMCE} editor\n * @param {array} selectors\n */\nexport const registerPlaceholderSelectors = (editor, selectors) => {\n if (selectors.length) {\n let existingData = getPlaceholderSelectors(editor);\n existingData = existingData.concat(selectors);\n editor.options.set(optionPlaceholderSelectors, existingData);\n }\n};\n"],"names":["editor","options","registerOption","register","setOption","set","processor","context","filepicker","draftitemid","currentLanguage","language","placeholderSelectors","get","getFilepickers","type","config","Object","entries","plugins","forEach","_ref","pluginName","pluginConfig","_ref2","optionName","value","getPluginOptionName","getPlaceholderSelectors","selectors","length","existingData","concat"],"mappings":"2bA8BwB,CAACA,OAAQC,iBACvBC,eAAiBF,OAAOC,QAAQE,SAChCC,UAAYJ,OAAOC,QAAQI,IAEjCH,eAXoB,mBAWY,CAC5BI,UAAW,iBACA,IAEfF,UAfoB,mBAeOH,QAAQM,SAEnCL,eAfgB,qBAeY,CACxBI,UAAW,iBACA,KAEfF,UAnBgB,qBAmBOH,QAAQO,YAE/BN,eAtBsB,qBAsBY,CAC9BI,UAAW,iBACA,IAEfF,UA1BsB,qBA0BOH,QAAQQ,aAErCP,eAzBoB,yBAyBY,CAC5BI,UAAW,iBACA,OAEfF,UA7BoB,yBA6BOH,QAAQS,iBAGnCR,eAjCsB,kBAiCY,CAC9BI,UAAW,iBACA,KAEfF,UArCsB,kBAqCOH,QAAQU,UAErCT,eArC+B,8BAqCY,CACvCI,UAAW,gBACA,KAEfF,UAzC+B,8BAyCOH,QAAQW,6CAGrBZ,QAAWA,OAAOC,QAAQY,IAjD/B,4CAkDOb,QAAWA,OAAOC,QAAQY,IAjD/B,4BAkDbC,eAAkBd,QAAWA,OAAOC,QAAQY,IAjDrC,oFAkDS,CAACb,OAAQe,OAASD,eAAed,QAAQe,6BACxCf,QAAWA,OAAOC,QAAQY,IAlD9B,+CAmDSb,QAAWA,OAAOC,QAAQY,IAlDrC,iEA0DsBZ,gBACpCe,OAAS,UAEfC,OAAOC,QAAQjB,QAAQkB,SAASC,SAAQC,oCAAEC,WAAYC,mBACnCN,OAAOC,qCAAQK,aAAaP,4DAAU,IAC9CI,SAAQI,YAAEC,WAAYC,aACzBV,OAAOW,oBAAoBL,WAAYG,aAAeC,YAIvDV,cAUEW,oBAAsB,CAACL,WAAYG,uBAAkBH,uBAAcG,mEAQnEG,wBAA2B5B,QAAWA,OAAOC,QAAQY,IArF/B,8HA6FS,CAACb,OAAQ6B,gBAC7CA,UAAUC,OAAQ,KACdC,aAAeH,wBAAwB5B,QAC3C+B,aAAeA,aAAaC,OAAOH,WACnC7B,OAAOC,QAAQI,IAjGY,8BAiGoB0B"} \ 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 6eb4ad43060..2ba99ec41cf 100644 --- a/lib/editor/tiny/amd/src/editor.js +++ b/lib/editor/tiny/amd/src/editor.js @@ -174,68 +174,6 @@ const getPlugins = ({plugins = null} = {}) => { return {}; }; -/** - * Nest the dropdown menu inside the parent DOM. - * - * The TinyMCE menu has a significant issue with the Overflow style, - * and the Boost theme heavily uses Overflow for drawer navigation. - * Moving the menu container into the parent editor container makes it work correctly. - * - * @param {object} editor - */ - const nestMenu = (editor) => { - const container = editor.getContainer(); - const menuContainer = document.querySelector('body > .tox.tox-tinymce-aux'); - container.parentNode.appendChild(menuContainer); -}; - -/** - * Fix the Tiny menu position if the editor is in fullscreen mode on the Boost theme. - * - * The boost theme makes the TinyMCE editor rendered in a scrollable container, - * scrolling the editor’s container will cause TinyMCE UI elements to be detached from the anchor. - * Therefore, to keep the tinyMCE menu in the correct position, - * adjustments must be made on the page drawers style. - * - * @param {object} params - * @param {Boolean} params.open True if editor in fullscreen mode, otherwise false. - */ -const fixMenuPositionIfInFullsreen = (params) => { - if (params.open) { - // Keep the menu remains visible and properly aligned. - document.querySelector('.tox-fullscreen').style.overflow = 'unset'; - } - - const pageWithDrawers = document.querySelector('#page.drawers'); - if (pageWithDrawers) { - pageWithDrawers.style.overflow = params.open ? "unset" : ""; - } -}; - -/** - * Fix the dialogue window positioning issue of TinyMCE editor in Safari browsers. - * - * When using TinyMCE editor in Safari browsers, a problem may occur where the dialogue - * windows (such as modal dialogs) overlap with page drawers due to a specific behavior - * in Safari's rendering. This function addresses the issue by adjusting the CSS overflow - * property of the page drawers, ensuring they do not obscure the dialogue windows. - * - * @param {object} params - * @param {object} params.browser Browser environment. - * @param {object} params.fsplugin Fullscreen plugin. - * @param {Boolean} params.open True if the dialogue window opens, otherwise false. - */ -const fixDialoguePositionIfOpen = (params) => { - // To avoid modification the existing overflow value that has been set fixMenuPositionIfInFullsreen(), - // the fix only applied if the editor not in fullscreen mode. - if (params.browser.isSafari() && !params.fsplugin.isFullscreen()) { - const pageWithDrawers = document.querySelector('#page.drawers'); - if (pageWithDrawers) { - pageWithDrawers.style.overflow = params.open ? "unset" : ""; - } - } -}; - /** * Adjust the editor size base on the target element. * @@ -359,6 +297,11 @@ const getStandardConfig = (target, tinyMCE, options, plugins) => { // eslint-disable-next-line camelcase entity_encoding: "raw", + // Enable support for editors in scrollable containers. + // https://www.tiny.cloud/docs/tinymce/6/ui-mode-configuration-options/#ui_mode + // eslint-disable-next-line camelcase + ui_mode: 'split', + setup: (editor) => { Options.register(editor, options); @@ -377,32 +320,6 @@ const getStandardConfig = (target, tinyMCE, options, plugins) => { adjustEditorSize(editor, target); }); - editor.on('PostRender', function() { - // Nest menu if set. - if (options.nestedmenu) { - nestMenu(editor); - } - }); - - // The Boost and Classic theme cause issues if editor is in fullscreen mode. - // The problem was resolved by changing the overflow value to related elements. - editor.on('FullscreenStateChanged', function(e) { - fixMenuPositionIfInFullsreen({ - open: e.state - }); - }); - - // The Boost theme uses Overflow=auto in the course index drawer, - // it causes the dialogue window to be not correctly displayed in Safari browser. - // The problem was resolved by changing the overflow value to the drawer. - editor.on('OpenWindow CloseWindow', function(e) { - fixDialoguePositionIfOpen({ - browser: tinyMCE.Env.browser, - fsplugin: editor.plugins.fullscreen, - open: e.type == "openwindow" - }); - }); - target.addEventListener('form:editorUpdated', function() { updateEditorState(editor, target); }); diff --git a/lib/editor/tiny/amd/src/options.js b/lib/editor/tiny/amd/src/options.js index 95b2311d86c..8a8d5a9626a 100644 --- a/lib/editor/tiny/amd/src/options.js +++ b/lib/editor/tiny/amd/src/options.js @@ -27,7 +27,6 @@ const filePickers = 'moodle:filepickers'; const optionsMoodleLang = 'moodle:language'; const currentLanguage = 'moodle:currentLanguage'; const optionPlaceholderSelectors = 'moodle:placeholderSelectors'; -const nestedMenu = 'moodle:nestedmenu'; export const register = (editor, options) => { const registerOption = editor.options.register; @@ -69,12 +68,6 @@ export const register = (editor, options) => { "default": [], }); setOption(optionPlaceholderSelectors, options.placeholderSelectors); - - registerOption(nestedMenu, { - processor: 'boolean', - "default": false, - }); - setOption(nestedMenu, options.nestedmenu); }; export const getContextId = (editor) => editor.options.get(optionContextId); @@ -83,7 +76,6 @@ export const getFilepickers = (editor) => editor.options.get(filePickers); export const getFilePicker = (editor, type) => getFilepickers(editor)[type]; export const getMoodleLang = (editor) => editor.options.get(optionsMoodleLang); export const getCurrentLanguage = (editor) => editor.options.get(currentLanguage); -export const getNestedMenu = (editor) => editor.options.get(nestedMenu); /** * Get a set of namespaced options for all defined plugins. diff --git a/lib/editor/tiny/classes/editor.php b/lib/editor/tiny/classes/editor.php index ec8e12e08ba..a7b11cb1eaa 100644 --- a/lib/editor/tiny/classes/editor.php +++ b/lib/editor/tiny/classes/editor.php @@ -194,9 +194,6 @@ class editor extends \texteditor { // Plugin configuration. 'plugins' => $this->manager->get_plugin_configuration($context, $options, $fpoptions, $this), - - // Nest menu inside parent DOM. - 'nestedmenu' => true, ]; if (defined('BEHAT_SITE_RUNNING') && BEHAT_SITE_RUNNING) {