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 //