diff --git a/lib/editor/tiny/amd/build/editor.min.js b/lib/editor/tiny/amd/build/editor.min.js
index ec662ee4209..b8112be357f 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,language:lang,content_css:[options.css],convert_urls:!1,a11y_advanced_options:!0,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)}))}});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,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,"|")),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 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()})),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,language:lang,content_css:[options.css],convert_urls:!1,a11y_advanced_options:!0,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)}))}});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,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,"|")),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 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()})),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 0696a47ae25..5fb86bb9bf5 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 <http://www.gnu.org/licenses/>.\n\n/**\n * TinyMCE Editor Manager.\n *\n * @module editor_tiny/editor\n * @copyright  2022 Andrew Lyons <andrew@nicols.co.uk>\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<Node, Editor>}\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<TinyMCE>} 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 * 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        // 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        // 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        // Disable some of the standard paragraph levels.\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    });\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    // 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<TinyMCE>} 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    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","content_css","css","convert_urls","a11y_advanced_options","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","toolbar","getEditorConfiguration","pluginValues","instanceConfig","menu","file","items","format","replace","replaceAll","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 <http://www.gnu.org/licenses/>.\n\n/**\n * TinyMCE Editor Manager.\n *\n * @module editor_tiny/editor\n * @copyright  2022 Andrew Lyons <andrew@nicols.co.uk>\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<Node, Editor>}\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<TinyMCE>} 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 * 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        // Base the height on the size of the text area.\n        height: target.clientHeight,\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        // 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        // Disable some of the standard paragraph levels.\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    });\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    // 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<TinyMCE>} 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    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","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","toolbar","getEditorConfiguration","pluginValues","instanceConfig","menu","file","items","format","replace","replaceAll","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/src/editor.js b/lib/editor/tiny/amd/src/editor.js
index 7c07215fe1f..f2915a34a12 100644
--- a/lib/editor/tiny/amd/src/editor.js
+++ b/lib/editor/tiny/amd/src/editor.js
@@ -209,6 +209,13 @@ const getStandardConfig = (target, tinyMCE, options, plugins) => {
         // https://www.tiny.cloud/docs/tinymce/6/editor-important-options/#target
         target,
 
+        // https://www.tiny.cloud/docs/tinymce/6/customize-ui/#set-maximum-and-minimum-heights-and-widths
+        // Set the minimum height to the smallest height that we can fit the Menu bar, Tool bar, Status bar and the text area.
+        // eslint-disable-next-line camelcase
+        min_height: 175,
+        // Base the height on the size of the text area.
+        height: target.clientHeight,
+
         // Set the language.
         // https://www.tiny.cloud/docs/tinymce/6/ui-localization/#language
         // eslint-disable-next-line camelcase