From db8f7fc95a5593db90992a2e9b4e97f58b2e19ea Mon Sep 17 00:00:00 2001 From: Martin Gauk Date: Fri, 16 May 2025 19:07:14 +0200 Subject: [PATCH] MDL-85488 mathjax: refactor loader to enhance configuration and loading --- filter/mathjaxloader/amd/build/loader.min.js | 10 +- .../mathjaxloader/amd/build/loader.min.js.map | 2 +- filter/mathjaxloader/amd/src/loader.js | 118 ++++++++++++------ filter/mathjaxloader/classes/text_filter.php | 4 +- .../lang/en/filter_mathjaxloader.php | 8 +- 5 files changed, 102 insertions(+), 40 deletions(-) diff --git a/filter/mathjaxloader/amd/build/loader.min.js b/filter/mathjaxloader/amd/build/loader.min.js index 20578da35c5..dd8cad82b8b 100644 --- a/filter/mathjaxloader/amd/build/loader.min.js +++ b/filter/mathjaxloader/amd/build/loader.min.js @@ -1,3 +1,11 @@ -define("filter_mathjaxloader/loader",["exports","core_filters/events"],(function(_exports,_events){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.typeset=_exports.contentUpdated=_exports.configure=void 0;_exports.configure=params=>{loadMathJax(params.mathjaxurl,(()=>{window.MathJax&&(window.MathJax.config.locale=params.lang),document.addEventListener(_events.eventTypes.filterContentUpdated,contentUpdated)}))};const typesetNode=node=>{node instanceof HTMLElement&&window.MathJax&&window.MathJax.typesetPromise([node]).then((()=>{(0,_events.notifyFilterContentRenderingComplete)([node])})).catch((e=>{window.console.log(e)}))};_exports.typeset=()=>{const elements=document.getElementsByClassName("filter_mathjaxloader_equation");for(const element of elements)void 0!==window.MathJax&&typesetNode(element)};const contentUpdated=event=>{if(void 0===window.MathJax)return;let listOfElementContainMathJax=[],hasMathJax=!1;event.detail.nodes.forEach((node=>{if(!(node instanceof HTMLElement))return;const mathjaxElements=node.querySelectorAll(".filter_mathjaxloader_equation");mathjaxElements.length>0&&(hasMathJax=!0),listOfElementContainMathJax.push(mathjaxElements)})),hasMathJax&&listOfElementContainMathJax.forEach((mathjaxElements=>{mathjaxElements.forEach((node=>typesetNode(node)))}))};_exports.contentUpdated=contentUpdated;const loadMathJax=(url,callback)=>{const script=document.createElement("script");script.type="text/javascript",script.onload=()=>{callback()},script.src=url,document.getElementsByTagName("head")[0].appendChild(script)}})); +define("filter_mathjaxloader/loader",["exports","core_filters/events"],(function(_exports,_events){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.typeset=_exports.loadMathJax=_exports.contentUpdated=_exports.configure=void 0; +/** + * Mathjax JS Loader. + * + * @module filter_mathjaxloader/loader + * @copyright 2014 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +let mathJaxUrl=null,mathJaxLoaded=null;_exports.configure=params=>{let config={};try{""!==params.mathjaxconfig&&(config=JSON.parse(params.mathjaxconfig))}catch(e){window.console.error("Invalid JSON in mathjaxconfig.",e)}"object"!=typeof config&&(config={}),"object"!=typeof config.loader&&(config.loader={}),Array.isArray(config.loader.load)||(config.loader.load=[]),"object"!=typeof config.startup&&(config.startup={}),config.loader.load.includes("ui/safe")||config.loader.load.push("ui/safe"),config.startup.typeset=!1,config.locale=params.lang,mathJaxUrl=params.mathjaxurl,window.MathJax=config,document.addEventListener(_events.eventTypes.filterContentUpdated,contentUpdated)};const typesetNode=node=>{node instanceof HTMLElement&&loadMathJax().then((()=>{window.MathJax.startup.promise=window.MathJax.startup.promise.then((()=>window.MathJax.typesetPromise([node]))).then((()=>{(0,_events.notifyFilterContentRenderingComplete)([node])})).catch((e=>{window.console.log(e)}))}))};_exports.typeset=()=>{const elements=document.getElementsByClassName("filter_mathjaxloader_equation");for(const element of elements)typesetNode(element)};const contentUpdated=event=>{let listOfElementContainMathJax=[],hasMathJax=!1;event.detail.nodes.forEach((node=>{if(!(node instanceof HTMLElement))return;const mathjaxElements=node.querySelectorAll(".filter_mathjaxloader_equation");mathjaxElements.length>0&&(hasMathJax=!0),listOfElementContainMathJax.push(mathjaxElements)})),hasMathJax&&listOfElementContainMathJax.forEach((mathjaxElements=>{mathjaxElements.forEach((node=>typesetNode(node)))}))};_exports.contentUpdated=contentUpdated;const loadMathJax=()=>{if(!mathJaxLoaded){if(!mathJaxUrl)return Promise.reject(new Error("URL to MathJax not set."));mathJaxLoaded=new Promise(((resolve,reject)=>{const script=document.createElement("script");script.type="text/javascript",script.onload=resolve,script.onerror=reject,script.src=mathJaxUrl,document.getElementsByTagName("head")[0].appendChild(script)}))}return mathJaxLoaded};_exports.loadMathJax=loadMathJax})); //# sourceMappingURL=loader.min.js.map \ No newline at end of file diff --git a/filter/mathjaxloader/amd/build/loader.min.js.map b/filter/mathjaxloader/amd/build/loader.min.js.map index 9e13f474bb2..315522e064d 100644 --- a/filter/mathjaxloader/amd/build/loader.min.js.map +++ b/filter/mathjaxloader/amd/build/loader.min.js.map @@ -1 +1 @@ -{"version":3,"file":"loader.min.js","sources":["../src/loader.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/ //\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 * Mathjax JS Loader.\n *\n * @module filter_mathjaxloader/loader\n * @copyright 2014 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {\n eventTypes,\n notifyFilterContentRenderingComplete,\n} from 'core_filters/events';\n\n/**\n * Called by the filter when it is active on any page.\n * This does not load MathJAX yet - it addes the configuration to the head incase it gets loaded later.\n * It also subscribes to the filter-content-updated event so MathJax can respond to content loaded by Ajax.\n *\n * @param {Object} params List of configuration params containing mathjaxurl, mathjaxconfig (text) and lang\n */\nexport const configure = (params) => {\n loadMathJax(params.mathjaxurl, () => {\n if (window.MathJax) {\n // Let's still set the locale even if the localization is not yet ported to version 3.2.2\n // https://docs.mathjax.org/en/v3.2-latest/upgrading/v2.html#not-yet-ported-to-version-3.\n window.MathJax.config.locale = params.lang;\n }\n\n // Listen for events triggered when new text is added to a page that needs\n // processing by a filter.\n document.addEventListener(eventTypes.filterContentUpdated, contentUpdated);\n });\n};\n\n/**\n * Add the node to the typeset queue.\n *\n * @param {HTMLElement} node The Node to be processed by MathJax\n * @private\n */\nconst typesetNode = (node) => {\n if (!(node instanceof HTMLElement)) {\n // We may have been passed a #text node.\n // These cannot be formatted.\n return;\n }\n\n if (window.MathJax) {\n window.MathJax.typesetPromise([node]).then(() => {\n notifyFilterContentRenderingComplete([node]);\n return;\n })\n .catch(e => {\n window.console.log(e);\n });\n }\n};\n\n/**\n * Called by the filter when an equation is found while rendering the page.\n */\nexport const typeset = () => {\n const elements = document.getElementsByClassName('filter_mathjaxloader_equation');\n for (const element of elements) {\n if (typeof window.MathJax !== \"undefined\") {\n typesetNode(element);\n }\n }\n};\n\n/**\n * Handle content updated events - typeset the new content.\n *\n * @param {CustomEvent} event - Custom event with \"nodes\" indicating the root of the updated nodes.\n */\nexport const contentUpdated = (event) => {\n if (typeof window.MathJax === \"undefined\") {\n return;\n }\n\n let listOfElementContainMathJax = [];\n let hasMathJax = false;\n // The list of HTMLElements in an Array.\n event.detail.nodes.forEach((node) => {\n if (!(node instanceof HTMLElement)) {\n // We may have been passed a #text node.\n return;\n }\n const mathjaxElements = node.querySelectorAll('.filter_mathjaxloader_equation');\n if (mathjaxElements.length > 0) {\n hasMathJax = true;\n }\n listOfElementContainMathJax.push(mathjaxElements);\n });\n\n if (!hasMathJax) {\n return;\n }\n\n listOfElementContainMathJax.forEach((mathjaxElements) => {\n mathjaxElements.forEach((node) => typesetNode(node));\n });\n};\n\n/**\n * Load the MathJax script.\n *\n * @param {String} url The URL of the MathJax script to load.\n * @param {function} callback The function to call when the script has loaded.\n */\nconst loadMathJax = (url, callback) => {\n const script = document.createElement('script');\n script.type = 'text/javascript';\n script.onload = () => {\n callback();\n };\n script.src = url;\n document.getElementsByTagName('head')[0].appendChild(script);\n};\n"],"names":["params","loadMathJax","mathjaxurl","window","MathJax","config","locale","lang","document","addEventListener","eventTypes","filterContentUpdated","contentUpdated","typesetNode","node","HTMLElement","typesetPromise","then","catch","e","console","log","elements","getElementsByClassName","element","event","listOfElementContainMathJax","hasMathJax","detail","nodes","forEach","mathjaxElements","querySelectorAll","length","push","url","callback","script","createElement","type","onload","src","getElementsByTagName","appendChild"],"mappings":"iPAiC0BA,SACtBC,YAAYD,OAAOE,YAAY,KACvBC,OAAOC,UAGPD,OAAOC,QAAQC,OAAOC,OAASN,OAAOO,MAK1CC,SAASC,iBAAiBC,mBAAWC,qBAAsBC,0BAU7DC,YAAeC,OACXA,gBAAgBC,aAMlBZ,OAAOC,SACPD,OAAOC,QAAQY,eAAe,CAACF,OAAOG,MAAK,sDACF,CAACH,UAGzCI,OAAMC,IACHhB,OAAOiB,QAAQC,IAAIF,wBAQR,WACbG,SAAWd,SAASe,uBAAuB,qCAC5C,MAAMC,WAAWF,cACY,IAAnBnB,OAAOC,SACdS,YAAYW,gBAUXZ,eAAkBa,gBACG,IAAnBtB,OAAOC,mBAIdsB,4BAA8B,GAC9BC,YAAa,EAEjBF,MAAMG,OAAOC,MAAMC,SAAShB,YAClBA,gBAAgBC,0BAIhBgB,gBAAkBjB,KAAKkB,iBAAiB,kCAC1CD,gBAAgBE,OAAS,IACzBN,YAAa,GAEjBD,4BAA4BQ,KAAKH,oBAGhCJ,YAILD,4BAA4BI,SAASC,kBACjCA,gBAAgBD,SAAShB,MAASD,YAAYC,yDAUhDb,YAAc,CAACkC,IAAKC,kBAChBC,OAAS7B,SAAS8B,cAAc,UACtCD,OAAOE,KAAO,kBACdF,OAAOG,OAAS,KACZJ,YAEJC,OAAOI,IAAMN,IACb3B,SAASkC,qBAAqB,QAAQ,GAAGC,YAAYN"} \ No newline at end of file +{"version":3,"file":"loader.min.js","sources":["../src/loader.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/ //\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 * Mathjax JS Loader.\n *\n * @module filter_mathjaxloader/loader\n * @copyright 2014 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {\n eventTypes,\n notifyFilterContentRenderingComplete,\n} from 'core_filters/events';\n\n/**\n * URL to MathJax.\n * @type {string|null}\n */\nlet mathJaxUrl = null;\n\n/**\n * Promise that is resolved when MathJax was loaded.\n * @type {Promise|null}\n */\nlet mathJaxLoaded = null;\n\n/**\n * Called by the filter when it is active on any page.\n * This does not load MathJAX yet - it adds the configuration in case it gets loaded later.\n * It also subscribes to the filter-content-updated event so MathJax can respond to content loaded by Ajax.\n *\n * @param {Object} params List of configuration params containing mathjaxurl, mathjaxconfig (text) and lang\n */\nexport const configure = (params) => {\n let config = {};\n try {\n if (params.mathjaxconfig !== '') {\n config = JSON.parse(params.mathjaxconfig);\n }\n }\n catch (e) {\n window.console.error('Invalid JSON in mathjaxconfig.', e);\n }\n if (typeof config != 'object') {\n config = {};\n }\n if (typeof config.loader !== 'object') {\n config.loader = {};\n }\n if (!Array.isArray(config.loader.load)) {\n config.loader.load = [];\n }\n if (typeof config.startup !== 'object') {\n config.startup = {};\n }\n\n // Always ensure that ui/safe is in the list. Otherwise, there is a risk of XSS.\n // https://docs.mathjax.org/en/v3.2-latest/options/safe.html.\n if (!config.loader.load.includes('ui/safe')) {\n config.loader.load.push('ui/safe');\n }\n\n // This filter controls what elements to typeset.\n config.startup.typeset = false;\n\n // Let's still set the locale even if the localization is not yet ported to version 3.2.2\n // https://docs.mathjax.org/en/v3.2-latest/upgrading/v2.html#not-yet-ported-to-version-3.\n config.locale = params.lang;\n\n mathJaxUrl = params.mathjaxurl;\n window.MathJax = config;\n\n // Listen for events triggered when new text is added to a page that needs\n // processing by a filter.\n document.addEventListener(eventTypes.filterContentUpdated, contentUpdated);\n};\n\n/**\n * Add the node to the typeset queue.\n *\n * @param {HTMLElement} node The Node to be processed by MathJax\n * @private\n */\nconst typesetNode = (node) => {\n if (!(node instanceof HTMLElement)) {\n // We may have been passed a #text node.\n // These cannot be formatted.\n return;\n }\n\n loadMathJax().then(() => {\n // Chain the calls to typesetPromise as it is recommended.\n // https://docs.mathjax.org/en/v3.2-latest/web/typeset.html#handling-asynchronous-typesetting.\n window.MathJax.startup.promise = window.MathJax.startup.promise\n .then(() => window.MathJax.typesetPromise([node]))\n .then(() => {\n notifyFilterContentRenderingComplete([node]);\n })\n .catch(e => {\n window.console.log(e);\n });\n });\n};\n\n/**\n * Called by the filter when an equation is found while rendering the page.\n */\nexport const typeset = () => {\n const elements = document.getElementsByClassName('filter_mathjaxloader_equation');\n for (const element of elements) {\n typesetNode(element);\n }\n};\n\n/**\n * Handle content updated events - typeset the new content.\n *\n * @param {CustomEvent} event - Custom event with \"nodes\" indicating the root of the updated nodes.\n */\nexport const contentUpdated = (event) => {\n let listOfElementContainMathJax = [];\n let hasMathJax = false;\n // The list of HTMLElements in an Array.\n event.detail.nodes.forEach((node) => {\n if (!(node instanceof HTMLElement)) {\n // We may have been passed a #text node.\n return;\n }\n const mathjaxElements = node.querySelectorAll('.filter_mathjaxloader_equation');\n if (mathjaxElements.length > 0) {\n hasMathJax = true;\n }\n listOfElementContainMathJax.push(mathjaxElements);\n });\n\n if (!hasMathJax) {\n return;\n }\n\n listOfElementContainMathJax.forEach((mathjaxElements) => {\n mathjaxElements.forEach((node) => typesetNode(node));\n });\n};\n\n/**\n * Load the MathJax script.\n *\n * @return Promise that is resolved when MathJax was loaded.\n */\nexport const loadMathJax = () => {\n if (!mathJaxLoaded) {\n if (!mathJaxUrl) {\n return Promise.reject(new Error('URL to MathJax not set.'));\n }\n\n mathJaxLoaded = new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.type = 'text/javascript';\n script.onload = resolve;\n script.onerror = reject;\n script.src = mathJaxUrl;\n document.getElementsByTagName('head')[0].appendChild(script);\n });\n }\n return mathJaxLoaded;\n};\n"],"names":["mathJaxUrl","mathJaxLoaded","params","config","mathjaxconfig","JSON","parse","e","window","console","error","loader","Array","isArray","load","startup","includes","push","typeset","locale","lang","mathjaxurl","MathJax","document","addEventListener","eventTypes","filterContentUpdated","contentUpdated","typesetNode","node","HTMLElement","loadMathJax","then","promise","typesetPromise","catch","log","elements","getElementsByClassName","element","event","listOfElementContainMathJax","hasMathJax","detail","nodes","forEach","mathjaxElements","querySelectorAll","length","Promise","reject","Error","resolve","script","createElement","type","onload","onerror","src","getElementsByTagName","appendChild"],"mappings":";;;;;;;;IA8BIA,WAAa,KAMbC,cAAgB,wBASMC,aAClBC,OAAS,OAEoB,KAAzBD,OAAOE,gBACPD,OAASE,KAAKC,MAAMJ,OAAOE,gBAGnC,MAAOG,GACHC,OAAOC,QAAQC,MAAM,iCAAkCH,GAEtC,iBAAVJ,SACPA,OAAS,IAEgB,iBAAlBA,OAAOQ,SACdR,OAAOQ,OAAS,IAEfC,MAAMC,QAAQV,OAAOQ,OAAOG,QAC7BX,OAAOQ,OAAOG,KAAO,IAEK,iBAAnBX,OAAOY,UACdZ,OAAOY,QAAU,IAKhBZ,OAAOQ,OAAOG,KAAKE,SAAS,YAC7Bb,OAAOQ,OAAOG,KAAKG,KAAK,WAI5Bd,OAAOY,QAAQG,SAAU,EAIzBf,OAAOgB,OAASjB,OAAOkB,KAEvBpB,WAAaE,OAAOmB,WACpBb,OAAOc,QAAUnB,OAIjBoB,SAASC,iBAAiBC,mBAAWC,qBAAsBC,uBASzDC,YAAeC,OACXA,gBAAgBC,aAMtBC,cAAcC,MAAK,KAGfxB,OAAOc,QAAQP,QAAQkB,QAAUzB,OAAOc,QAAQP,QAAQkB,QACnDD,MAAK,IAAMxB,OAAOc,QAAQY,eAAe,CAACL,SAC1CG,MAAK,sDACmC,CAACH,UAEzCM,OAAM5B,IACHC,OAAOC,QAAQ2B,IAAI7B,2BAQZ,WACb8B,SAAWd,SAASe,uBAAuB,qCAC5C,MAAMC,WAAWF,SAClBT,YAAYW,gBASPZ,eAAkBa,YACvBC,4BAA8B,GAC9BC,YAAa,EAEjBF,MAAMG,OAAOC,MAAMC,SAAShB,YAClBA,gBAAgBC,0BAIhBgB,gBAAkBjB,KAAKkB,iBAAiB,kCAC1CD,gBAAgBE,OAAS,IACzBN,YAAa,GAEjBD,4BAA4BxB,KAAK6B,oBAGhCJ,YAILD,4BAA4BI,SAASC,kBACjCA,gBAAgBD,SAAShB,MAASD,YAAYC,yDASzCE,YAAc,SAClB9B,cAAe,KACXD,kBACMiD,QAAQC,OAAO,IAAIC,MAAM,4BAGpClD,cAAgB,IAAIgD,SAAQ,CAACG,QAASF,gBAC5BG,OAAS9B,SAAS+B,cAAc,UACtCD,OAAOE,KAAO,kBACdF,OAAOG,OAASJ,QAChBC,OAAOI,QAAUP,OACjBG,OAAOK,IAAM1D,WACbuB,SAASoC,qBAAqB,QAAQ,GAAGC,YAAYP,kBAGtDpD"} \ No newline at end of file diff --git a/filter/mathjaxloader/amd/src/loader.js b/filter/mathjaxloader/amd/src/loader.js index a9731469181..f8a382e30c9 100644 --- a/filter/mathjaxloader/amd/src/loader.js +++ b/filter/mathjaxloader/amd/src/loader.js @@ -24,25 +24,67 @@ import { notifyFilterContentRenderingComplete, } from 'core_filters/events'; +/** + * URL to MathJax. + * @type {string|null} + */ +let mathJaxUrl = null; + +/** + * Promise that is resolved when MathJax was loaded. + * @type {Promise|null} + */ +let mathJaxLoaded = null; + /** * Called by the filter when it is active on any page. - * This does not load MathJAX yet - it addes the configuration to the head incase it gets loaded later. + * This does not load MathJAX yet - it adds the configuration in case it gets loaded later. * It also subscribes to the filter-content-updated event so MathJax can respond to content loaded by Ajax. * * @param {Object} params List of configuration params containing mathjaxurl, mathjaxconfig (text) and lang */ export const configure = (params) => { - loadMathJax(params.mathjaxurl, () => { - if (window.MathJax) { - // Let's still set the locale even if the localization is not yet ported to version 3.2.2 - // https://docs.mathjax.org/en/v3.2-latest/upgrading/v2.html#not-yet-ported-to-version-3. - window.MathJax.config.locale = params.lang; + let config = {}; + try { + if (params.mathjaxconfig !== '') { + config = JSON.parse(params.mathjaxconfig); } + } + catch (e) { + window.console.error('Invalid JSON in mathjaxconfig.', e); + } + if (typeof config != 'object') { + config = {}; + } + if (typeof config.loader !== 'object') { + config.loader = {}; + } + if (!Array.isArray(config.loader.load)) { + config.loader.load = []; + } + if (typeof config.startup !== 'object') { + config.startup = {}; + } - // Listen for events triggered when new text is added to a page that needs - // processing by a filter. - document.addEventListener(eventTypes.filterContentUpdated, contentUpdated); - }); + // Always ensure that ui/safe is in the list. Otherwise, there is a risk of XSS. + // https://docs.mathjax.org/en/v3.2-latest/options/safe.html. + if (!config.loader.load.includes('ui/safe')) { + config.loader.load.push('ui/safe'); + } + + // This filter controls what elements to typeset. + config.startup.typeset = false; + + // Let's still set the locale even if the localization is not yet ported to version 3.2.2 + // https://docs.mathjax.org/en/v3.2-latest/upgrading/v2.html#not-yet-ported-to-version-3. + config.locale = params.lang; + + mathJaxUrl = params.mathjaxurl; + window.MathJax = config; + + // Listen for events triggered when new text is added to a page that needs + // processing by a filter. + document.addEventListener(eventTypes.filterContentUpdated, contentUpdated); }; /** @@ -58,15 +100,18 @@ const typesetNode = (node) => { return; } - if (window.MathJax) { - window.MathJax.typesetPromise([node]).then(() => { - notifyFilterContentRenderingComplete([node]); - return; - }) - .catch(e => { - window.console.log(e); - }); - } + loadMathJax().then(() => { + // Chain the calls to typesetPromise as it is recommended. + // https://docs.mathjax.org/en/v3.2-latest/web/typeset.html#handling-asynchronous-typesetting. + window.MathJax.startup.promise = window.MathJax.startup.promise + .then(() => window.MathJax.typesetPromise([node])) + .then(() => { + notifyFilterContentRenderingComplete([node]); + }) + .catch(e => { + window.console.log(e); + }); + }); }; /** @@ -75,9 +120,7 @@ const typesetNode = (node) => { export const typeset = () => { const elements = document.getElementsByClassName('filter_mathjaxloader_equation'); for (const element of elements) { - if (typeof window.MathJax !== "undefined") { - typesetNode(element); - } + typesetNode(element); } }; @@ -87,10 +130,6 @@ export const typeset = () => { * @param {CustomEvent} event - Custom event with "nodes" indicating the root of the updated nodes. */ export const contentUpdated = (event) => { - if (typeof window.MathJax === "undefined") { - return; - } - let listOfElementContainMathJax = []; let hasMathJax = false; // The list of HTMLElements in an Array. @@ -118,15 +157,22 @@ export const contentUpdated = (event) => { /** * Load the MathJax script. * - * @param {String} url The URL of the MathJax script to load. - * @param {function} callback The function to call when the script has loaded. + * @return Promise that is resolved when MathJax was loaded. */ -const loadMathJax = (url, callback) => { - const script = document.createElement('script'); - script.type = 'text/javascript'; - script.onload = () => { - callback(); - }; - script.src = url; - document.getElementsByTagName('head')[0].appendChild(script); +export const loadMathJax = () => { + if (!mathJaxLoaded) { + if (!mathJaxUrl) { + return Promise.reject(new Error('URL to MathJax not set.')); + } + + mathJaxLoaded = new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.onload = resolve; + script.onerror = reject; + script.src = mathJaxUrl; + document.getElementsByTagName('head')[0].appendChild(script); + }); + } + return mathJaxLoaded; }; diff --git a/filter/mathjaxloader/classes/text_filter.php b/filter/mathjaxloader/classes/text_filter.php index 68227d1f6a5..729be926e2e 100644 --- a/filter/mathjaxloader/classes/text_filter.php +++ b/filter/mathjaxloader/classes/text_filter.php @@ -147,7 +147,9 @@ class text_filter extends \core_filters\text_filter { } if ($hasdisplayorinline || $hasextra) { - $PAGE->requires->js_call_amd('filter_mathjaxloader/loader', 'typeset'); + if ($PAGE->requires->should_create_one_time_item_now('filter_mathjaxloader-typeset')) { + $PAGE->requires->js_call_amd('filter_mathjaxloader/loader', 'typeset'); + } return '' . $text . ''; } return $text; diff --git a/filter/mathjaxloader/lang/en/filter_mathjaxloader.php b/filter/mathjaxloader/lang/en/filter_mathjaxloader.php index c3bbe43c41c..11b2285fbdc 100644 --- a/filter/mathjaxloader/lang/en/filter_mathjaxloader.php +++ b/filter/mathjaxloader/lang/en/filter_mathjaxloader.php @@ -40,5 +40,11 @@ This can be useful to save on bandwidth or because of local proxy restrictions. To use a local installation of MathJax, first download the full MathJax library from https://www.mathjax.org/. Then install it on a web server. Finally update the MathJax filter settings httpurl and/or httpsurl to point to the local MathJax.js URL.'; $string['mathjaxsettings'] = 'MathJax configuration'; -$string['mathjaxsettings_desc'] = 'The default MathJax configuration should be appropriate for most users, but MathJax is highly configurable and any of the standard MathJax configuration options can be added here.'; +$string['mathjaxsettings_desc'] = 'The default MathJax configuration should be appropriate for most users, but MathJax is highly configurable and any of the standard MathJax configuration options can be added here as JSON. This field must be empty or contain a valid JSON object! For example, to enable AsciiMath and the zoom feature, set this field to: +
+{
+  "loader": {"load": ["input/asciimath"]},
+  "options": {"menuOptions": {"settings": {"zoom": "DoubleClick"}}}
+}
+
'; $string['privacy:metadata'] = 'The MathJax plugin does not store any personal data.';