diff --git a/lib/editor/tiny/amd/build/options.min.js b/lib/editor/tiny/amd/build/options.min.js
index 821fd17067c..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.register=_exports.getPluginOptionName=_exports.getMoodleLang=_exports.getInitialPluginConfiguration=_exports.getFilepickers=_exports.getFilePicker=_exports.getDraftItemId=_exports.getCurrentLanguage=_exports.getContextId=void 0;_exports.register=(editor,options)=>{const registerOption=editor.options.register,setOption=editor.options.set;registerOption("moodle:contextid",{processor:"number",default:0}),setOption("moodle:contextid",options.context),registerOption("moodle:filepickers",{processor:"object",default:{}}),setOption("moodle:filepickers",options.filepicker),registerOption("moodle:draftitemid",{processor:"number",default:0}),setOption("moodle:draftitemid",options.draftitemid),registerOption("moodle:currentLanguage",{processor:"string",default:"en"}),setOption("moodle:currentLanguage",options.currentLanguage),registerOption("moodle:language",{processor:"object",default:{}}),setOption("moodle:language",options.language)};_exports.getContextId=editor=>editor.options.get("moodle:contextid");_exports.getDraftItemId=editor=>editor.options.get("moodle:draftitemid");const getFilepickers=editor=>editor.options.get("moodle:filepickers");_exports.getFilepickers=getFilepickers;_exports.getFilePicker=(editor,type)=>getFilepickers(editor)[type];_exports.getMoodleLang=editor=>editor.options.get("moodle:language");_exports.getCurrentLanguage=editor=>editor.options.get("moodle:currentLanguage");_exports.getInitialPluginConfiguration=options=>{const config={};return Object.entries(options.plugins).forEach((_ref=>{var _pluginConfig$config;let[pluginName,pluginConfig]=_ref;Object.entries(null!==(_pluginConfig$config=pluginConfig.config)&&void 0!==_pluginConfig$config?_pluginConfig$config:{}).forEach((_ref2=>{let[optionName,value]=_ref2;config[getPluginOptionName(pluginName,optionName)]=value}))})),config};const getPluginOptionName=(pluginName,optionName)=>"".concat(pluginName,":").concat(optionName);_exports.getPluginOptionName=getPluginOptionName}));
+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 64c1668b759..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';\n\nexport const register = (editor, options) => {\n const registerOption = editor.options.register;\n const setOption = editor.options.set;\n\n registerOption(optionContextId, {\n processor: 'number',\n \"default\": 0,\n });\n setOption(optionContextId, options.context);\n\n registerOption(filePickers, {\n processor: 'object',\n \"default\": {},\n });\n setOption(filePickers, options.filepicker);\n\n registerOption(optionDraftItemId, {\n processor: 'number',\n \"default\": 0,\n });\n setOption(optionDraftItemId, options.draftitemid);\n\n registerOption(currentLanguage, {\n processor: 'string',\n \"default\": 'en',\n });\n setOption(currentLanguage, options.currentLanguage);\n\n // This is primarily used by the media plugin, but it may be re-used elsewhere so is included here as it is large.\n registerOption(optionsMoodleLang, {\n processor: 'object',\n \"default\": {},\n });\n setOption(optionsMoodleLang, options.language);\n};\n\nexport const getContextId = (editor) => editor.options.get(optionContextId);\nexport const getDraftItemId = (editor) => editor.options.get(optionDraftItemId);\nexport const getFilepickers = (editor) => editor.options.get(filePickers);\nexport const getFilePicker = (editor, type) => getFilepickers(editor)[type];\nexport const getMoodleLang = (editor) => editor.options.get(optionsMoodleLang);\nexport const getCurrentLanguage = (editor) => editor.options.get(currentLanguage);\n\n/**\n * Get a set of namespaced options for all defined plugins.\n *\n * @param {object} options\n * @returns {object}\n */\nexport const getInitialPluginConfiguration = (options) => {\n const config = {};\n\n Object.entries(options.plugins).forEach(([pluginName, pluginConfig]) => {\n const values = Object.entries(pluginConfig.config ?? {});\n values.forEach(([optionName, value]) => {\n config[getPluginOptionName(pluginName, optionName)] = value;\n });\n });\n\n return config;\n};\n\n/**\n * Get the namespaced option name for a plugin.\n *\n * @param {string} pluginName\n * @param {string} optionName\n * @returns {string}\n */\nexport const getPluginOptionName = (pluginName, optionName) => `${pluginName}:${optionName}`;\n"],"names":["editor","options","registerOption","register","setOption","set","processor","context","filepicker","draftitemid","currentLanguage","language","get","getFilepickers","type","config","Object","entries","plugins","forEach","_ref","pluginName","pluginConfig","_ref2","optionName","value","getPluginOptionName"],"mappings":"oXA6BwB,CAACA,OAAQC,iBACvBC,eAAiBF,OAAOC,QAAQE,SAChCC,UAAYJ,OAAOC,QAAQI,IAEjCH,eAVoB,mBAUY,CAC5BI,UAAW,iBACA,IAEfF,UAdoB,mBAcOH,QAAQM,SAEnCL,eAdgB,qBAcY,CACxBI,UAAW,iBACA,KAEfF,UAlBgB,qBAkBOH,QAAQO,YAE/BN,eArBsB,qBAqBY,CAC9BI,UAAW,iBACA,IAEfF,UAzBsB,qBAyBOH,QAAQQ,aAErCP,eAxBoB,yBAwBY,CAC5BI,UAAW,iBACA,OAEfF,UA5BoB,yBA4BOH,QAAQS,iBAGnCR,eAhCsB,kBAgCY,CAC9BI,UAAW,iBACA,KAEfF,UApCsB,kBAoCOH,QAAQU,iCAGZX,QAAWA,OAAOC,QAAQW,IA1C/B,4CA2COZ,QAAWA,OAAOC,QAAQW,IA1C/B,4BA2CbC,eAAkBb,QAAWA,OAAOC,QAAQW,IA1CrC,oFA2CS,CAACZ,OAAQc,OAASD,eAAeb,QAAQc,6BACxCd,QAAWA,OAAOC,QAAQW,IA3C9B,+CA4CSZ,QAAWA,OAAOC,QAAQW,IA3CrC,iEAmDsBX,gBACpCc,OAAS,UAEfC,OAAOC,QAAQhB,QAAQiB,SAASC,SAAQC,oCAAEC,WAAYC,mBACnCN,OAAOC,qCAAQK,aAAaP,4DAAU,IAC9CI,SAAQI,YAAEC,WAAYC,aACzBV,OAAOW,oBAAoBL,WAAYG,aAAeC,YAIvDV,cAUEW,oBAAsB,CAACL,WAAYG,uBAAkBH,uBAAcG"}
\ No newline at end of file
+{"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/options.js b/lib/editor/tiny/amd/src/options.js
index 490aaa0f274..8a8d5a9626a 100644
--- a/lib/editor/tiny/amd/src/options.js
+++ b/lib/editor/tiny/amd/src/options.js
@@ -26,6 +26,7 @@ const optionDraftItemId = 'moodle:draftitemid';
const filePickers = 'moodle:filepickers';
const optionsMoodleLang = 'moodle:language';
const currentLanguage = 'moodle:currentLanguage';
+const optionPlaceholderSelectors = 'moodle:placeholderSelectors';
export const register = (editor, options) => {
const registerOption = editor.options.register;
@@ -61,6 +62,12 @@ export const register = (editor, options) => {
"default": {},
});
setOption(optionsMoodleLang, options.language);
+
+ registerOption(optionPlaceholderSelectors, {
+ processor: 'array',
+ "default": [],
+ });
+ setOption(optionPlaceholderSelectors, options.placeholderSelectors);
};
export const getContextId = (editor) => editor.options.get(optionContextId);
@@ -97,3 +104,25 @@ export const getInitialPluginConfiguration = (options) => {
* @returns {string}
*/
export const getPluginOptionName = (pluginName, optionName) => `${pluginName}:${optionName}`;
+
+/**
+ * Get the placeholder selectors.
+ *
+ * @param {TinyMCE} editor
+ * @returns {array}
+ */
+export const getPlaceholderSelectors = (editor) => editor.options.get(optionPlaceholderSelectors);
+
+/**
+ * Register placeholder selectos.
+ *
+ * @param {TinyMCE} editor
+ * @param {array} selectors
+ */
+export const registerPlaceholderSelectors = (editor, selectors) => {
+ if (selectors.length) {
+ let existingData = getPlaceholderSelectors(editor);
+ existingData = existingData.concat(selectors);
+ editor.options.set(optionPlaceholderSelectors, existingData);
+ }
+};
diff --git a/lib/editor/tiny/classes/editor.php b/lib/editor/tiny/classes/editor.php
index 39a9bcd97f3..9356645e8ce 100644
--- a/lib/editor/tiny/classes/editor.php
+++ b/lib/editor/tiny/classes/editor.php
@@ -183,10 +183,24 @@ class editor extends \texteditor {
'available' => get_string_manager()->get_list_of_languages()
],
+ // Placeholder selectors.
+ // Some contents (Example: placeholder elements) are only shown in the editor, and not to users. It is unrelated to the
+ // real display. We created a list of placeholder selectors, so we can decide to or not to apply rules, styles... to
+ // these elements.
+ // The default of this list will be empty.
+ // Other plugins can register their placeholder elements to placeholderSelectors list by calling
+ // editor_tiny/options::registerPlaceholderSelectors.
+ 'placeholderSelectors' => [],
+
// Plugin configuration.
'plugins' => $this->manager->get_plugin_configuration($context, $options, $fpoptions, $this),
];
+ if (defined('BEHAT_SITE_RUNNING') && BEHAT_SITE_RUNNING) {
+ // Add sample selectors for Behat test.
+ $config->placeholderSelectors = ['.behat-tinymce-placeholder'];
+ }
+
foreach ($fpoptions as $fp) {
// Guess the draftitemid for the editor.
// Note: This is the best we can do at the moment.
diff --git a/lib/editor/tiny/upgrade.txt b/lib/editor/tiny/upgrade.txt
new file mode 100644
index 00000000000..2073ca4a4bf
--- /dev/null
+++ b/lib/editor/tiny/upgrade.txt
@@ -0,0 +1,10 @@
+This files describes API changes in /lib/editor/tiny/* - TinyMCE editor,
+information provided here is intended especially for developers.
+
+=== 4.1.1 ===
+
+* A list of placeholder selectors was created and can be accessed by placeholderSelectors options. The purpose of this list
+ is to indicate the contents that are only shown in the editor and not to the users, by that way, we can decide to apply or
+ not to apply rules, styles, etc... to these contents.
+ Other Tiny plugins can register their placeholder elements to placeholderSelectors list
+ by calling editor_tiny/options::registerPlaceholderSelectors.