From 3019f4e57c30a3053ada11ea751be6d125553374 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Tue, 12 Dec 2023 17:17:23 +0000 Subject: [PATCH] MDL-80393 lang: expose current page language to JS modules. Since 4be39296 the root HTML element contains the iso6391 language code of the current language pack. However various JS modules need access to the original Moodle language pack code, so expose that in the `M.cfg` structure. --- lib/amd/build/local/templates/loader.min.js | 2 +- lib/amd/build/local/templates/loader.min.js.map | 2 +- lib/amd/build/str.min.js | 4 ++-- lib/amd/build/str.min.js.map | 2 +- lib/amd/build/user_date.min.js | 2 +- lib/amd/build/user_date.min.js.map | 2 +- lib/amd/src/local/templates/loader.js | 2 +- lib/amd/src/str.js | 9 ++++----- lib/amd/src/user_date.js | 3 +-- lib/outputrequirementslib.php | 1 + lib/upgrade.txt | 1 + 11 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/amd/build/local/templates/loader.min.js b/lib/amd/build/local/templates/loader.min.js index 405f09eebf3..07fad8d0272 100644 --- a/lib/amd/build/local/templates/loader.min.js +++ b/lib/amd/build/local/templates/loader.min.js @@ -6,6 +6,6 @@ define("core/local/templates/loader",["exports","jquery","core/ajax","core/str", * @copyright 2023 Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 4.3 - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_ajax=_interopRequireDefault(_ajax),str=_interopRequireWildcard(str),config=_interopRequireWildcard(config),_mustache=_interopRequireDefault(_mustache),_localstorage=_interopRequireDefault(_localstorage);class Loader{constructor(){_defineProperty(this,"currentThemeName","")}static getSearchKey(theme,templateName){return"".concat(theme,"/").concat(templateName)}static getTemplate(templateName){let themeName=arguments.length>1&&void 0!==arguments[1]?arguments[1]:config.theme;const searchKey=this.getSearchKey(themeName,templateName),cachedPromise=this.getTemplatePromiseFromCache(searchKey);if(cachedPromise)return cachedPromise;const existingBufferRecords=this.loadTemplateBuffer.filter((record=>record.searchKey===searchKey));if(existingBufferRecords.length)return existingBufferRecords[0].deferred.promise();const parts=templateName.split("/"),component=(0,_utils.getNormalisedComponent)(parts.shift()),name=parts.join("/"),deferred=_jquery.default.Deferred();return this.loadTemplateBuffer.push({component:component,name:name,theme:themeName,searchKey:searchKey,deferred:deferred}),this.processLoadTemplateBuffer(),deferred.promise()}static setTemplateInCache(searchKey,templateSource){this.templateCache.set(searchKey,templateSource)}static getTemplateFromCache(searchKey){return this.templateCache.get(searchKey)}static hasTemplateInCache(searchKey){return this.templateCache.has(searchKey)}static prefetchTemplates(templateNames,themeName){templateNames.forEach((templateName=>this.prefetchTemplate(templateName,themeName)))}static prefetchTemplate(templateName,themeName){const searchKey=this.getSearchKey(themeName,templateName);if(this.hasTemplateInCache(searchKey))return;if(this.loadTemplateBuffer.filter((record=>record.searchKey===searchKey)).length)return;const parts=templateName.split("/"),component=(0,_utils.getNormalisedComponent)(parts.shift()),name=parts.join("/");this.loadTemplateBuffer.push({component:component,name:name,theme:themeName,searchKey:searchKey,deferred:_jquery.default.Deferred()}),this.processLoadTemplateBuffer()}static partialHelper(name){let themeName=arguments.length>1&&void 0!==arguments[1]?arguments[1]:config.theme;const searchKey=this.getSearchKey(themeName,name);return this.hasTemplateInCache(searchKey)||new Error("Failed to pre-fetch the template: ".concat(name)),this.getTemplateFromCache(searchKey)}static scanForPartials(templateSource){const tokens=_mustache.default.parse(templateSource),partials=[],findPartial=(tokens,partials)=>{let i;for(i=0;i"!=token[0]&&"<"!=token[0]||partials.push(token[1]),token.length>4&&findPartial(token[4],partials)}};return findPartial(tokens,partials),partials}static cachePartials(templateName){let themeName=arguments.length>1&&void 0!==arguments[1]?arguments[1]:config.theme,parentage=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[];const searchKey=this.getSearchKey(themeName,templateName);return searchKey in this.cachePartialPromises||(parentage.length||parentage.push(searchKey),this.cachePartialPromises[searchKey]=_jquery.default.Deferred(),this._cachePartials(templateName,themeName,parentage).catch((error=>{this.cachePartialPromises[searchKey].reject(error)}))),this.cachePartialPromises[searchKey]}static async _cachePartials(templateName,themeName,parentage){const searchKey=this.getSearchKey(themeName,templateName),templateSource=await this.getTemplate(templateName,themeName),fetchThemAll=this.scanForPartials(templateSource).filter((partialName=>!(parentage.indexOf("".concat(themeName,"/").concat(partialName))>=0)&&partialName!==templateName)).map((partialName=>(parentage.push("".concat(themeName,"/").concat(partialName)),this.cachePartials(partialName,themeName,parentage))));return await Promise.all(fetchThemAll),this.cachePartialPromises[searchKey].resolve(templateSource)}static processLoadTemplateBuffer(){if(!this.loadTemplateBuffer.length)return;if(this.isLoadingTemplates)return;this.isLoadingTemplates=!0;const templatesToLoad=this.loadTemplateBuffer.slice(),serverRequestsDeferred=_jquery.default.Deferred(),requests=[],templatePromises=templatesToLoad.map((templateData=>{const component=(0,_utils.getNormalisedComponent)(templateData.component),name=templateData.name,searchKey=templateData.searchKey,theme=templateData.theme,templateDeferred=templateData.deferred;let promise=null;if(this.hasTemplateInCache(searchKey))promise=this.getTemplatePromiseFromCache(searchKey);else{requests.push({methodname:"core_output_load_template_with_dependencies",args:{component:component,template:name,themename:theme,lang:(0,_jquery.default)("html").attr("lang").replace(/-/g,"_")}});const index=requests.length-1;promise=serverRequestsDeferred.promise().then((promises=>(templatePromises[searchKey]=promises[index].then((response=>(response.templates.forEach((data=>{data.component=(0,_utils.getNormalisedComponent)(data.component);const tempSearchKey=this.getSearchKey(theme,[data.component,data.name].join("/"));this.setTemplateInCache(tempSearchKey,data.value),config.templaterev>0&&_localstorage.default.set("core_template/".concat(config.templaterev,":").concat(tempSearchKey),data.value)})),response.strings.length&&str.cache_strings(response.strings.map((_ref=>{let{component:component,name:name,value:value}=_ref;return{component:(0,_utils.getNormalisedComponent)(component),key:name,value:value}}))),this.hasTemplateInCache(searchKey)?this.getTemplateFromCache(searchKey):null))),templatePromises[searchKey])))}return promise.then((source=>templateDeferred.resolve(source))).catch((error=>{throw templateDeferred.reject(error),error}))}));requests.length?serverRequestsDeferred.resolve(_ajax.default.call(requests,!0,!1,!1,0,config.templaterev)):serverRequestsDeferred.resolve(),_jquery.default.when.apply(null,templatePromises).then((()=>{this.loadTemplateBuffer.splice(0,templatesToLoad.length),this.isLoadingTemplates=!1,this.processLoadTemplateBuffer()})).catch((()=>{this.loadTemplateBuffer.splice(0,templatesToLoad.length),this.isLoadingTemplates=!1,this.processLoadTemplateBuffer()}))}static getTemplatePromiseFromCache(searchKey){if(searchKey in this.templatePromises)return this.templatePromises[searchKey];if(this.hasTemplateInCache(searchKey)){const templateSource=this.getTemplateFromCache(searchKey);return this.templatePromises[searchKey]=_jquery.default.Deferred().resolve(templateSource).promise(),this.templatePromises[searchKey]}if(config.templaterev<=0)return null;const cached=_localstorage.default.get("core_template/".concat(config.templaterev,":").concat(searchKey));return cached?(this.setTemplateInCache(searchKey,cached),this.templatePromises[searchKey]=_jquery.default.Deferred().resolve(cached).promise(),this.templatePromises[searchKey]):null}}return _exports.default=Loader,_defineProperty(Loader,"loadTemplateBuffer",[]),_defineProperty(Loader,"isLoadingTemplates",!1),_defineProperty(Loader,"templateCache",new Map),_defineProperty(Loader,"templatePromises",{}),_defineProperty(Loader,"cachePartialPromises",[]),_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_ajax=_interopRequireDefault(_ajax),str=_interopRequireWildcard(str),config=_interopRequireWildcard(config),_mustache=_interopRequireDefault(_mustache),_localstorage=_interopRequireDefault(_localstorage);class Loader{constructor(){_defineProperty(this,"currentThemeName","")}static getSearchKey(theme,templateName){return"".concat(theme,"/").concat(templateName)}static getTemplate(templateName){let themeName=arguments.length>1&&void 0!==arguments[1]?arguments[1]:config.theme;const searchKey=this.getSearchKey(themeName,templateName),cachedPromise=this.getTemplatePromiseFromCache(searchKey);if(cachedPromise)return cachedPromise;const existingBufferRecords=this.loadTemplateBuffer.filter((record=>record.searchKey===searchKey));if(existingBufferRecords.length)return existingBufferRecords[0].deferred.promise();const parts=templateName.split("/"),component=(0,_utils.getNormalisedComponent)(parts.shift()),name=parts.join("/"),deferred=_jquery.default.Deferred();return this.loadTemplateBuffer.push({component:component,name:name,theme:themeName,searchKey:searchKey,deferred:deferred}),this.processLoadTemplateBuffer(),deferred.promise()}static setTemplateInCache(searchKey,templateSource){this.templateCache.set(searchKey,templateSource)}static getTemplateFromCache(searchKey){return this.templateCache.get(searchKey)}static hasTemplateInCache(searchKey){return this.templateCache.has(searchKey)}static prefetchTemplates(templateNames,themeName){templateNames.forEach((templateName=>this.prefetchTemplate(templateName,themeName)))}static prefetchTemplate(templateName,themeName){const searchKey=this.getSearchKey(themeName,templateName);if(this.hasTemplateInCache(searchKey))return;if(this.loadTemplateBuffer.filter((record=>record.searchKey===searchKey)).length)return;const parts=templateName.split("/"),component=(0,_utils.getNormalisedComponent)(parts.shift()),name=parts.join("/");this.loadTemplateBuffer.push({component:component,name:name,theme:themeName,searchKey:searchKey,deferred:_jquery.default.Deferred()}),this.processLoadTemplateBuffer()}static partialHelper(name){let themeName=arguments.length>1&&void 0!==arguments[1]?arguments[1]:config.theme;const searchKey=this.getSearchKey(themeName,name);return this.hasTemplateInCache(searchKey)||new Error("Failed to pre-fetch the template: ".concat(name)),this.getTemplateFromCache(searchKey)}static scanForPartials(templateSource){const tokens=_mustache.default.parse(templateSource),partials=[],findPartial=(tokens,partials)=>{let i;for(i=0;i"!=token[0]&&"<"!=token[0]||partials.push(token[1]),token.length>4&&findPartial(token[4],partials)}};return findPartial(tokens,partials),partials}static cachePartials(templateName){let themeName=arguments.length>1&&void 0!==arguments[1]?arguments[1]:config.theme,parentage=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[];const searchKey=this.getSearchKey(themeName,templateName);return searchKey in this.cachePartialPromises||(parentage.length||parentage.push(searchKey),this.cachePartialPromises[searchKey]=_jquery.default.Deferred(),this._cachePartials(templateName,themeName,parentage).catch((error=>{this.cachePartialPromises[searchKey].reject(error)}))),this.cachePartialPromises[searchKey]}static async _cachePartials(templateName,themeName,parentage){const searchKey=this.getSearchKey(themeName,templateName),templateSource=await this.getTemplate(templateName,themeName),fetchThemAll=this.scanForPartials(templateSource).filter((partialName=>!(parentage.indexOf("".concat(themeName,"/").concat(partialName))>=0)&&partialName!==templateName)).map((partialName=>(parentage.push("".concat(themeName,"/").concat(partialName)),this.cachePartials(partialName,themeName,parentage))));return await Promise.all(fetchThemAll),this.cachePartialPromises[searchKey].resolve(templateSource)}static processLoadTemplateBuffer(){if(!this.loadTemplateBuffer.length)return;if(this.isLoadingTemplates)return;this.isLoadingTemplates=!0;const templatesToLoad=this.loadTemplateBuffer.slice(),serverRequestsDeferred=_jquery.default.Deferred(),requests=[],templatePromises=templatesToLoad.map((templateData=>{const component=(0,_utils.getNormalisedComponent)(templateData.component),name=templateData.name,searchKey=templateData.searchKey,theme=templateData.theme,templateDeferred=templateData.deferred;let promise=null;if(this.hasTemplateInCache(searchKey))promise=this.getTemplatePromiseFromCache(searchKey);else{requests.push({methodname:"core_output_load_template_with_dependencies",args:{component:component,template:name,themename:theme,lang:config.language}});const index=requests.length-1;promise=serverRequestsDeferred.promise().then((promises=>(templatePromises[searchKey]=promises[index].then((response=>(response.templates.forEach((data=>{data.component=(0,_utils.getNormalisedComponent)(data.component);const tempSearchKey=this.getSearchKey(theme,[data.component,data.name].join("/"));this.setTemplateInCache(tempSearchKey,data.value),config.templaterev>0&&_localstorage.default.set("core_template/".concat(config.templaterev,":").concat(tempSearchKey),data.value)})),response.strings.length&&str.cache_strings(response.strings.map((_ref=>{let{component:component,name:name,value:value}=_ref;return{component:(0,_utils.getNormalisedComponent)(component),key:name,value:value}}))),this.hasTemplateInCache(searchKey)?this.getTemplateFromCache(searchKey):null))),templatePromises[searchKey])))}return promise.then((source=>templateDeferred.resolve(source))).catch((error=>{throw templateDeferred.reject(error),error}))}));requests.length?serverRequestsDeferred.resolve(_ajax.default.call(requests,!0,!1,!1,0,config.templaterev)):serverRequestsDeferred.resolve(),_jquery.default.when.apply(null,templatePromises).then((()=>{this.loadTemplateBuffer.splice(0,templatesToLoad.length),this.isLoadingTemplates=!1,this.processLoadTemplateBuffer()})).catch((()=>{this.loadTemplateBuffer.splice(0,templatesToLoad.length),this.isLoadingTemplates=!1,this.processLoadTemplateBuffer()}))}static getTemplatePromiseFromCache(searchKey){if(searchKey in this.templatePromises)return this.templatePromises[searchKey];if(this.hasTemplateInCache(searchKey)){const templateSource=this.getTemplateFromCache(searchKey);return this.templatePromises[searchKey]=_jquery.default.Deferred().resolve(templateSource).promise(),this.templatePromises[searchKey]}if(config.templaterev<=0)return null;const cached=_localstorage.default.get("core_template/".concat(config.templaterev,":").concat(searchKey));return cached?(this.setTemplateInCache(searchKey,cached),this.templatePromises[searchKey]=_jquery.default.Deferred().resolve(cached).promise(),this.templatePromises[searchKey]):null}}return _exports.default=Loader,_defineProperty(Loader,"loadTemplateBuffer",[]),_defineProperty(Loader,"isLoadingTemplates",!1),_defineProperty(Loader,"templateCache",new Map),_defineProperty(Loader,"templatePromises",{}),_defineProperty(Loader,"cachePartialPromises",[]),_exports.default})); //# sourceMappingURL=loader.min.js.map \ No newline at end of file diff --git a/lib/amd/build/local/templates/loader.min.js.map b/lib/amd/build/local/templates/loader.min.js.map index 5fad93dacad..c9327ad5e74 100644 --- a/lib/amd/build/local/templates/loader.min.js.map +++ b/lib/amd/build/local/templates/loader.min.js.map @@ -1 +1 @@ -{"version":3,"file":"loader.min.js","sources":["../../../src/local/templates/loader.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport $ from 'jquery';\nimport ajax from 'core/ajax';\nimport * as str from 'core/str';\nimport * as config from 'core/config';\nimport mustache from 'core/mustache';\nimport storage from 'core/localstorage';\nimport {getNormalisedComponent} from 'core/utils';\n\n/**\n * Template this.\n *\n * @module core/local/templates/loader\n * @copyright 2023 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.3\n */\nexport default class Loader {\n /** @var {String} themeName for the current render */\n currentThemeName = '';\n\n /** @var {Object[]} loadTemplateBuffer - List of templates to be loaded */\n static loadTemplateBuffer = [];\n\n /** @var {Bool} isLoadingTemplates - Whether templates are currently being loaded */\n static isLoadingTemplates = false;\n\n /** @var {Map} templateCache - Cache of already loaded template strings */\n static templateCache = new Map();\n\n /** @var {Promise[]} templatePromises - Cache of already loaded template promises */\n static templatePromises = {};\n\n /** @var {Promise[]} cachePartialPromises - Cache of already loaded template partial promises */\n static cachePartialPromises = [];\n\n /**\n * A helper to get the search key\n *\n * @param {string} theme\n * @param {string} templateName\n * @returns {string}\n */\n static getSearchKey(theme, templateName) {\n return `${theme}/${templateName}`;\n }\n\n /**\n * Load a template.\n *\n * @method getTemplate\n * @param {string} templateName - should consist of the component and the name of the template like this:\n * core/menu (lib/templates/menu.mustache) or\n * tool_bananas/yellow (admin/tool/bananas/templates/yellow.mustache)\n * @param {string} [themeName=config.theme] - The theme to load the template from\n * @return {Promise} JQuery promise object resolved when the template has been fetched.\n */\n static getTemplate(templateName, themeName = config.theme) {\n const searchKey = this.getSearchKey(themeName, templateName);\n\n // If we haven't already seen this template then buffer it.\n const cachedPromise = this.getTemplatePromiseFromCache(searchKey);\n if (cachedPromise) {\n return cachedPromise;\n }\n\n // Check the buffer to see if this template has already been added.\n const existingBufferRecords = this.loadTemplateBuffer.filter((record) => record.searchKey === searchKey);\n if (existingBufferRecords.length) {\n // This template is already in the buffer so just return the existing\n // promise. No need to add it to the buffer again.\n return existingBufferRecords[0].deferred.promise();\n }\n\n // This is the first time this has been requested so let's add it to the buffer\n // to be loaded.\n const parts = templateName.split('/');\n const component = getNormalisedComponent(parts.shift());\n const name = parts.join('/');\n const deferred = $.Deferred();\n\n // Add this template to the buffer to be loaded.\n this.loadTemplateBuffer.push({\n component,\n name,\n theme: themeName,\n searchKey,\n deferred,\n });\n\n // We know there is at least one thing in the buffer so kick off a processing run.\n this.processLoadTemplateBuffer();\n return deferred.promise();\n }\n\n /**\n * Store a template in the cache.\n *\n * @param {string} searchKey\n * @param {string} templateSource\n */\n static setTemplateInCache(searchKey, templateSource) {\n // Cache all of the dependent templates because we'll need them to render\n // the requested template.\n this.templateCache.set(searchKey, templateSource);\n }\n\n /**\n * Fetch a template from the cache.\n *\n * @param {string} searchKey\n * @returns {string}\n */\n static getTemplateFromCache(searchKey) {\n return this.templateCache.get(searchKey);\n }\n\n /**\n * Check whether a template is in the cache.\n *\n * @param {string} searchKey\n * @returns {bool}\n */\n static hasTemplateInCache(searchKey) {\n return this.templateCache.has(searchKey);\n }\n\n /**\n * Prefetch a set of templates without rendering them.\n *\n * @param {Array} templateNames The list of templates to fetch\n * @param {string} themeName\n */\n static prefetchTemplates(templateNames, themeName) {\n templateNames.forEach((templateName) => this.prefetchTemplate(templateName, themeName));\n }\n\n /**\n * Prefetech a sginle template without rendering it.\n *\n * @param {string} templateName\n * @param {string} themeName\n */\n static prefetchTemplate(templateName, themeName) {\n const searchKey = this.getSearchKey(themeName, templateName);\n\n // If we haven't already seen this template then buffer it.\n if (this.hasTemplateInCache(searchKey)) {\n return;\n }\n\n // Check the buffer to see if this template has already been added.\n const existingBufferRecords = this.loadTemplateBuffer.filter((record) => record.searchKey === searchKey);\n\n if (existingBufferRecords.length) {\n // This template is already in the buffer so just return the existing promise.\n // No need to add it to the buffer again.\n return;\n }\n\n // This is the first time this has been requested so let's add it to the buffer to be loaded.\n const parts = templateName.split('/');\n const component = getNormalisedComponent(parts.shift());\n const name = parts.join('/');\n\n // Add this template to the buffer to be loaded.\n this.loadTemplateBuffer.push({\n component,\n name,\n theme: themeName,\n searchKey,\n deferred: $.Deferred(),\n });\n\n this.processLoadTemplateBuffer();\n }\n\n /**\n * Load a partial from the cache or ajax.\n *\n * @method partialHelper\n * @param {string} name The partial name to load.\n * @param {string} [themeName = config.theme] The theme to load the partial from.\n * @return {string}\n */\n static partialHelper(name, themeName = config.theme) {\n const searchKey = this.getSearchKey(themeName, name);\n\n if (!this.hasTemplateInCache(searchKey)) {\n new Error(`Failed to pre-fetch the template: ${name}`);\n }\n return this.getTemplateFromCache(searchKey);\n }\n\n /**\n * Scan a template source for partial tags and return a list of the found partials.\n *\n * @method scanForPartials\n * @param {string} templateSource - source template to scan.\n * @return {Array} List of partials.\n */\n static scanForPartials(templateSource) {\n const tokens = mustache.parse(templateSource);\n const partials = [];\n\n const findPartial = (tokens, partials) => {\n let i;\n for (i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (token[0] == '>' || token[0] == '<') {\n partials.push(token[1]);\n }\n if (token.length > 4) {\n findPartial(token[4], partials);\n }\n }\n };\n\n findPartial(tokens, partials);\n\n return partials;\n }\n\n /**\n * Load a template and scan it for partials. Recursively fetch the partials.\n *\n * @method cachePartials\n * @param {string} templateName - should consist of the component and the name of the template like this:\n * core/menu (lib/templates/menu.mustache) or\n * tool_bananas/yellow (admin/tool/bananas/templates/yellow.mustache)\n * @param {string} [themeName=config.theme]\n * @param {Array} parentage - A list of requested partials in this render chain.\n * @return {Promise} JQuery promise object resolved when all partials are in the cache.\n */\n static cachePartials(templateName, themeName = config.theme, parentage = []) {\n const searchKey = this.getSearchKey(themeName, templateName);\n\n if (searchKey in this.cachePartialPromises) {\n return this.cachePartialPromises[searchKey];\n }\n\n // This promise will not be resolved until all child partials are also resolved and ready.\n // We create it here to allow us to check for recursive inclusion of templates.\n // Keep track of the requested partials in this chain.\n if (!parentage.length) {\n parentage.push(searchKey);\n }\n\n this.cachePartialPromises[searchKey] = $.Deferred();\n this._cachePartials(templateName, themeName, parentage).catch((error) => {\n this.cachePartialPromises[searchKey].reject(error);\n });\n\n return this.cachePartialPromises[searchKey];\n }\n\n /**\n * Cache the template partials for the specified template.\n *\n * @param {string} templateName\n * @param {string} themeName\n * @param {array} parentage\n * @returns {promise}\n */\n static async _cachePartials(templateName, themeName, parentage) {\n const searchKey = this.getSearchKey(themeName, templateName);\n const templateSource = await this.getTemplate(templateName, themeName);\n const partials = this.scanForPartials(templateSource);\n const uniquePartials = partials.filter((partialName) => {\n // Check for recursion.\n if (parentage.indexOf(`${themeName}/${partialName}`) >= 0) {\n // Ignore templates which include a parent template already requested in the current chain.\n return false;\n }\n\n // Ignore templates that include themselves.\n return partialName !== templateName;\n });\n\n // Fetch any partial which has not already been fetched.\n const fetchThemAll = uniquePartials.map((partialName) => {\n parentage.push(`${themeName}/${partialName}`);\n return this.cachePartials(partialName, themeName, parentage);\n });\n\n await Promise.all(fetchThemAll);\n return this.cachePartialPromises[searchKey].resolve(templateSource);\n }\n\n /**\n * Take all of the templates waiting in the buffer and load them from the server\n * or from the cache.\n *\n * All of the templates that need to be loaded from the server will be batched up\n * and sent in a single network request.\n */\n static processLoadTemplateBuffer() {\n if (!this.loadTemplateBuffer.length) {\n return;\n }\n\n if (this.isLoadingTemplates) {\n return;\n }\n\n this.isLoadingTemplates = true;\n // Grab any templates waiting in the buffer.\n const templatesToLoad = this.loadTemplateBuffer.slice();\n // This will be resolved with the list of promises for the server request.\n const serverRequestsDeferred = $.Deferred();\n const requests = [];\n // Get a list of promises for each of the templates we need to load.\n const templatePromises = templatesToLoad.map((templateData) => {\n const component = getNormalisedComponent(templateData.component);\n const name = templateData.name;\n const searchKey = templateData.searchKey;\n const theme = templateData.theme;\n const templateDeferred = templateData.deferred;\n let promise = null;\n\n // Double check to see if this template happened to have landed in the\n // cache as a dependency of an earlier template.\n if (this.hasTemplateInCache(searchKey)) {\n // We've seen this template so immediately resolve the existing promise.\n promise = this.getTemplatePromiseFromCache(searchKey);\n } else {\n // We haven't seen this template yet so we need to request it from\n // the server.\n requests.push({\n methodname: 'core_output_load_template_with_dependencies',\n args: {\n component,\n template: name,\n themename: theme,\n lang: $('html').attr('lang').replace(/-/g, '_')\n }\n });\n // Remember the index in the requests list for this template so that\n // we can get the appropriate promise back.\n const index = requests.length - 1;\n\n // The server deferred will be resolved with a list of all of the promises\n // that were sent in the order that they were added to the requests array.\n promise = serverRequestsDeferred.promise()\n .then((promises) => {\n // The promise for this template will be the one that matches the index\n // for it's entry in the requests array.\n //\n // Make sure the promise is added to the promises cache for this template\n // search key so that we don't request it again.\n templatePromises[searchKey] = promises[index].then((response) => {\n // Process all of the template dependencies for this template and add\n // them to the caches so that we don't request them again later.\n response.templates.forEach((data) => {\n data.component = getNormalisedComponent(data.component);\n const tempSearchKey = this.getSearchKey(\n theme,\n [data.component, data.name].join('/'),\n );\n\n // Cache all of the dependent templates because we'll need them to render\n // the requested template.\n this.setTemplateInCache(tempSearchKey, data.value);\n\n if (config.templaterev > 0) {\n // The template cache is enabled - set the value there.\n storage.set(`core_template/${config.templaterev}:${tempSearchKey}`, data.value);\n }\n });\n\n if (response.strings.length) {\n // If we have strings that the template needs then warm the string cache\n // with them now so that we don't need to re-fetch them.\n str.cache_strings(response.strings.map(({component, name, value}) => ({\n component: getNormalisedComponent(component),\n key: name,\n value,\n })));\n }\n\n // Return the original template source that the user requested.\n if (this.hasTemplateInCache(searchKey)) {\n return this.getTemplateFromCache(searchKey);\n }\n\n return null;\n });\n\n return templatePromises[searchKey];\n });\n }\n\n return promise\n // When we've successfully loaded the template then resolve the deferred\n // in the buffer so that all of the calling code can proceed.\n .then((source) => templateDeferred.resolve(source))\n .catch((error) => {\n // If there was an error loading the template then reject the deferred\n // in the buffer so that all of the calling code can proceed.\n templateDeferred.reject(error);\n // Rethrow for anyone else listening.\n throw error;\n });\n });\n\n if (requests.length) {\n // We have requests to send so resolve the deferred with the promises.\n serverRequestsDeferred.resolve(ajax.call(requests, true, false, false, 0, config.templaterev));\n } else {\n // Nothing to load so we can resolve our deferred.\n serverRequestsDeferred.resolve();\n }\n\n // Once we've finished loading all of the templates then recurse to process\n // any templates that may have been added to the buffer in the time that we\n // were fetching.\n $.when.apply(null, templatePromises)\n .then(() => {\n // Remove the templates we've loaded from the buffer.\n this.loadTemplateBuffer.splice(0, templatesToLoad.length);\n this.isLoadingTemplates = false;\n this.processLoadTemplateBuffer();\n return;\n })\n .catch(() => {\n // Remove the templates we've loaded from the buffer.\n this.loadTemplateBuffer.splice(0, templatesToLoad.length);\n this.isLoadingTemplates = false;\n this.processLoadTemplateBuffer();\n });\n }\n\n /**\n * Search the various caches for a template promise for the given search key.\n * The search key should be in the format //