From 0a86c6a123dd952d24cdeb407a34aefd5b736b2f Mon Sep 17 00:00:00 2001
From: Paul Holden <paulh@moodle.com>
Date: Mon, 23 Jan 2023 23:59:48 +0000
Subject: [PATCH] MDL-77003 javascript: improve string helper JSON detection.

Changes in fbc2732d made the JS template string helper method
too strict in regards to what it considers a valid JSON object.

Co-authored-by: Mike Churchward <mike@brickfieldlabs.ie>
---
 lib/amd/build/templates.min.js     | 2 +-
 lib/amd/build/templates.min.js.map | 2 +-
 lib/amd/src/templates.js           | 3 +--
 3 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/lib/amd/build/templates.min.js b/lib/amd/build/templates.min.js
index 33748237397..c33aa8dd70d 100644
--- a/lib/amd/build/templates.min.js
+++ b/lib/amd/build/templates.min.js
@@ -6,6 +6,6 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  * @since      2.9
  */
-define("core/templates",["core/mustache","jquery","core/ajax","core/str","core/notification","core/url","core/config","core/localstorage","core/icon_system","core_filters/events","core/yui","core/log","core/truncate","core/user_date","core/pending"],(function(mustache,$,ajax,str,notification,coreurl,config,storage,IconSystem,filterEvents,Y,Log,Truncate,UserDate,Pending){var uniqInstances=0,templateCache={},templatePromises={},cachePartialPromises={},iconSystem={},loadTemplateBuffer=[],isLoadingTemplates=!1,disallowedNestedHelpers=["js"],getNormalisedComponent=function(component){return component&&"moodle"!==component&&"core"!==component?component:"core"},getTemplatePromiseFromCache=function(searchKey){if(searchKey in templatePromises)return templatePromises[searchKey];if(searchKey in templateCache)return templatePromises[searchKey]=$.Deferred().resolve(templateCache[searchKey]).promise(),templatePromises[searchKey];if(M.cfg.templaterev<=0)return null;var cached=storage.get("core_template/"+M.cfg.templaterev+":"+searchKey);return cached?(templateCache[searchKey]=cached,templatePromises[searchKey]=$.Deferred().resolve(cached).promise(),templatePromises[searchKey]):null},processLoadTemplateBuffer=function(){if(loadTemplateBuffer.length&&!isLoadingTemplates){isLoadingTemplates=!0;var templatesToLoad=loadTemplateBuffer.slice(),serverRequestsDeferred=$.Deferred(),requests=[],templatePromises=templatesToLoad.map((function(templateData){var component=getNormalisedComponent(templateData.component),name=templateData.name,searchKey=templateData.searchKey,theme=templateData.theme,templateDeferred=templateData.deferred,promise=null,cachedPromise=getTemplatePromiseFromCache(searchKey);if(cachedPromise)promise=cachedPromise;else{requests.push({methodname:"core_output_load_template_with_dependencies",args:{component:component,template:name,themename:theme,lang:$("html").attr("lang").replace(/-/g,"_")}});var index=requests.length-1;promise=serverRequestsDeferred.promise().then((function(promises){return templatePromises[searchKey]=promises[index].then((function(response){var templateSource=null;return response.templates.forEach((function(data){data.component=getNormalisedComponent(data.component);var tempSearchKey=[theme,data.component,data.name].join("/");templateCache[tempSearchKey]=data.value,M.cfg.templaterev>0&&storage.set("core_template/"+M.cfg.templaterev+":"+tempSearchKey,data.value),data.component==component&&data.name==name&&(templateSource=data.value)})),response.strings.length&&str.cache_strings(response.strings.map((function(data){return{component:getNormalisedComponent(data.component),key:data.name,value:data.value}}))),templateSource})),templatePromises[searchKey]}))}return promise.then((function(source){return templateDeferred.resolve(source)})).catch((function(error){throw templateDeferred.reject(error),error}))}));requests.length?serverRequestsDeferred.resolve(ajax.call(requests,!0,!1,!1,0,M.cfg.templaterev)):serverRequestsDeferred.resolve(),$.when.apply(null,templatePromises).then((function(){loadTemplateBuffer.splice(0,templatesToLoad.length),isLoadingTemplates=!1,processLoadTemplateBuffer()})).catch((function(){loadTemplateBuffer.splice(0,templatesToLoad.length),isLoadingTemplates=!1,processLoadTemplateBuffer()}))}},Renderer=function(){this.requiredStrings=[],this.requiredJS=[],this.requiredDates=[],this.currentThemeName=""};Renderer.prototype.requiredStrings=null,Renderer.prototype.requiredDates=[],Renderer.prototype.requiredJS=null,Renderer.prototype.currentThemeName="",Renderer.prototype.getTemplate=function(templateName){var currentTheme=this.currentThemeName,searchKey=currentTheme+"/"+templateName,cachedPromise=getTemplatePromiseFromCache(searchKey);if(cachedPromise)return cachedPromise;var existingBufferRecords=loadTemplateBuffer.filter((function(record){return record.searchKey==searchKey}));if(existingBufferRecords.length)return existingBufferRecords[0].deferred.promise();var parts=templateName.split("/"),component=getNormalisedComponent(parts.shift()),name=parts.join("/"),deferred=$.Deferred();return loadTemplateBuffer.push({component:component,name:name,theme:currentTheme,searchKey:searchKey,deferred:deferred}),processLoadTemplateBuffer(),deferred.promise()},Renderer.prototype.prefetchTemplates=function(templateNames,currentTheme){templateNames.forEach((function(templateName){var searchKey=currentTheme+"/"+templateName;if(!getTemplatePromiseFromCache(searchKey)&&!loadTemplateBuffer.filter((function(record){return record.searchKey==searchKey})).length){var parts=templateName.split("/"),component=getNormalisedComponent(parts.shift()),name=parts.join("/");loadTemplateBuffer.push({component:component,name:name,theme:currentTheme,searchKey:searchKey,deferred:$.Deferred()})}})),processLoadTemplateBuffer()},Renderer.prototype.partialHelper=function(name){var searchKey=this.currentThemeName+"/"+name;return searchKey in templateCache||notification.exception(new Error("Failed to pre-fetch the template: "+name)),templateCache[searchKey]},Renderer.prototype.renderIcon=function(key,component,title){var modulename=config.iconsystemmodule;component=getNormalisedComponent(component);var ready=$.Deferred();return require([modulename],(function(System){var system=new System;system instanceof IconSystem?(iconSystem=system,system.init().then(ready.resolve).catch(notification.exception)):ready.reject("Invalid icon system specified"+config.iconsystemmodule)})),ready.then(function(iconSystem){return this.getTemplate(iconSystem.getTemplateName())}.bind(this)).then((function(template){return iconSystem.renderIcon(key,component,title,template)}))},Renderer.prototype.pixHelper=function(context,sectionText,helper){var parts=sectionText.split(","),key="",component="",text="";parts.length>0&&(key=helper(parts.shift().trim(),context)),parts.length>0&&(component=helper(parts.shift().trim(),context)),parts.length>0&&(text=helper(parts.join(",").trim(),context));var templateName=iconSystem.getTemplateName(),searchKey=this.currentThemeName+"/"+templateName,template=templateCache[searchKey];return component=getNormalisedComponent(component),key=key.replace(/&#x2F;/gi,"/"),iconSystem.renderIcon(key,component,text,template)},Renderer.prototype.jsHelper=function(context,sectionText,helper){return this.requiredJS.push(helper(sectionText,context)),""},Renderer.prototype.stringHelper=function(context,sectionText,helper){var parts=sectionText.split(","),key="",component="",param="";if(parts.length>0&&(key=parts.shift().trim()),parts.length>0&&(component=parts.shift().trim()),parts.length>0&&(param=parts.join(",").trim()),component=getNormalisedComponent(component),""!==param&&(param=helper(param,context)),0===param.indexOf('{"'))try{const parsedParam=JSON.parse(param);parsedParam&&"object"==typeof parsedParam&&(param=parsedParam)}catch(err){window.console.warn(err.message)}var index=this.requiredStrings.length;return this.requiredStrings.push({key:key,component:component,param:param}),"[[_s"+index+"]]"},Renderer.prototype.cleanStringHelper=function(context,sectionText,helper){return this.stringHelper(context,sectionText,helper).replace("s","c")},Renderer.prototype.quoteHelper=function(context,sectionText,helper){var content=helper(sectionText.trim(),context);return content=(content=JSON.stringify(content)).replace(/([{}]{2,3})/g,"{{=<% %>=}}$1<%={{ }}=%>")},Renderer.prototype.shortenTextHelper=function(context,sectionText,helper){var parts=sectionText.match(/(.*?),(.*)/),length=parts[1].trim(),content=helper(parts[2].trim(),context);return Truncate.truncate(content,{length:length,words:!0,ellipsis:"..."})},Renderer.prototype.userDateHelper=function(context,sectionText,helper){var parts=sectionText.match(/(.*?),(.*)/),timestamp=helper(parts[1].trim(),context),format=helper(parts[2].trim(),context),index=this.requiredDates.length;return this.requiredDates.push({timestamp:timestamp,format:format}),"[[_t_"+index+"]]"},Renderer.prototype.addHelperFunction=function(helperFunction,context){return function(){return function(sectionText,helper){var originalHelpers=disallowedNestedHelpers.reduce((function(carry,name){return context.hasOwnProperty(name)&&(carry[name]=context[name]),carry}),{});disallowedNestedHelpers.forEach((function(helperName){context[helperName]=function(){return""}}));var result=helperFunction.apply(this,[context,sectionText,helper]);for(var name in originalHelpers)context[name]=originalHelpers[name];return result}.bind(this)}.bind(this)},Renderer.prototype.addHelpers=function(context,themeName){this.currentThemeName=themeName,this.requiredStrings=[],this.requiredJS=[],context.uniqid=uniqInstances++,context.str=this.addHelperFunction(this.stringHelper,context),context.cleanstr=this.addHelperFunction(this.cleanStringHelper,context),context.pix=this.addHelperFunction(this.pixHelper,context),context.js=this.addHelperFunction(this.jsHelper,context),context.quote=this.addHelperFunction(this.quoteHelper,context),context.shortentext=this.addHelperFunction(this.shortenTextHelper,context),context.userdate=this.addHelperFunction(this.userDateHelper,context),context.globals={config:config},context.currentTheme=themeName},Renderer.prototype.getJS=function(){var js="";return this.requiredJS.length>0&&(js=this.requiredJS.join(";\n")),js},Renderer.prototype.treatStringsInContent=function(content,strings){var treated,index,strIndex,walker,char,strFinal,isClean,pattern=/\[\[_(s|c)\d+\]\]/;do{for(treated="",index=content.search(pattern);index>-1;){treated+=content.substring(0,index),isClean="c"==(content=content.substr(index))[3],strIndex="",walker=4,char=content.substr(walker,1);do{strIndex+=char,walker++,char=content.substr(walker,1)}while("]"!=char);void 0===(strFinal=strings[parseInt(strIndex,10)])&&(Log.debug("Could not find string for pattern [[_"+(isClean?"c":"s")+strIndex+"]]."),strFinal=""),isClean&&(strFinal=mustache.escape(strFinal)),treated+=strFinal,index=(content=content.substr(6+strIndex.length)).search(pattern)}index=(content=treated+content).search(pattern)}while(index>-1);return content},Renderer.prototype.treatDatesInContent=function(content,dates){return dates.forEach((function(date,index){var re=new RegExp("\\[\\[_t_"+index+"\\]\\]","g");content=content.replace(re,date)})),content},Renderer.prototype.doRender=function(templateSource,context,themeName){this.currentThemeName=themeName;var iconTemplate=iconSystem.getTemplateName(),pendingPromise=new Pending("core/templates:doRender");return this.getTemplate(iconTemplate).then(function(){this.addHelpers(context,themeName);var result=mustache.render(templateSource,context,this.partialHelper.bind(this));return $.Deferred().resolve(result.trim(),this.getJS()).promise()}.bind(this)).then(function(html,js){return this.requiredStrings.length>0?str.get_strings(this.requiredStrings).then(function(strings){return this.requiredDates=this.requiredDates.map(function(date){return{timestamp:this.treatStringsInContent(date.timestamp,strings),format:this.treatStringsInContent(date.format,strings)}}.bind(this)),html=this.treatStringsInContent(html,strings),js=this.treatStringsInContent(js,strings),$.Deferred().resolve(html,js).promise()}.bind(this)):$.Deferred().resolve(html,js).promise()}.bind(this)).then(function(html,js){return this.requiredDates.length>0?UserDate.get(this.requiredDates).then(function(dates){return html=this.treatDatesInContent(html,dates),js=this.treatDatesInContent(js,dates),$.Deferred().resolve(html,js).promise()}.bind(this)):$.Deferred().resolve(html,js).promise()}.bind(this)).then((function(html,js){return pendingPromise.resolve(),$.Deferred().resolve(html,js).promise()}))};var runTemplateJS=function(source){if(""!==source.trim()){var newscript=$("<script>").attr("type","text/javascript").html(source);$("head").append(newscript)}},domReplace=function(element,newHTML,newJS,replaceChildNodes){var replaceNode=$(element);if(replaceNode.length){var newNodes=$(newHTML);return replaceChildNodes?(new Y.NodeList(replaceNode.children().get()).destroy(!0),replaceNode.empty(),replaceNode.append(newNodes)):(new Y.NodeList(replaceNode.get()).destroy(!0),replaceNode.replaceWith(newNodes)),runTemplateJS(newJS),filterEvents.notifyFilterContentUpdated(newNodes),newNodes.get()}return[]};Renderer.prototype.scanForPartials=function(templateSource){var tokens=mustache.parse(templateSource),partials=[],findPartial=function(tokens,partials){var i,token;for(i=0;i<tokens.length;i++)">"!=(token=tokens[i])[0]&&"<"!=token[0]||partials.push(token[1]),token.length>4&&findPartial(token[4],partials)};return findPartial(tokens,partials),partials},Renderer.prototype.cachePartials=function(templateName,parentage){var searchKey=this.currentThemeName+"/"+templateName;return searchKey in cachePartialPromises||(parentage=parentage||[searchKey],cachePartialPromises[searchKey]=$.Deferred(),this.getTemplate(templateName).then(function(templateSource){var fetchThemAll=this.scanForPartials(templateSource).filter(function(partialName){return!(parentage.indexOf(this.currentThemeName+"/"+partialName)>=0)&&partialName!=templateName}.bind(this)).map(function(partialName){return parentage.push(this.currentThemeName+"/"+partialName),this.cachePartials(partialName,parentage)}.bind(this));return $.when.apply($,fetchThemAll).then((function(){return cachePartialPromises[searchKey].resolve(templateSource)}))}.bind(this)).catch(cachePartialPromises[searchKey].reject)),cachePartialPromises[searchKey]},Renderer.prototype.render=function(templateName,context,themeName){void 0===themeName&&(themeName=config.theme),this.currentThemeName=themeName;var modulename=config.iconsystemmodule,ready=$.Deferred();return require([modulename],(function(System){var system=new System;system instanceof IconSystem?(iconSystem=system,system.init().then(ready.resolve).catch(notification.exception)):ready.reject("Invalid icon system specified"+config.iconsystem)})),ready.then(function(){return this.cachePartials(templateName)}.bind(this)).then(function(templateSource){return this.doRender(templateSource,context,themeName)}.bind(this))};return{render:function(templateName,context,themeName){return(new Renderer).render(templateName,context,themeName)},prefetchTemplates:function(templateNames,themeName){var renderer=new Renderer;return void 0===themeName&&(themeName=config.theme),renderer.prefetchTemplates(templateNames,themeName)},renderForPromise:function(templateName,context,themeName){return(new Renderer).render(templateName,context,themeName).then((function(html,js){return{html:html,js:js}}))},renderPix:function(key,component,title){return(new Renderer).renderIcon(key,getNormalisedComponent(component),title)},runTemplateJS:runTemplateJS,replaceNodeContents:function(element,newHTML,newJS){return domReplace(element,newHTML,newJS,!0)},replaceNode:function(element,newHTML,newJS){return domReplace(element,newHTML,newJS,!1)},prependNodeContents:function(element,html,js){return function(element,html,js){var node=$(element);if(node.length){var newContent=$(html);return node.prepend(newContent),runTemplateJS(js),filterEvents.notifyFilterContentUpdated(node),newContent.get()}return[]}(element,html,js)},appendNodeContents:function(element,html,js){return function(element,html,js){var node=$(element);if(node.length){var newContent=$(html);return node.append(newContent),runTemplateJS(js),filterEvents.notifyFilterContentUpdated(node),newContent.get()}return[]}(element,html,js)}}}));
+define("core/templates",["core/mustache","jquery","core/ajax","core/str","core/notification","core/url","core/config","core/localstorage","core/icon_system","core_filters/events","core/yui","core/log","core/truncate","core/user_date","core/pending"],(function(mustache,$,ajax,str,notification,coreurl,config,storage,IconSystem,filterEvents,Y,Log,Truncate,UserDate,Pending){var uniqInstances=0,templateCache={},templatePromises={},cachePartialPromises={},iconSystem={},loadTemplateBuffer=[],isLoadingTemplates=!1,disallowedNestedHelpers=["js"],getNormalisedComponent=function(component){return component&&"moodle"!==component&&"core"!==component?component:"core"},getTemplatePromiseFromCache=function(searchKey){if(searchKey in templatePromises)return templatePromises[searchKey];if(searchKey in templateCache)return templatePromises[searchKey]=$.Deferred().resolve(templateCache[searchKey]).promise(),templatePromises[searchKey];if(M.cfg.templaterev<=0)return null;var cached=storage.get("core_template/"+M.cfg.templaterev+":"+searchKey);return cached?(templateCache[searchKey]=cached,templatePromises[searchKey]=$.Deferred().resolve(cached).promise(),templatePromises[searchKey]):null},processLoadTemplateBuffer=function(){if(loadTemplateBuffer.length&&!isLoadingTemplates){isLoadingTemplates=!0;var templatesToLoad=loadTemplateBuffer.slice(),serverRequestsDeferred=$.Deferred(),requests=[],templatePromises=templatesToLoad.map((function(templateData){var component=getNormalisedComponent(templateData.component),name=templateData.name,searchKey=templateData.searchKey,theme=templateData.theme,templateDeferred=templateData.deferred,promise=null,cachedPromise=getTemplatePromiseFromCache(searchKey);if(cachedPromise)promise=cachedPromise;else{requests.push({methodname:"core_output_load_template_with_dependencies",args:{component:component,template:name,themename:theme,lang:$("html").attr("lang").replace(/-/g,"_")}});var index=requests.length-1;promise=serverRequestsDeferred.promise().then((function(promises){return templatePromises[searchKey]=promises[index].then((function(response){var templateSource=null;return response.templates.forEach((function(data){data.component=getNormalisedComponent(data.component);var tempSearchKey=[theme,data.component,data.name].join("/");templateCache[tempSearchKey]=data.value,M.cfg.templaterev>0&&storage.set("core_template/"+M.cfg.templaterev+":"+tempSearchKey,data.value),data.component==component&&data.name==name&&(templateSource=data.value)})),response.strings.length&&str.cache_strings(response.strings.map((function(data){return{component:getNormalisedComponent(data.component),key:data.name,value:data.value}}))),templateSource})),templatePromises[searchKey]}))}return promise.then((function(source){return templateDeferred.resolve(source)})).catch((function(error){throw templateDeferred.reject(error),error}))}));requests.length?serverRequestsDeferred.resolve(ajax.call(requests,!0,!1,!1,0,M.cfg.templaterev)):serverRequestsDeferred.resolve(),$.when.apply(null,templatePromises).then((function(){loadTemplateBuffer.splice(0,templatesToLoad.length),isLoadingTemplates=!1,processLoadTemplateBuffer()})).catch((function(){loadTemplateBuffer.splice(0,templatesToLoad.length),isLoadingTemplates=!1,processLoadTemplateBuffer()}))}},Renderer=function(){this.requiredStrings=[],this.requiredJS=[],this.requiredDates=[],this.currentThemeName=""};Renderer.prototype.requiredStrings=null,Renderer.prototype.requiredDates=[],Renderer.prototype.requiredJS=null,Renderer.prototype.currentThemeName="",Renderer.prototype.getTemplate=function(templateName){var currentTheme=this.currentThemeName,searchKey=currentTheme+"/"+templateName,cachedPromise=getTemplatePromiseFromCache(searchKey);if(cachedPromise)return cachedPromise;var existingBufferRecords=loadTemplateBuffer.filter((function(record){return record.searchKey==searchKey}));if(existingBufferRecords.length)return existingBufferRecords[0].deferred.promise();var parts=templateName.split("/"),component=getNormalisedComponent(parts.shift()),name=parts.join("/"),deferred=$.Deferred();return loadTemplateBuffer.push({component:component,name:name,theme:currentTheme,searchKey:searchKey,deferred:deferred}),processLoadTemplateBuffer(),deferred.promise()},Renderer.prototype.prefetchTemplates=function(templateNames,currentTheme){templateNames.forEach((function(templateName){var searchKey=currentTheme+"/"+templateName;if(!getTemplatePromiseFromCache(searchKey)&&!loadTemplateBuffer.filter((function(record){return record.searchKey==searchKey})).length){var parts=templateName.split("/"),component=getNormalisedComponent(parts.shift()),name=parts.join("/");loadTemplateBuffer.push({component:component,name:name,theme:currentTheme,searchKey:searchKey,deferred:$.Deferred()})}})),processLoadTemplateBuffer()},Renderer.prototype.partialHelper=function(name){var searchKey=this.currentThemeName+"/"+name;return searchKey in templateCache||notification.exception(new Error("Failed to pre-fetch the template: "+name)),templateCache[searchKey]},Renderer.prototype.renderIcon=function(key,component,title){var modulename=config.iconsystemmodule;component=getNormalisedComponent(component);var ready=$.Deferred();return require([modulename],(function(System){var system=new System;system instanceof IconSystem?(iconSystem=system,system.init().then(ready.resolve).catch(notification.exception)):ready.reject("Invalid icon system specified"+config.iconsystemmodule)})),ready.then(function(iconSystem){return this.getTemplate(iconSystem.getTemplateName())}.bind(this)).then((function(template){return iconSystem.renderIcon(key,component,title,template)}))},Renderer.prototype.pixHelper=function(context,sectionText,helper){var parts=sectionText.split(","),key="",component="",text="";parts.length>0&&(key=helper(parts.shift().trim(),context)),parts.length>0&&(component=helper(parts.shift().trim(),context)),parts.length>0&&(text=helper(parts.join(",").trim(),context));var templateName=iconSystem.getTemplateName(),searchKey=this.currentThemeName+"/"+templateName,template=templateCache[searchKey];return component=getNormalisedComponent(component),key=key.replace(/&#x2F;/gi,"/"),iconSystem.renderIcon(key,component,text,template)},Renderer.prototype.jsHelper=function(context,sectionText,helper){return this.requiredJS.push(helper(sectionText,context)),""},Renderer.prototype.stringHelper=function(context,sectionText,helper){var parts=sectionText.split(","),key="",component="",param="";if(parts.length>0&&(key=parts.shift().trim()),parts.length>0&&(component=parts.shift().trim()),parts.length>0&&(param=parts.join(",").trim()),component=getNormalisedComponent(component),""!==param&&(param=helper(param,context)),param.match(/^{\s*"/gm))try{const parsedParam=JSON.parse(param);parsedParam&&"object"==typeof parsedParam&&(param=parsedParam)}catch(err){window.console.warn(err.message)}var index=this.requiredStrings.length;return this.requiredStrings.push({key:key,component:component,param:param}),"[[_s"+index+"]]"},Renderer.prototype.cleanStringHelper=function(context,sectionText,helper){return this.stringHelper(context,sectionText,helper).replace("s","c")},Renderer.prototype.quoteHelper=function(context,sectionText,helper){var content=helper(sectionText.trim(),context);return content=(content=JSON.stringify(content)).replace(/([{}]{2,3})/g,"{{=<% %>=}}$1<%={{ }}=%>")},Renderer.prototype.shortenTextHelper=function(context,sectionText,helper){var parts=sectionText.match(/(.*?),(.*)/),length=parts[1].trim(),content=helper(parts[2].trim(),context);return Truncate.truncate(content,{length:length,words:!0,ellipsis:"..."})},Renderer.prototype.userDateHelper=function(context,sectionText,helper){var parts=sectionText.match(/(.*?),(.*)/),timestamp=helper(parts[1].trim(),context),format=helper(parts[2].trim(),context),index=this.requiredDates.length;return this.requiredDates.push({timestamp:timestamp,format:format}),"[[_t_"+index+"]]"},Renderer.prototype.addHelperFunction=function(helperFunction,context){return function(){return function(sectionText,helper){var originalHelpers=disallowedNestedHelpers.reduce((function(carry,name){return context.hasOwnProperty(name)&&(carry[name]=context[name]),carry}),{});disallowedNestedHelpers.forEach((function(helperName){context[helperName]=function(){return""}}));var result=helperFunction.apply(this,[context,sectionText,helper]);for(var name in originalHelpers)context[name]=originalHelpers[name];return result}.bind(this)}.bind(this)},Renderer.prototype.addHelpers=function(context,themeName){this.currentThemeName=themeName,this.requiredStrings=[],this.requiredJS=[],context.uniqid=uniqInstances++,context.str=this.addHelperFunction(this.stringHelper,context),context.cleanstr=this.addHelperFunction(this.cleanStringHelper,context),context.pix=this.addHelperFunction(this.pixHelper,context),context.js=this.addHelperFunction(this.jsHelper,context),context.quote=this.addHelperFunction(this.quoteHelper,context),context.shortentext=this.addHelperFunction(this.shortenTextHelper,context),context.userdate=this.addHelperFunction(this.userDateHelper,context),context.globals={config:config},context.currentTheme=themeName},Renderer.prototype.getJS=function(){var js="";return this.requiredJS.length>0&&(js=this.requiredJS.join(";\n")),js},Renderer.prototype.treatStringsInContent=function(content,strings){var treated,index,strIndex,walker,char,strFinal,isClean,pattern=/\[\[_(s|c)\d+\]\]/;do{for(treated="",index=content.search(pattern);index>-1;){treated+=content.substring(0,index),isClean="c"==(content=content.substr(index))[3],strIndex="",walker=4,char=content.substr(walker,1);do{strIndex+=char,walker++,char=content.substr(walker,1)}while("]"!=char);void 0===(strFinal=strings[parseInt(strIndex,10)])&&(Log.debug("Could not find string for pattern [[_"+(isClean?"c":"s")+strIndex+"]]."),strFinal=""),isClean&&(strFinal=mustache.escape(strFinal)),treated+=strFinal,index=(content=content.substr(6+strIndex.length)).search(pattern)}index=(content=treated+content).search(pattern)}while(index>-1);return content},Renderer.prototype.treatDatesInContent=function(content,dates){return dates.forEach((function(date,index){var re=new RegExp("\\[\\[_t_"+index+"\\]\\]","g");content=content.replace(re,date)})),content},Renderer.prototype.doRender=function(templateSource,context,themeName){this.currentThemeName=themeName;var iconTemplate=iconSystem.getTemplateName(),pendingPromise=new Pending("core/templates:doRender");return this.getTemplate(iconTemplate).then(function(){this.addHelpers(context,themeName);var result=mustache.render(templateSource,context,this.partialHelper.bind(this));return $.Deferred().resolve(result.trim(),this.getJS()).promise()}.bind(this)).then(function(html,js){return this.requiredStrings.length>0?str.get_strings(this.requiredStrings).then(function(strings){return this.requiredDates=this.requiredDates.map(function(date){return{timestamp:this.treatStringsInContent(date.timestamp,strings),format:this.treatStringsInContent(date.format,strings)}}.bind(this)),html=this.treatStringsInContent(html,strings),js=this.treatStringsInContent(js,strings),$.Deferred().resolve(html,js).promise()}.bind(this)):$.Deferred().resolve(html,js).promise()}.bind(this)).then(function(html,js){return this.requiredDates.length>0?UserDate.get(this.requiredDates).then(function(dates){return html=this.treatDatesInContent(html,dates),js=this.treatDatesInContent(js,dates),$.Deferred().resolve(html,js).promise()}.bind(this)):$.Deferred().resolve(html,js).promise()}.bind(this)).then((function(html,js){return pendingPromise.resolve(),$.Deferred().resolve(html,js).promise()}))};var runTemplateJS=function(source){if(""!==source.trim()){var newscript=$("<script>").attr("type","text/javascript").html(source);$("head").append(newscript)}},domReplace=function(element,newHTML,newJS,replaceChildNodes){var replaceNode=$(element);if(replaceNode.length){var newNodes=$(newHTML);return replaceChildNodes?(new Y.NodeList(replaceNode.children().get()).destroy(!0),replaceNode.empty(),replaceNode.append(newNodes)):(new Y.NodeList(replaceNode.get()).destroy(!0),replaceNode.replaceWith(newNodes)),runTemplateJS(newJS),filterEvents.notifyFilterContentUpdated(newNodes),newNodes.get()}return[]};Renderer.prototype.scanForPartials=function(templateSource){var tokens=mustache.parse(templateSource),partials=[],findPartial=function(tokens,partials){var i,token;for(i=0;i<tokens.length;i++)">"!=(token=tokens[i])[0]&&"<"!=token[0]||partials.push(token[1]),token.length>4&&findPartial(token[4],partials)};return findPartial(tokens,partials),partials},Renderer.prototype.cachePartials=function(templateName,parentage){var searchKey=this.currentThemeName+"/"+templateName;return searchKey in cachePartialPromises||(parentage=parentage||[searchKey],cachePartialPromises[searchKey]=$.Deferred(),this.getTemplate(templateName).then(function(templateSource){var fetchThemAll=this.scanForPartials(templateSource).filter(function(partialName){return!(parentage.indexOf(this.currentThemeName+"/"+partialName)>=0)&&partialName!=templateName}.bind(this)).map(function(partialName){return parentage.push(this.currentThemeName+"/"+partialName),this.cachePartials(partialName,parentage)}.bind(this));return $.when.apply($,fetchThemAll).then((function(){return cachePartialPromises[searchKey].resolve(templateSource)}))}.bind(this)).catch(cachePartialPromises[searchKey].reject)),cachePartialPromises[searchKey]},Renderer.prototype.render=function(templateName,context,themeName){void 0===themeName&&(themeName=config.theme),this.currentThemeName=themeName;var modulename=config.iconsystemmodule,ready=$.Deferred();return require([modulename],(function(System){var system=new System;system instanceof IconSystem?(iconSystem=system,system.init().then(ready.resolve).catch(notification.exception)):ready.reject("Invalid icon system specified"+config.iconsystem)})),ready.then(function(){return this.cachePartials(templateName)}.bind(this)).then(function(templateSource){return this.doRender(templateSource,context,themeName)}.bind(this))};return{render:function(templateName,context,themeName){return(new Renderer).render(templateName,context,themeName)},prefetchTemplates:function(templateNames,themeName){var renderer=new Renderer;return void 0===themeName&&(themeName=config.theme),renderer.prefetchTemplates(templateNames,themeName)},renderForPromise:function(templateName,context,themeName){return(new Renderer).render(templateName,context,themeName).then((function(html,js){return{html:html,js:js}}))},renderPix:function(key,component,title){return(new Renderer).renderIcon(key,getNormalisedComponent(component),title)},runTemplateJS:runTemplateJS,replaceNodeContents:function(element,newHTML,newJS){return domReplace(element,newHTML,newJS,!0)},replaceNode:function(element,newHTML,newJS){return domReplace(element,newHTML,newJS,!1)},prependNodeContents:function(element,html,js){return function(element,html,js){var node=$(element);if(node.length){var newContent=$(html);return node.prepend(newContent),runTemplateJS(js),filterEvents.notifyFilterContentUpdated(node),newContent.get()}return[]}(element,html,js)},appendNodeContents:function(element,html,js){return function(element,html,js){var node=$(element);if(node.length){var newContent=$(html);return node.append(newContent),runTemplateJS(js),filterEvents.notifyFilterContentUpdated(node),newContent.get()}return[]}(element,html,js)}}}));
 
 //# sourceMappingURL=templates.min.js.map
\ No newline at end of file
diff --git a/lib/amd/build/templates.min.js.map b/lib/amd/build/templates.min.js.map
index 0cc65655988..a14f532ea41 100644
--- a/lib/amd/build/templates.min.js.map
+++ b/lib/amd/build/templates.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"templates.min.js","sources":["../src/templates.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Template renderer for Moodle. Load and render Moodle templates with Mustache.\n *\n * @module     core/templates\n * @copyright  2015 Damyon Wiese <damyon@moodle.com>\n * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since      2.9\n */\ndefine([\n    'core/mustache',\n    'jquery',\n    'core/ajax',\n    'core/str',\n    'core/notification',\n    'core/url',\n    'core/config',\n    'core/localstorage',\n    'core/icon_system',\n    'core_filters/events',\n    'core/yui',\n    'core/log',\n    'core/truncate',\n    'core/user_date',\n    'core/pending',\n],\nfunction(\n    mustache,\n    $,\n    ajax,\n    str,\n    notification,\n    coreurl,\n    config,\n    storage,\n    IconSystem,\n    filterEvents,\n    Y,\n    Log,\n    Truncate,\n    UserDate,\n    Pending\n) {\n\n    // Module variables.\n    /** @var {Number} uniqInstances Count of times this constructor has been called. */\n    var uniqInstances = 0;\n\n    /** @var {String[]} templateCache - Cache of already loaded template strings */\n    var templateCache = {};\n\n    /** @var {Promise[]} templatePromises - Cache of already loaded template promises */\n    var templatePromises = {};\n\n    /** @var {Promise[]} cachePartialPromises - Cache of already loaded template partial promises */\n    var cachePartialPromises = {};\n\n    /** @var {Object} iconSystem - Object extending core/iconsystem */\n    var iconSystem = {};\n\n    /** @var {Object[]} loadTemplateBuffer - List of templates to be loaded */\n    var loadTemplateBuffer = [];\n\n    /** @var {Bool} isLoadingTemplates - Whether templates are currently being loaded */\n    var isLoadingTemplates = false;\n\n    /** @var {Array} disallowedNestedHelpers - List of helpers that can't be called within other helpers */\n    var disallowedNestedHelpers = ['js'];\n\n    /**\n     * Normalise the provided component such that '', 'moodle', and 'core' are treated consistently.\n     *\n     * @param   {String} component\n     * @returns {String}\n     */\n    var getNormalisedComponent = function(component) {\n        if (component) {\n            if (component !== 'moodle' && component !== 'core') {\n                return component;\n            }\n        }\n\n        return 'core';\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 <theme>/<component>/<template> e.g. boost/core/modal.\n     *\n     * If the template is found in any of the caches it will populate the other caches with\n     * the same data as well.\n     *\n     * @param {String} searchKey The template search key in the format <theme>/<component>/<template> e.g. boost/core/modal\n     * @return {Object} jQuery promise resolved with the template source\n     */\n    var getTemplatePromiseFromCache = function(searchKey) {\n        // First try the cache of promises.\n        if (searchKey in templatePromises) {\n            return templatePromises[searchKey];\n        }\n\n        // Check the module cache.\n        if (searchKey in templateCache) {\n            // Add this to the promises cache for future.\n            templatePromises[searchKey] = $.Deferred().resolve(templateCache[searchKey]).promise();\n            return templatePromises[searchKey];\n        }\n\n        if (M.cfg.templaterev <= 0) {\n            // Template caching is disabled. Do not store in persistent storage.\n            return null;\n        }\n\n        // Now try local storage.\n        var cached = storage.get('core_template/' + M.cfg.templaterev + ':' + searchKey);\n        if (cached) {\n            // Add this to the module cache for future.\n            templateCache[searchKey] = cached;\n            // Add this to the promises cache for future.\n            templatePromises[searchKey] = $.Deferred().resolve(cached).promise();\n            return templatePromises[searchKey];\n        }\n\n        return null;\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    var processLoadTemplateBuffer = function() {\n        if (!loadTemplateBuffer.length) {\n            return;\n        }\n\n        if (isLoadingTemplates) {\n            return;\n        }\n\n        isLoadingTemplates = true;\n        // Grab any templates waiting in the buffer.\n        var templatesToLoad = loadTemplateBuffer.slice();\n        // This will be resolved with the list of promises for the server request.\n        var serverRequestsDeferred = $.Deferred();\n        var requests = [];\n        // Get a list of promises for each of the templates we need to load.\n        var templatePromises = templatesToLoad.map(function(templateData) {\n            var component = getNormalisedComponent(templateData.component);\n            var name = templateData.name;\n            var searchKey = templateData.searchKey;\n            var theme = templateData.theme;\n            var templateDeferred = templateData.deferred;\n            var 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            var cachedPromise = getTemplatePromiseFromCache(searchKey);\n            if (cachedPromise) {\n                // We've seen this template so immediately resolve the existing promise.\n                promise = cachedPromise;\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: 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                var 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(function(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(function(response) {\n                            var templateSource = null;\n\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(function(data) {\n                                data.component = getNormalisedComponent(data.component);\n                                // Generate the search key for this template in the response so that we\n                                // can add it to the caches.\n                                var tempSearchKey = [theme, data.component, data.name].join('/');\n                                // Cache all of the dependent templates because we'll need them to render\n                                // the requested template.\n                                templateCache[tempSearchKey] = data.value;\n\n                                if (M.cfg.templaterev > 0) {\n                                    // The template cache is enabled - set the value there.\n                                    storage.set('core_template/' + M.cfg.templaterev + ':' + tempSearchKey, data.value);\n                                }\n\n                                if (data.component == component && data.name == name) {\n                                    // This is the original template that was requested so remember it to return.\n                                    templateSource = 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(function(data) {\n                                    return {\n                                        component: getNormalisedComponent(data.component),\n                                        key: data.name,\n                                        value: data.value\n                                    };\n                                }));\n                            }\n\n                            // Return the original template source that the user requested.\n                            return templateSource;\n                        });\n\n                        return templatePromises[searchKey];\n                    });\n            }\n\n            return promise\n                .then(function(source) {\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                    return templateDeferred.resolve(source);\n                })\n                .catch(function(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, M.cfg.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(function() {\n                // Remove the templates we've loaded from the buffer.\n                loadTemplateBuffer.splice(0, templatesToLoad.length);\n                isLoadingTemplates = false;\n                processLoadTemplateBuffer();\n                return;\n            })\n            .catch(function() {\n                // Remove the templates we've loaded from the buffer.\n                loadTemplateBuffer.splice(0, templatesToLoad.length);\n                isLoadingTemplates = false;\n                processLoadTemplateBuffer();\n            });\n    };\n\n    /**\n     * Constructor\n     *\n     * Each call to templates.render gets it's own instance of this class.\n     */\n    var Renderer = function() {\n        this.requiredStrings = [];\n        this.requiredJS = [];\n        this.requiredDates = [];\n        this.currentThemeName = '';\n    };\n    // Class variables and functions.\n\n    /** @var {string[]} requiredStrings - Collection of strings found during the rendering of one template */\n    Renderer.prototype.requiredStrings = null;\n\n    /** @var {object[]} requiredDates - Collection of dates found during the rendering of one template */\n    Renderer.prototype.requiredDates = [];\n\n    /** @var {string[]} requiredJS - Collection of js blocks found during the rendering of one template */\n    Renderer.prototype.requiredJS = null;\n\n    /** @var {String} themeName for the current render */\n    Renderer.prototype.currentThemeName = '';\n\n    /**\n     * Load a template.\n     *\n     * @method getTemplate\n     * @private\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     * @return {Promise} JQuery promise object resolved when the template has been fetched.\n     */\n    Renderer.prototype.getTemplate = function(templateName) {\n        var currentTheme = this.currentThemeName;\n        var searchKey = currentTheme + '/' + templateName;\n\n        // If we haven't already seen this template then buffer it.\n        var cachedPromise = getTemplatePromiseFromCache(searchKey);\n        if (cachedPromise) {\n            return cachedPromise;\n        }\n\n        // Check the buffer to see if this template has already been added.\n        var existingBufferRecords = loadTemplateBuffer.filter(function(record) {\n            return record.searchKey == searchKey;\n        });\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        var parts = templateName.split('/');\n        var component = getNormalisedComponent(parts.shift());\n        var name = parts.join('/');\n        var deferred = $.Deferred();\n\n        // Add this template to the buffer to be loaded.\n        loadTemplateBuffer.push({\n            component: component,\n            name: name,\n            theme: currentTheme,\n            searchKey: searchKey,\n            deferred: deferred\n        });\n\n        // We know there is at least one thing in the buffer so kick off a processing run.\n        processLoadTemplateBuffer();\n        return deferred.promise();\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} currentTheme\n     */\n    Renderer.prototype.prefetchTemplates = function(templateNames, currentTheme) {\n        templateNames.forEach(function(templateName) {\n            var searchKey = currentTheme + '/' + templateName;\n\n            // If we haven't already seen this template then buffer it.\n            if (getTemplatePromiseFromCache(searchKey)) {\n                return;\n            }\n\n            // Check the buffer to see if this template has already been added.\n            var existingBufferRecords = loadTemplateBuffer.filter(function(record) {\n                return record.searchKey == searchKey;\n            });\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            var parts = templateName.split('/');\n            var component = getNormalisedComponent(parts.shift());\n            var name = parts.join('/');\n\n            // Add this template to the buffer to be loaded.\n            loadTemplateBuffer.push({\n                component: component,\n                name: name,\n                theme: currentTheme,\n                searchKey: searchKey,\n                deferred: $.Deferred(),\n            });\n        });\n\n        processLoadTemplateBuffer();\n    };\n\n    /**\n     * Load a partial from the cache or ajax.\n     *\n     * @method partialHelper\n     * @private\n     * @param {string} name The partial name to load.\n     * @return {string}\n     */\n    Renderer.prototype.partialHelper = function(name) {\n\n        var searchKey = this.currentThemeName + '/' + name;\n\n        if (!(searchKey in templateCache)) {\n            notification.exception(new Error('Failed to pre-fetch the template: ' + name));\n        }\n\n        return templateCache[searchKey];\n    };\n\n    /**\n     * Render a single image icon.\n     *\n     * @method renderIcon\n     * @private\n     * @param {string} key The icon key.\n     * @param {string} component The component name.\n     * @param {string} title The icon title\n     * @return {Promise}\n     */\n    Renderer.prototype.renderIcon = function(key, component, title) {\n        // Preload the module to do the icon rendering based on the theme iconsystem.\n        var modulename = config.iconsystemmodule;\n        component = getNormalisedComponent(component);\n\n        // RequireJS does not return a promise.\n        var ready = $.Deferred();\n        require([modulename], function(System) {\n            var system = new System();\n            if (!(system instanceof IconSystem)) {\n                ready.reject('Invalid icon system specified' + config.iconsystemmodule);\n            } else {\n                iconSystem = system;\n                system.init().then(ready.resolve).catch(notification.exception);\n            }\n        });\n\n        return ready.then(function(iconSystem) {\n            return this.getTemplate(iconSystem.getTemplateName());\n        }.bind(this)).then(function(template) {\n            return iconSystem.renderIcon(\n                key,\n                component,\n                title,\n                template\n            );\n        });\n    };\n\n    /**\n     * Render image icons.\n     *\n     * @method pixHelper\n     * @private\n     * @param {object} context The mustache context\n     * @param {string} sectionText The text to parse arguments from.\n     * @param {function} helper Used to render the alt attribute of the text.\n     * @return {string}\n     */\n    Renderer.prototype.pixHelper = function(context, sectionText, helper) {\n        var parts = sectionText.split(',');\n        var key = '';\n        var component = '';\n        var text = '';\n\n        if (parts.length > 0) {\n            key = helper(parts.shift().trim(), context);\n        }\n        if (parts.length > 0) {\n            component = helper(parts.shift().trim(), context);\n        }\n        if (parts.length > 0) {\n            text = helper(parts.join(',').trim(), context);\n        }\n\n        var templateName = iconSystem.getTemplateName();\n        var searchKey = this.currentThemeName + '/' + templateName;\n        var template = templateCache[searchKey];\n\n        component = getNormalisedComponent(component);\n\n        // The key might have been escaped by the JS Mustache engine which\n        // converts forward slashes to HTML entities. Let us undo that here.\n        key = key.replace(/&#x2F;/gi, '/');\n\n        return iconSystem.renderIcon(\n            key,\n            component,\n            text,\n            template\n        );\n    };\n\n    /**\n     * Render blocks of javascript and save them in an array.\n     *\n     * @method jsHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to save as a js block.\n     * @param {function} helper Used to render the block.\n     * @return {string}\n     */\n    Renderer.prototype.jsHelper = function(context, sectionText, helper) {\n        this.requiredJS.push(helper(sectionText, context));\n        return '';\n    };\n\n    /**\n     * String helper used to render {{#str}}abd component { a : 'fish'}{{/str}}\n     * into a get_string call.\n     *\n     * @method stringHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @return {string}\n     */\n    Renderer.prototype.stringHelper = function(context, sectionText, helper) {\n        var parts = sectionText.split(',');\n        var key = '';\n        var component = '';\n        var param = '';\n        if (parts.length > 0) {\n            key = parts.shift().trim();\n        }\n        if (parts.length > 0) {\n            component = parts.shift().trim();\n        }\n        if (parts.length > 0) {\n            param = parts.join(',').trim();\n        }\n\n        component = getNormalisedComponent(component);\n\n        if (param !== '') {\n            // Allow variable expansion in the param part only.\n            param = helper(param, context);\n        }\n\n        // Allow json formatted $a arguments.\n        // Added double quote after left curly bracket to differentiate between string and JSON string.\n        if (param.indexOf('{\"') === 0) {\n            // If it can't be parsed then the string is not a JSON format.\n            try {\n                const parsedParam = JSON.parse(param);\n                // Handle non-exception-throwing cases, e.g. null, integer, boolean.\n                if (parsedParam && typeof parsedParam === \"object\") {\n                    param = parsedParam;\n                }\n            } catch (err) {\n                // This was probably not JSON.\n                // Keep the error message visible.\n                window.console.warn(err.message);\n            }\n        }\n\n        var index = this.requiredStrings.length;\n        this.requiredStrings.push({\n            key: key,\n            component: component,\n            param: param\n        });\n\n        // The placeholder must not use {{}} as those can be misinterpreted by the engine.\n        return '[[_s' + index + ']]';\n    };\n\n    /**\n     * String helper to render {{#cleanstr}}abd component { a : 'fish'}{{/cleanstr}}\n     * into a get_string following by an HTML escape.\n     *\n     * @method cleanStringHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @return {string}\n     */\n    Renderer.prototype.cleanStringHelper = function(context, sectionText, helper) {\n        var str = this.stringHelper(context, sectionText, helper);\n\n        // We're going to use [[_cx]] format for clean strings, where x is a number.\n        // Hence, replacing 's' with 'c' in the placeholder that stringHelper returns.\n        return str.replace('s', 'c');\n    };\n\n    /**\n     * Quote helper used to wrap content in quotes, and escape all special JSON characters present in the content.\n     *\n     * @method quoteHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @return {string}\n     */\n    Renderer.prototype.quoteHelper = function(context, sectionText, helper) {\n        var content = helper(sectionText.trim(), context);\n\n        // Escape the {{ and JSON encode.\n        // This involves wrapping {{, and }} in change delimeter tags.\n        content = JSON.stringify(content);\n        content = content.replace(/([{}]{2,3})/g, '{{=<% %>=}}$1<%={{ }}=%>');\n        return content;\n    };\n\n    /**\n     * Shorten text helper to truncate text and append a trailing ellipsis.\n     *\n     * @method shortenTextHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @return {string}\n     */\n    Renderer.prototype.shortenTextHelper = function(context, sectionText, helper) {\n        // Non-greedy split on comma to grab section text into the length and\n        // text parts.\n        var regex = /(.*?),(.*)/;\n        var parts = sectionText.match(regex);\n        // The length is the part matched in the first set of parethesis.\n        var length = parts[1].trim();\n        // The length is the part matched in the second set of parethesis.\n        var text = parts[2].trim();\n        var content = helper(text, context);\n        return Truncate.truncate(content, {\n            length: length,\n            words: true,\n            ellipsis: '...'\n        });\n    };\n\n    /**\n     * User date helper to render user dates from timestamps.\n     *\n     * @method userDateHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @return {string}\n     */\n    Renderer.prototype.userDateHelper = function(context, sectionText, helper) {\n        // Non-greedy split on comma to grab the timestamp and format.\n        var regex = /(.*?),(.*)/;\n        var parts = sectionText.match(regex);\n        var timestamp = helper(parts[1].trim(), context);\n        var format = helper(parts[2].trim(), context);\n        var index = this.requiredDates.length;\n\n        this.requiredDates.push({\n            timestamp: timestamp,\n            format: format\n        });\n\n        return '[[_t_' + index + ']]';\n    };\n\n    /**\n     * Return a helper function to be added to the context for rendering the a\n     * template.\n     *\n     * This will parse the provided text before giving it to the helper function\n     * in order to remove any disallowed nested helpers to prevent one helper\n     * from calling another.\n     *\n     * In particular to prevent the JS helper from being called from within another\n     * helper because it can lead to security issues when the JS portion is user\n     * provided.\n     *\n     * @param  {function} helperFunction The helper function to add\n     * @param  {object} context The template context for the helper function\n     * @return {Function} To be set in the context\n     */\n    Renderer.prototype.addHelperFunction = function(helperFunction, context) {\n        return function() {\n            return function(sectionText, helper) {\n                // Override the disallowed helpers in the template context with\n                // a function that returns an empty string for use when executing\n                // other helpers. This is to prevent these helpers from being\n                // executed as part of the rendering of another helper in order to\n                // prevent any potential security issues.\n                var originalHelpers = disallowedNestedHelpers.reduce(function(carry, name) {\n                    if (context.hasOwnProperty(name)) {\n                        carry[name] = context[name];\n                    }\n\n                    return carry;\n                }, {});\n\n                disallowedNestedHelpers.forEach(function(helperName) {\n                    context[helperName] = function() {\n                        return '';\n                    };\n                });\n\n                // Execute the helper with the modified context that doesn't include\n                // the disallowed nested helpers. This prevents the disallowed\n                // helpers from being called from within other helpers.\n                var result = helperFunction.apply(this, [context, sectionText, helper]);\n\n                // Restore the original helper implementation in the context so that\n                // any further rendering has access to them again.\n                for (var name in originalHelpers) {\n                    context[name] = originalHelpers[name];\n                }\n\n                return result;\n            }.bind(this);\n        }.bind(this);\n    };\n\n    /**\n     * Add some common helper functions to all context objects passed to templates.\n     * These helpers match exactly the helpers available in php.\n     *\n     * @method addHelpers\n     * @private\n     * @param {Object} context Simple types used as the context for the template.\n     * @param {String} themeName We set this multiple times, because there are async calls.\n     */\n    Renderer.prototype.addHelpers = function(context, themeName) {\n        this.currentThemeName = themeName;\n        this.requiredStrings = [];\n        this.requiredJS = [];\n        context.uniqid = (uniqInstances++);\n        context.str = this.addHelperFunction(this.stringHelper, context);\n        context.cleanstr = this.addHelperFunction(this.cleanStringHelper, context);\n        context.pix = this.addHelperFunction(this.pixHelper, context);\n        context.js = this.addHelperFunction(this.jsHelper, context);\n        context.quote = this.addHelperFunction(this.quoteHelper, context);\n        context.shortentext = this.addHelperFunction(this.shortenTextHelper, context);\n        context.userdate = this.addHelperFunction(this.userDateHelper, context);\n        context.globals = {config: config};\n        context.currentTheme = themeName;\n    };\n\n    /**\n     * Get all the JS blocks from the last rendered template.\n     *\n     * @method getJS\n     * @private\n     * @return {string}\n     */\n    Renderer.prototype.getJS = function() {\n        var js = '';\n        if (this.requiredJS.length > 0) {\n            js = this.requiredJS.join(\";\\n\");\n        }\n\n        return js;\n    };\n\n    /**\n     * Treat strings in content.\n     *\n     * The purpose of this method is to replace the placeholders found in a string\n     * with the their respective translated strings.\n     *\n     * Previously we were relying on String.replace() but the complexity increased with\n     * the numbers of strings to replace. Now we manually walk the string and stop at each\n     * placeholder we find, only then we replace it. Most of the time we will\n     * replace all the placeholders in a single run, at times we will need a few\n     * more runs when placeholders are replaced with strings that contain placeholders\n     * themselves.\n     *\n     * @param {String} content The content in which string placeholders are to be found.\n     * @param {Array} strings The strings to replace with.\n     * @return {String} The treated content.\n     */\n    Renderer.prototype.treatStringsInContent = function(content, strings) {\n        var pattern = /\\[\\[_(s|c)\\d+\\]\\]/,\n            treated,\n            index,\n            strIndex,\n            walker,\n            char,\n            strFinal,\n            isClean;\n\n        do {\n            treated = '';\n            index = content.search(pattern);\n            while (index > -1) {\n\n                // Copy the part prior to the placeholder to the treated string.\n                treated += content.substring(0, index);\n                content = content.substr(index);\n                isClean = content[3] == 'c';\n                strIndex = '';\n                walker = 4; // 4 is the length of either '[[_s' or '[[_c'.\n\n                // Walk the characters to manually extract the index of the string from the placeholder.\n                char = content.substr(walker, 1);\n                do {\n                    strIndex += char;\n                    walker++;\n                    char = content.substr(walker, 1);\n                } while (char != ']');\n\n                // Get the string, add it to the treated result, and remove the placeholder from the content to treat.\n                strFinal = strings[parseInt(strIndex, 10)];\n                if (typeof strFinal === 'undefined') {\n                    Log.debug('Could not find string for pattern [[_' + (isClean ? 'c' : 's') + strIndex + ']].');\n                    strFinal = '';\n                }\n                if (isClean) {\n                    strFinal = mustache.escape(strFinal);\n                }\n                treated += strFinal;\n                content = content.substr(6 + strIndex.length); // 6 is the length of the placeholder without the index.\n                                                               // That's either '[[_s]]' or '[[_c]]'.\n\n                // Find the next placeholder.\n                index = content.search(pattern);\n            }\n\n            // The content becomes the treated part with the rest of the content.\n            content = treated + content;\n\n            // Check if we need to walk the content again, in case strings contained placeholders.\n            index = content.search(pattern);\n\n        } while (index > -1);\n\n        return content;\n    };\n\n    /**\n     * Treat strings in content.\n     *\n     * The purpose of this method is to replace the date placeholders found in the\n     * content with the their respective translated dates.\n     *\n     * @param {String} content The content in which string placeholders are to be found.\n     * @param {Array} dates The dates to replace with.\n     * @return {String} The treated content.\n     */\n    Renderer.prototype.treatDatesInContent = function(content, dates) {\n        dates.forEach(function(date, index) {\n            var key = '\\\\[\\\\[_t_' + index + '\\\\]\\\\]';\n            var re = new RegExp(key, 'g');\n            content = content.replace(re, date);\n        });\n\n        return content;\n    };\n\n    /**\n     * Render a template and then call the callback with the result.\n     *\n     * @method doRender\n     * @private\n     * @param {string} templateSource The mustache template to render.\n     * @param {Object} context Simple types used as the context for the template.\n     * @param {String} themeName Name of the current theme.\n     * @return {Promise} object\n     */\n    Renderer.prototype.doRender = function(templateSource, context, themeName) {\n        this.currentThemeName = themeName;\n        var iconTemplate = iconSystem.getTemplateName();\n\n        var pendingPromise = new Pending('core/templates:doRender');\n        return this.getTemplate(iconTemplate).then(function() {\n            this.addHelpers(context, themeName);\n            var result = mustache.render(templateSource, context, this.partialHelper.bind(this));\n            return $.Deferred().resolve(result.trim(), this.getJS()).promise();\n        }.bind(this))\n        .then(function(html, js) {\n            if (this.requiredStrings.length > 0) {\n                return str.get_strings(this.requiredStrings).then(function(strings) {\n\n                    // Make sure string substitutions are done for the userdate\n                    // values as well.\n                    this.requiredDates = this.requiredDates.map(function(date) {\n                        return {\n                            timestamp: this.treatStringsInContent(date.timestamp, strings),\n                            format: this.treatStringsInContent(date.format, strings)\n                        };\n                    }.bind(this));\n\n                    // Why do we not do another call the render here?\n                    //\n                    // Because that would expose DOS holes. E.g.\n                    // I create an assignment called \"{{fish\" which\n                    // would get inserted in the template in the first pass\n                    // and cause the template to die on the second pass (unbalanced).\n                    html = this.treatStringsInContent(html, strings);\n                    js = this.treatStringsInContent(js, strings);\n                    return $.Deferred().resolve(html, js).promise();\n                }.bind(this));\n            }\n\n            return $.Deferred().resolve(html, js).promise();\n        }.bind(this))\n        .then(function(html, js) {\n            // This has to happen after the strings replacement because you can\n            // use the string helper in content for the user date helper.\n            if (this.requiredDates.length > 0) {\n                return UserDate.get(this.requiredDates).then(function(dates) {\n                    html = this.treatDatesInContent(html, dates);\n                    js = this.treatDatesInContent(js, dates);\n                    return $.Deferred().resolve(html, js).promise();\n                }.bind(this));\n            }\n\n            return $.Deferred().resolve(html, js).promise();\n        }.bind(this))\n        .then(function(html, js) {\n            pendingPromise.resolve();\n            return $.Deferred().resolve(html, js).promise();\n        });\n    };\n\n    /**\n     * Execute a block of JS returned from a template.\n     * Call this AFTER adding the template HTML into the DOM so the nodes can be found.\n     *\n     * @method runTemplateJS\n     * @param {string} source - A block of javascript.\n     */\n    var runTemplateJS = function(source) {\n        if (source.trim() !== '') {\n            var newscript = $('<script>').attr('type', 'text/javascript').html(source);\n            $('head').append(newscript);\n        }\n    };\n\n    /**\n     * Do some DOM replacement and trigger correct events and fire javascript.\n     *\n     * @method domReplace\n     * @private\n     * @param {JQuery} element - Element or selector to replace.\n     * @param {String} newHTML - HTML to insert / replace.\n     * @param {String} newJS - Javascript to run after the insertion.\n     * @param {Boolean} replaceChildNodes - Replace only the childnodes, alternative is to replace the entire node.\n     * @return {Array} The list of new DOM Nodes\n     * @fires event:filterContentUpdated\n     */\n    var domReplace = function(element, newHTML, newJS, replaceChildNodes) {\n        var replaceNode = $(element);\n        if (replaceNode.length) {\n            // First create the dom nodes so we have a reference to them.\n            var newNodes = $(newHTML);\n            var yuiNodes = null;\n            // Do the replacement in the page.\n            if (replaceChildNodes) {\n                // Cleanup any YUI event listeners attached to any of these nodes.\n                yuiNodes = new Y.NodeList(replaceNode.children().get());\n                yuiNodes.destroy(true);\n\n                // JQuery will cleanup after itself.\n                replaceNode.empty();\n                replaceNode.append(newNodes);\n            } else {\n                // Cleanup any YUI event listeners attached to any of these nodes.\n                yuiNodes = new Y.NodeList(replaceNode.get());\n                yuiNodes.destroy(true);\n\n                // JQuery will cleanup after itself.\n                replaceNode.replaceWith(newNodes);\n            }\n            // Run any javascript associated with the new HTML.\n            runTemplateJS(newJS);\n            // Notify all filters about the new content.\n            filterEvents.notifyFilterContentUpdated(newNodes);\n\n            return newNodes.get();\n        }\n\n        return [];\n    };\n\n    /**\n     * Scan a template source for partial tags and return a list of the found partials.\n     *\n     * @method scanForPartials\n     * @private\n     * @param {string} templateSource - source template to scan.\n     * @return {Array} List of partials.\n     */\n    Renderer.prototype.scanForPartials = function(templateSource) {\n        var tokens = mustache.parse(templateSource),\n            partials = [];\n\n        var findPartial = function(tokens, partials) {\n            var i, token;\n            for (i = 0; i < tokens.length; i++) {\n                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     * @private\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 {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    Renderer.prototype.cachePartials = function(templateName, parentage) {\n        var searchKey = this.currentThemeName + '/' + templateName;\n\n        if (searchKey in cachePartialPromises) {\n            return 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        parentage = parentage || [searchKey];\n\n        cachePartialPromises[searchKey] = $.Deferred();\n\n        this.getTemplate(templateName)\n        .then(function(templateSource) {\n            var partials = this.scanForPartials(templateSource);\n            var uniquePartials = partials.filter(function(partialName) {\n                // Check for recursion.\n\n                if (parentage.indexOf(this.currentThemeName + '/' + 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            }.bind(this));\n\n            // Fetch any partial which has not already been fetched.\n            var fetchThemAll = uniquePartials.map(function(partialName) {\n                parentage.push(this.currentThemeName + '/' + partialName);\n                return this.cachePartials(partialName, parentage);\n            }.bind(this));\n\n            // Resolve the templateName promise when all of the children are resolved.\n            return $.when.apply($, fetchThemAll)\n            .then(function() {\n                return cachePartialPromises[searchKey].resolve(templateSource);\n            });\n        }.bind(this))\n        .catch(cachePartialPromises[searchKey].reject);\n\n        return cachePartialPromises[searchKey];\n    };\n\n    /**\n     * Load a template and call doRender on it.\n     *\n     * @method render\n     * @private\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 {Object} context - Could be array, string or simple value for the context of the template.\n     * @param {string} themeName - Name of the current theme.\n     * @return {Promise} JQuery promise object resolved when the template has been rendered.\n     */\n    Renderer.prototype.render = function(templateName, context, themeName) {\n        if (typeof (themeName) === \"undefined\") {\n            // System context by default.\n            themeName = config.theme;\n        }\n\n        this.currentThemeName = themeName;\n\n        // Preload the module to do the icon rendering based on the theme iconsystem.\n        var modulename = config.iconsystemmodule;\n\n        var ready = $.Deferred();\n        require([modulename], function(System) {\n            var system = new System();\n            if (!(system instanceof IconSystem)) {\n                ready.reject('Invalid icon system specified' + config.iconsystem);\n            } else {\n                iconSystem = system;\n                system.init().then(ready.resolve).catch(notification.exception);\n            }\n        });\n\n        return ready.then(function() {\n                return this.cachePartials(templateName);\n            }.bind(this)).then(function(templateSource) {\n                return this.doRender(templateSource, context, themeName);\n            }.bind(this));\n    };\n\n    /**\n     * Prepend some HTML to a node and trigger events and fire javascript.\n     *\n     * @method domPrepend\n     * @private\n     * @param {jQuery|String} element - Element or selector to prepend HTML to\n     * @param {String} html - HTML to prepend\n     * @param {String} js - Javascript to run after we prepend the html\n     * @return {Array} The list of new DOM Nodes\n     * @fires event:filterContentUpdated\n     */\n    var domPrepend = function(element, html, js) {\n        var node = $(element);\n        if (node.length) {\n            // Prepend the html.\n            var newContent = $(html);\n            node.prepend(newContent);\n            // Run any javascript associated with the new HTML.\n            runTemplateJS(js);\n            // Notify all filters about the new content.\n            filterEvents.notifyFilterContentUpdated(node);\n\n            return newContent.get();\n        }\n\n        return [];\n    };\n\n    /**\n     * Append some HTML to a node and trigger events and fire javascript.\n     *\n     * @method domAppend\n     * @private\n     * @param {jQuery|String} element - Element or selector to append HTML to\n     * @param {String} html - HTML to append\n     * @param {String} js - Javascript to run after we append the html\n     * @return {Array} The list of new DOM Nodes\n     * @fires event:filterContentUpdated\n     */\n    var domAppend = function(element, html, js) {\n        var node = $(element);\n        if (node.length) {\n            // Append the html.\n            var newContent = $(html);\n            node.append(newContent);\n            // Run any javascript associated with the new HTML.\n            runTemplateJS(js);\n            // Notify all filters about the new content.\n            filterEvents.notifyFilterContentUpdated(node);\n\n            return newContent.get();\n        }\n\n        return [];\n    };\n\n    return /** @alias module:core/templates */ {\n        // Public variables and functions.\n        /**\n         * Every call to render creates a new instance of the class and calls render on it. This\n         * means each render call has it's own class variables.\n         *\n         * @method render\n         * @private\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 {Object} context - Could be array, string or simple value for the context of the template.\n         * @param {string} themeName - Name of the current theme.\n         * @return {Promise} JQuery promise object resolved when the template has been rendered.\n         */\n        render: function(templateName, context, themeName) {\n            var renderer = new Renderer();\n            return renderer.render(templateName, context, themeName);\n        },\n\n        /**\n         * Prefetch a set of templates without rendering them.\n         *\n         * @method getTemplate\n         * @param {Array} templateNames The list of templates to fetch\n         * @param {String} themeName\n         * @returns {Promise}\n         */\n        prefetchTemplates: function(templateNames, themeName) {\n            var renderer = new Renderer();\n\n            if (typeof themeName === \"undefined\") {\n                // System context by default.\n                themeName = config.theme;\n            }\n\n            return renderer.prefetchTemplates(templateNames, themeName);\n        },\n\n        /**\n         * Every call to render creates a new instance of the class and calls render on it. This\n         * means each render call has it's own class variables.\n         *\n         * This alernate to the standard .render() function returns the html and js in a single object suitable for a\n         * native Promise.\n         *\n         * @method renderForPromise\n         * @private\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 {Object} context - Could be array, string or simple value for the context of the template.\n         * @param {string} themeName - Name of the current theme.\n         * @return {Promise} JQuery promise object resolved when the template has been rendered.\n         */\n        renderForPromise: function(templateName, context, themeName) {\n            var renderer = new Renderer();\n            return renderer.render(templateName, context, themeName)\n            .then(function(html, js) {\n                return {\n                    html: html,\n                    js: js,\n                };\n            });\n        },\n\n        /**\n         * Every call to renderIcon creates a new instance of the class and calls renderIcon on it. This\n         * means each render call has it's own class variables.\n         *\n         * @method renderIcon\n         * @public\n         * @param {string} key - Icon key.\n         * @param {string} component - Icon component\n         * @param {string} title - Icon title\n         * @return {Promise} JQuery promise object resolved when the pix has been rendered.\n         */\n        renderPix: function(key, component, title) {\n            var renderer = new Renderer();\n            return renderer.renderIcon(\n                key,\n                getNormalisedComponent(component),\n                title\n            );\n        },\n\n        /**\n         * Execute a block of JS returned from a template.\n         * Call this AFTER adding the template HTML into the DOM so the nodes can be found.\n         *\n         * @method runTemplateJS\n         * @param {string} source - A block of javascript.\n         */\n        runTemplateJS: runTemplateJS,\n\n        /**\n         * Replace a node in the page with some new HTML and run the JS.\n         *\n         * @method replaceNodeContents\n         * @param {JQuery} element - Element or selector to replace.\n         * @param {String} newHTML - HTML to insert / replace.\n         * @param {String} newJS - Javascript to run after the insertion.\n         * @return {Array} The list of new DOM Nodes\n         */\n        replaceNodeContents: function(element, newHTML, newJS) {\n            return domReplace(element, newHTML, newJS, true);\n        },\n\n        /**\n         * Insert a node in the page with some new HTML and run the JS.\n         *\n         * @method replaceNode\n         * @param {JQuery} element - Element or selector to replace.\n         * @param {String} newHTML - HTML to insert / replace.\n         * @param {String} newJS - Javascript to run after the insertion.\n         * @return {Array} The list of new DOM Nodes\n         */\n        replaceNode: function(element, newHTML, newJS) {\n            return domReplace(element, newHTML, newJS, false);\n        },\n\n        /**\n         * Prepend some HTML to a node and trigger events and fire javascript.\n         *\n         * @method prependNodeContents\n         * @param {jQuery|String} element - Element or selector to prepend HTML to\n         * @param {String} html - HTML to prepend\n         * @param {String} js - Javascript to run after we prepend the html\n         * @return {Array} The list of new DOM Nodes\n         */\n        prependNodeContents: function(element, html, js) {\n            return domPrepend(element, html, js);\n        },\n\n        /**\n         * Append some HTML to a node and trigger events and fire javascript.\n         *\n         * @method appendNodeContents\n         * @param {jQuery|String} element - Element or selector to append HTML to\n         * @param {String} html - HTML to append\n         * @param {String} js - Javascript to run after we append the html\n         * @return {Array} The list of new DOM Nodes\n         */\n        appendNodeContents: function(element, html, js) {\n            return domAppend(element, html, js);\n        },\n    };\n});\n"],"names":["define","mustache","$","ajax","str","notification","coreurl","config","storage","IconSystem","filterEvents","Y","Log","Truncate","UserDate","Pending","uniqInstances","templateCache","templatePromises","cachePartialPromises","iconSystem","loadTemplateBuffer","isLoadingTemplates","disallowedNestedHelpers","getNormalisedComponent","component","getTemplatePromiseFromCache","searchKey","Deferred","resolve","promise","M","cfg","templaterev","cached","get","processLoadTemplateBuffer","length","templatesToLoad","slice","serverRequestsDeferred","requests","map","templateData","name","theme","templateDeferred","deferred","cachedPromise","push","methodname","args","template","themename","lang","attr","replace","index","then","promises","response","templateSource","templates","forEach","data","tempSearchKey","join","value","set","strings","cache_strings","key","source","catch","error","reject","call","when","apply","splice","Renderer","requiredStrings","requiredJS","requiredDates","currentThemeName","prototype","getTemplate","templateName","currentTheme","this","existingBufferRecords","filter","record","parts","split","shift","prefetchTemplates","templateNames","partialHelper","exception","Error","renderIcon","title","modulename","iconsystemmodule","ready","require","System","system","init","getTemplateName","bind","pixHelper","context","sectionText","helper","text","trim","jsHelper","stringHelper","param","indexOf","parsedParam","JSON","parse","err","window","console","warn","message","cleanStringHelper","quoteHelper","content","stringify","shortenTextHelper","match","truncate","words","ellipsis","userDateHelper","timestamp","format","addHelperFunction","helperFunction","originalHelpers","reduce","carry","hasOwnProperty","helperName","result","addHelpers","themeName","uniqid","cleanstr","pix","js","quote","shortentext","userdate","globals","getJS","treatStringsInContent","treated","strIndex","walker","char","strFinal","isClean","pattern","search","substring","substr","parseInt","debug","escape","treatDatesInContent","dates","date","re","RegExp","doRender","iconTemplate","pendingPromise","render","html","get_strings","runTemplateJS","newscript","append","domReplace","element","newHTML","newJS","replaceChildNodes","replaceNode","newNodes","NodeList","children","destroy","empty","replaceWith","notifyFilterContentUpdated","scanForPartials","tokens","partials","findPartial","i","token","cachePartials","parentage","fetchThemAll","partialName","iconsystem","renderer","renderForPromise","renderPix","replaceNodeContents","prependNodeContents","node","newContent","prepend","domPrepend","appendNodeContents","domAppend"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"templates.min.js","sources":["../src/templates.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Template renderer for Moodle. Load and render Moodle templates with Mustache.\n *\n * @module     core/templates\n * @copyright  2015 Damyon Wiese <damyon@moodle.com>\n * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since      2.9\n */\ndefine([\n    'core/mustache',\n    'jquery',\n    'core/ajax',\n    'core/str',\n    'core/notification',\n    'core/url',\n    'core/config',\n    'core/localstorage',\n    'core/icon_system',\n    'core_filters/events',\n    'core/yui',\n    'core/log',\n    'core/truncate',\n    'core/user_date',\n    'core/pending',\n],\nfunction(\n    mustache,\n    $,\n    ajax,\n    str,\n    notification,\n    coreurl,\n    config,\n    storage,\n    IconSystem,\n    filterEvents,\n    Y,\n    Log,\n    Truncate,\n    UserDate,\n    Pending\n) {\n\n    // Module variables.\n    /** @var {Number} uniqInstances Count of times this constructor has been called. */\n    var uniqInstances = 0;\n\n    /** @var {String[]} templateCache - Cache of already loaded template strings */\n    var templateCache = {};\n\n    /** @var {Promise[]} templatePromises - Cache of already loaded template promises */\n    var templatePromises = {};\n\n    /** @var {Promise[]} cachePartialPromises - Cache of already loaded template partial promises */\n    var cachePartialPromises = {};\n\n    /** @var {Object} iconSystem - Object extending core/iconsystem */\n    var iconSystem = {};\n\n    /** @var {Object[]} loadTemplateBuffer - List of templates to be loaded */\n    var loadTemplateBuffer = [];\n\n    /** @var {Bool} isLoadingTemplates - Whether templates are currently being loaded */\n    var isLoadingTemplates = false;\n\n    /** @var {Array} disallowedNestedHelpers - List of helpers that can't be called within other helpers */\n    var disallowedNestedHelpers = ['js'];\n\n    /**\n     * Normalise the provided component such that '', 'moodle', and 'core' are treated consistently.\n     *\n     * @param   {String} component\n     * @returns {String}\n     */\n    var getNormalisedComponent = function(component) {\n        if (component) {\n            if (component !== 'moodle' && component !== 'core') {\n                return component;\n            }\n        }\n\n        return 'core';\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 <theme>/<component>/<template> e.g. boost/core/modal.\n     *\n     * If the template is found in any of the caches it will populate the other caches with\n     * the same data as well.\n     *\n     * @param {String} searchKey The template search key in the format <theme>/<component>/<template> e.g. boost/core/modal\n     * @return {Object} jQuery promise resolved with the template source\n     */\n    var getTemplatePromiseFromCache = function(searchKey) {\n        // First try the cache of promises.\n        if (searchKey in templatePromises) {\n            return templatePromises[searchKey];\n        }\n\n        // Check the module cache.\n        if (searchKey in templateCache) {\n            // Add this to the promises cache for future.\n            templatePromises[searchKey] = $.Deferred().resolve(templateCache[searchKey]).promise();\n            return templatePromises[searchKey];\n        }\n\n        if (M.cfg.templaterev <= 0) {\n            // Template caching is disabled. Do not store in persistent storage.\n            return null;\n        }\n\n        // Now try local storage.\n        var cached = storage.get('core_template/' + M.cfg.templaterev + ':' + searchKey);\n        if (cached) {\n            // Add this to the module cache for future.\n            templateCache[searchKey] = cached;\n            // Add this to the promises cache for future.\n            templatePromises[searchKey] = $.Deferred().resolve(cached).promise();\n            return templatePromises[searchKey];\n        }\n\n        return null;\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    var processLoadTemplateBuffer = function() {\n        if (!loadTemplateBuffer.length) {\n            return;\n        }\n\n        if (isLoadingTemplates) {\n            return;\n        }\n\n        isLoadingTemplates = true;\n        // Grab any templates waiting in the buffer.\n        var templatesToLoad = loadTemplateBuffer.slice();\n        // This will be resolved with the list of promises for the server request.\n        var serverRequestsDeferred = $.Deferred();\n        var requests = [];\n        // Get a list of promises for each of the templates we need to load.\n        var templatePromises = templatesToLoad.map(function(templateData) {\n            var component = getNormalisedComponent(templateData.component);\n            var name = templateData.name;\n            var searchKey = templateData.searchKey;\n            var theme = templateData.theme;\n            var templateDeferred = templateData.deferred;\n            var 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            var cachedPromise = getTemplatePromiseFromCache(searchKey);\n            if (cachedPromise) {\n                // We've seen this template so immediately resolve the existing promise.\n                promise = cachedPromise;\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: 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                var 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(function(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(function(response) {\n                            var templateSource = null;\n\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(function(data) {\n                                data.component = getNormalisedComponent(data.component);\n                                // Generate the search key for this template in the response so that we\n                                // can add it to the caches.\n                                var tempSearchKey = [theme, data.component, data.name].join('/');\n                                // Cache all of the dependent templates because we'll need them to render\n                                // the requested template.\n                                templateCache[tempSearchKey] = data.value;\n\n                                if (M.cfg.templaterev > 0) {\n                                    // The template cache is enabled - set the value there.\n                                    storage.set('core_template/' + M.cfg.templaterev + ':' + tempSearchKey, data.value);\n                                }\n\n                                if (data.component == component && data.name == name) {\n                                    // This is the original template that was requested so remember it to return.\n                                    templateSource = 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(function(data) {\n                                    return {\n                                        component: getNormalisedComponent(data.component),\n                                        key: data.name,\n                                        value: data.value\n                                    };\n                                }));\n                            }\n\n                            // Return the original template source that the user requested.\n                            return templateSource;\n                        });\n\n                        return templatePromises[searchKey];\n                    });\n            }\n\n            return promise\n                .then(function(source) {\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                    return templateDeferred.resolve(source);\n                })\n                .catch(function(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, M.cfg.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(function() {\n                // Remove the templates we've loaded from the buffer.\n                loadTemplateBuffer.splice(0, templatesToLoad.length);\n                isLoadingTemplates = false;\n                processLoadTemplateBuffer();\n                return;\n            })\n            .catch(function() {\n                // Remove the templates we've loaded from the buffer.\n                loadTemplateBuffer.splice(0, templatesToLoad.length);\n                isLoadingTemplates = false;\n                processLoadTemplateBuffer();\n            });\n    };\n\n    /**\n     * Constructor\n     *\n     * Each call to templates.render gets it's own instance of this class.\n     */\n    var Renderer = function() {\n        this.requiredStrings = [];\n        this.requiredJS = [];\n        this.requiredDates = [];\n        this.currentThemeName = '';\n    };\n    // Class variables and functions.\n\n    /** @var {string[]} requiredStrings - Collection of strings found during the rendering of one template */\n    Renderer.prototype.requiredStrings = null;\n\n    /** @var {object[]} requiredDates - Collection of dates found during the rendering of one template */\n    Renderer.prototype.requiredDates = [];\n\n    /** @var {string[]} requiredJS - Collection of js blocks found during the rendering of one template */\n    Renderer.prototype.requiredJS = null;\n\n    /** @var {String} themeName for the current render */\n    Renderer.prototype.currentThemeName = '';\n\n    /**\n     * Load a template.\n     *\n     * @method getTemplate\n     * @private\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     * @return {Promise} JQuery promise object resolved when the template has been fetched.\n     */\n    Renderer.prototype.getTemplate = function(templateName) {\n        var currentTheme = this.currentThemeName;\n        var searchKey = currentTheme + '/' + templateName;\n\n        // If we haven't already seen this template then buffer it.\n        var cachedPromise = getTemplatePromiseFromCache(searchKey);\n        if (cachedPromise) {\n            return cachedPromise;\n        }\n\n        // Check the buffer to see if this template has already been added.\n        var existingBufferRecords = loadTemplateBuffer.filter(function(record) {\n            return record.searchKey == searchKey;\n        });\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        var parts = templateName.split('/');\n        var component = getNormalisedComponent(parts.shift());\n        var name = parts.join('/');\n        var deferred = $.Deferred();\n\n        // Add this template to the buffer to be loaded.\n        loadTemplateBuffer.push({\n            component: component,\n            name: name,\n            theme: currentTheme,\n            searchKey: searchKey,\n            deferred: deferred\n        });\n\n        // We know there is at least one thing in the buffer so kick off a processing run.\n        processLoadTemplateBuffer();\n        return deferred.promise();\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} currentTheme\n     */\n    Renderer.prototype.prefetchTemplates = function(templateNames, currentTheme) {\n        templateNames.forEach(function(templateName) {\n            var searchKey = currentTheme + '/' + templateName;\n\n            // If we haven't already seen this template then buffer it.\n            if (getTemplatePromiseFromCache(searchKey)) {\n                return;\n            }\n\n            // Check the buffer to see if this template has already been added.\n            var existingBufferRecords = loadTemplateBuffer.filter(function(record) {\n                return record.searchKey == searchKey;\n            });\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            var parts = templateName.split('/');\n            var component = getNormalisedComponent(parts.shift());\n            var name = parts.join('/');\n\n            // Add this template to the buffer to be loaded.\n            loadTemplateBuffer.push({\n                component: component,\n                name: name,\n                theme: currentTheme,\n                searchKey: searchKey,\n                deferred: $.Deferred(),\n            });\n        });\n\n        processLoadTemplateBuffer();\n    };\n\n    /**\n     * Load a partial from the cache or ajax.\n     *\n     * @method partialHelper\n     * @private\n     * @param {string} name The partial name to load.\n     * @return {string}\n     */\n    Renderer.prototype.partialHelper = function(name) {\n\n        var searchKey = this.currentThemeName + '/' + name;\n\n        if (!(searchKey in templateCache)) {\n            notification.exception(new Error('Failed to pre-fetch the template: ' + name));\n        }\n\n        return templateCache[searchKey];\n    };\n\n    /**\n     * Render a single image icon.\n     *\n     * @method renderIcon\n     * @private\n     * @param {string} key The icon key.\n     * @param {string} component The component name.\n     * @param {string} title The icon title\n     * @return {Promise}\n     */\n    Renderer.prototype.renderIcon = function(key, component, title) {\n        // Preload the module to do the icon rendering based on the theme iconsystem.\n        var modulename = config.iconsystemmodule;\n        component = getNormalisedComponent(component);\n\n        // RequireJS does not return a promise.\n        var ready = $.Deferred();\n        require([modulename], function(System) {\n            var system = new System();\n            if (!(system instanceof IconSystem)) {\n                ready.reject('Invalid icon system specified' + config.iconsystemmodule);\n            } else {\n                iconSystem = system;\n                system.init().then(ready.resolve).catch(notification.exception);\n            }\n        });\n\n        return ready.then(function(iconSystem) {\n            return this.getTemplate(iconSystem.getTemplateName());\n        }.bind(this)).then(function(template) {\n            return iconSystem.renderIcon(\n                key,\n                component,\n                title,\n                template\n            );\n        });\n    };\n\n    /**\n     * Render image icons.\n     *\n     * @method pixHelper\n     * @private\n     * @param {object} context The mustache context\n     * @param {string} sectionText The text to parse arguments from.\n     * @param {function} helper Used to render the alt attribute of the text.\n     * @return {string}\n     */\n    Renderer.prototype.pixHelper = function(context, sectionText, helper) {\n        var parts = sectionText.split(',');\n        var key = '';\n        var component = '';\n        var text = '';\n\n        if (parts.length > 0) {\n            key = helper(parts.shift().trim(), context);\n        }\n        if (parts.length > 0) {\n            component = helper(parts.shift().trim(), context);\n        }\n        if (parts.length > 0) {\n            text = helper(parts.join(',').trim(), context);\n        }\n\n        var templateName = iconSystem.getTemplateName();\n        var searchKey = this.currentThemeName + '/' + templateName;\n        var template = templateCache[searchKey];\n\n        component = getNormalisedComponent(component);\n\n        // The key might have been escaped by the JS Mustache engine which\n        // converts forward slashes to HTML entities. Let us undo that here.\n        key = key.replace(/&#x2F;/gi, '/');\n\n        return iconSystem.renderIcon(\n            key,\n            component,\n            text,\n            template\n        );\n    };\n\n    /**\n     * Render blocks of javascript and save them in an array.\n     *\n     * @method jsHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to save as a js block.\n     * @param {function} helper Used to render the block.\n     * @return {string}\n     */\n    Renderer.prototype.jsHelper = function(context, sectionText, helper) {\n        this.requiredJS.push(helper(sectionText, context));\n        return '';\n    };\n\n    /**\n     * String helper used to render {{#str}}abd component { a : 'fish'}{{/str}}\n     * into a get_string call.\n     *\n     * @method stringHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @return {string}\n     */\n    Renderer.prototype.stringHelper = function(context, sectionText, helper) {\n        var parts = sectionText.split(',');\n        var key = '';\n        var component = '';\n        var param = '';\n        if (parts.length > 0) {\n            key = parts.shift().trim();\n        }\n        if (parts.length > 0) {\n            component = parts.shift().trim();\n        }\n        if (parts.length > 0) {\n            param = parts.join(',').trim();\n        }\n\n        component = getNormalisedComponent(component);\n\n        if (param !== '') {\n            // Allow variable expansion in the param part only.\n            param = helper(param, context);\n        }\n\n        // Allow json formatted $a arguments.\n        if (param.match(/^{\\s*\"/gm)) {\n            // If it can't be parsed then the string is not a JSON format.\n            try {\n                const parsedParam = JSON.parse(param);\n                // Handle non-exception-throwing cases, e.g. null, integer, boolean.\n                if (parsedParam && typeof parsedParam === \"object\") {\n                    param = parsedParam;\n                }\n            } catch (err) {\n                // This was probably not JSON.\n                // Keep the error message visible.\n                window.console.warn(err.message);\n            }\n        }\n\n        var index = this.requiredStrings.length;\n        this.requiredStrings.push({\n            key: key,\n            component: component,\n            param: param\n        });\n\n        // The placeholder must not use {{}} as those can be misinterpreted by the engine.\n        return '[[_s' + index + ']]';\n    };\n\n    /**\n     * String helper to render {{#cleanstr}}abd component { a : 'fish'}{{/cleanstr}}\n     * into a get_string following by an HTML escape.\n     *\n     * @method cleanStringHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @return {string}\n     */\n    Renderer.prototype.cleanStringHelper = function(context, sectionText, helper) {\n        var str = this.stringHelper(context, sectionText, helper);\n\n        // We're going to use [[_cx]] format for clean strings, where x is a number.\n        // Hence, replacing 's' with 'c' in the placeholder that stringHelper returns.\n        return str.replace('s', 'c');\n    };\n\n    /**\n     * Quote helper used to wrap content in quotes, and escape all special JSON characters present in the content.\n     *\n     * @method quoteHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @return {string}\n     */\n    Renderer.prototype.quoteHelper = function(context, sectionText, helper) {\n        var content = helper(sectionText.trim(), context);\n\n        // Escape the {{ and JSON encode.\n        // This involves wrapping {{, and }} in change delimeter tags.\n        content = JSON.stringify(content);\n        content = content.replace(/([{}]{2,3})/g, '{{=<% %>=}}$1<%={{ }}=%>');\n        return content;\n    };\n\n    /**\n     * Shorten text helper to truncate text and append a trailing ellipsis.\n     *\n     * @method shortenTextHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @return {string}\n     */\n    Renderer.prototype.shortenTextHelper = function(context, sectionText, helper) {\n        // Non-greedy split on comma to grab section text into the length and\n        // text parts.\n        var regex = /(.*?),(.*)/;\n        var parts = sectionText.match(regex);\n        // The length is the part matched in the first set of parethesis.\n        var length = parts[1].trim();\n        // The length is the part matched in the second set of parethesis.\n        var text = parts[2].trim();\n        var content = helper(text, context);\n        return Truncate.truncate(content, {\n            length: length,\n            words: true,\n            ellipsis: '...'\n        });\n    };\n\n    /**\n     * User date helper to render user dates from timestamps.\n     *\n     * @method userDateHelper\n     * @private\n     * @param {object} context The current mustache context.\n     * @param {string} sectionText The text to parse the arguments from.\n     * @param {function} helper Used to render subsections of the text.\n     * @return {string}\n     */\n    Renderer.prototype.userDateHelper = function(context, sectionText, helper) {\n        // Non-greedy split on comma to grab the timestamp and format.\n        var regex = /(.*?),(.*)/;\n        var parts = sectionText.match(regex);\n        var timestamp = helper(parts[1].trim(), context);\n        var format = helper(parts[2].trim(), context);\n        var index = this.requiredDates.length;\n\n        this.requiredDates.push({\n            timestamp: timestamp,\n            format: format\n        });\n\n        return '[[_t_' + index + ']]';\n    };\n\n    /**\n     * Return a helper function to be added to the context for rendering the a\n     * template.\n     *\n     * This will parse the provided text before giving it to the helper function\n     * in order to remove any disallowed nested helpers to prevent one helper\n     * from calling another.\n     *\n     * In particular to prevent the JS helper from being called from within another\n     * helper because it can lead to security issues when the JS portion is user\n     * provided.\n     *\n     * @param  {function} helperFunction The helper function to add\n     * @param  {object} context The template context for the helper function\n     * @return {Function} To be set in the context\n     */\n    Renderer.prototype.addHelperFunction = function(helperFunction, context) {\n        return function() {\n            return function(sectionText, helper) {\n                // Override the disallowed helpers in the template context with\n                // a function that returns an empty string for use when executing\n                // other helpers. This is to prevent these helpers from being\n                // executed as part of the rendering of another helper in order to\n                // prevent any potential security issues.\n                var originalHelpers = disallowedNestedHelpers.reduce(function(carry, name) {\n                    if (context.hasOwnProperty(name)) {\n                        carry[name] = context[name];\n                    }\n\n                    return carry;\n                }, {});\n\n                disallowedNestedHelpers.forEach(function(helperName) {\n                    context[helperName] = function() {\n                        return '';\n                    };\n                });\n\n                // Execute the helper with the modified context that doesn't include\n                // the disallowed nested helpers. This prevents the disallowed\n                // helpers from being called from within other helpers.\n                var result = helperFunction.apply(this, [context, sectionText, helper]);\n\n                // Restore the original helper implementation in the context so that\n                // any further rendering has access to them again.\n                for (var name in originalHelpers) {\n                    context[name] = originalHelpers[name];\n                }\n\n                return result;\n            }.bind(this);\n        }.bind(this);\n    };\n\n    /**\n     * Add some common helper functions to all context objects passed to templates.\n     * These helpers match exactly the helpers available in php.\n     *\n     * @method addHelpers\n     * @private\n     * @param {Object} context Simple types used as the context for the template.\n     * @param {String} themeName We set this multiple times, because there are async calls.\n     */\n    Renderer.prototype.addHelpers = function(context, themeName) {\n        this.currentThemeName = themeName;\n        this.requiredStrings = [];\n        this.requiredJS = [];\n        context.uniqid = (uniqInstances++);\n        context.str = this.addHelperFunction(this.stringHelper, context);\n        context.cleanstr = this.addHelperFunction(this.cleanStringHelper, context);\n        context.pix = this.addHelperFunction(this.pixHelper, context);\n        context.js = this.addHelperFunction(this.jsHelper, context);\n        context.quote = this.addHelperFunction(this.quoteHelper, context);\n        context.shortentext = this.addHelperFunction(this.shortenTextHelper, context);\n        context.userdate = this.addHelperFunction(this.userDateHelper, context);\n        context.globals = {config: config};\n        context.currentTheme = themeName;\n    };\n\n    /**\n     * Get all the JS blocks from the last rendered template.\n     *\n     * @method getJS\n     * @private\n     * @return {string}\n     */\n    Renderer.prototype.getJS = function() {\n        var js = '';\n        if (this.requiredJS.length > 0) {\n            js = this.requiredJS.join(\";\\n\");\n        }\n\n        return js;\n    };\n\n    /**\n     * Treat strings in content.\n     *\n     * The purpose of this method is to replace the placeholders found in a string\n     * with the their respective translated strings.\n     *\n     * Previously we were relying on String.replace() but the complexity increased with\n     * the numbers of strings to replace. Now we manually walk the string and stop at each\n     * placeholder we find, only then we replace it. Most of the time we will\n     * replace all the placeholders in a single run, at times we will need a few\n     * more runs when placeholders are replaced with strings that contain placeholders\n     * themselves.\n     *\n     * @param {String} content The content in which string placeholders are to be found.\n     * @param {Array} strings The strings to replace with.\n     * @return {String} The treated content.\n     */\n    Renderer.prototype.treatStringsInContent = function(content, strings) {\n        var pattern = /\\[\\[_(s|c)\\d+\\]\\]/,\n            treated,\n            index,\n            strIndex,\n            walker,\n            char,\n            strFinal,\n            isClean;\n\n        do {\n            treated = '';\n            index = content.search(pattern);\n            while (index > -1) {\n\n                // Copy the part prior to the placeholder to the treated string.\n                treated += content.substring(0, index);\n                content = content.substr(index);\n                isClean = content[3] == 'c';\n                strIndex = '';\n                walker = 4; // 4 is the length of either '[[_s' or '[[_c'.\n\n                // Walk the characters to manually extract the index of the string from the placeholder.\n                char = content.substr(walker, 1);\n                do {\n                    strIndex += char;\n                    walker++;\n                    char = content.substr(walker, 1);\n                } while (char != ']');\n\n                // Get the string, add it to the treated result, and remove the placeholder from the content to treat.\n                strFinal = strings[parseInt(strIndex, 10)];\n                if (typeof strFinal === 'undefined') {\n                    Log.debug('Could not find string for pattern [[_' + (isClean ? 'c' : 's') + strIndex + ']].');\n                    strFinal = '';\n                }\n                if (isClean) {\n                    strFinal = mustache.escape(strFinal);\n                }\n                treated += strFinal;\n                content = content.substr(6 + strIndex.length); // 6 is the length of the placeholder without the index.\n                                                               // That's either '[[_s]]' or '[[_c]]'.\n\n                // Find the next placeholder.\n                index = content.search(pattern);\n            }\n\n            // The content becomes the treated part with the rest of the content.\n            content = treated + content;\n\n            // Check if we need to walk the content again, in case strings contained placeholders.\n            index = content.search(pattern);\n\n        } while (index > -1);\n\n        return content;\n    };\n\n    /**\n     * Treat strings in content.\n     *\n     * The purpose of this method is to replace the date placeholders found in the\n     * content with the their respective translated dates.\n     *\n     * @param {String} content The content in which string placeholders are to be found.\n     * @param {Array} dates The dates to replace with.\n     * @return {String} The treated content.\n     */\n    Renderer.prototype.treatDatesInContent = function(content, dates) {\n        dates.forEach(function(date, index) {\n            var key = '\\\\[\\\\[_t_' + index + '\\\\]\\\\]';\n            var re = new RegExp(key, 'g');\n            content = content.replace(re, date);\n        });\n\n        return content;\n    };\n\n    /**\n     * Render a template and then call the callback with the result.\n     *\n     * @method doRender\n     * @private\n     * @param {string} templateSource The mustache template to render.\n     * @param {Object} context Simple types used as the context for the template.\n     * @param {String} themeName Name of the current theme.\n     * @return {Promise} object\n     */\n    Renderer.prototype.doRender = function(templateSource, context, themeName) {\n        this.currentThemeName = themeName;\n        var iconTemplate = iconSystem.getTemplateName();\n\n        var pendingPromise = new Pending('core/templates:doRender');\n        return this.getTemplate(iconTemplate).then(function() {\n            this.addHelpers(context, themeName);\n            var result = mustache.render(templateSource, context, this.partialHelper.bind(this));\n            return $.Deferred().resolve(result.trim(), this.getJS()).promise();\n        }.bind(this))\n        .then(function(html, js) {\n            if (this.requiredStrings.length > 0) {\n                return str.get_strings(this.requiredStrings).then(function(strings) {\n\n                    // Make sure string substitutions are done for the userdate\n                    // values as well.\n                    this.requiredDates = this.requiredDates.map(function(date) {\n                        return {\n                            timestamp: this.treatStringsInContent(date.timestamp, strings),\n                            format: this.treatStringsInContent(date.format, strings)\n                        };\n                    }.bind(this));\n\n                    // Why do we not do another call the render here?\n                    //\n                    // Because that would expose DOS holes. E.g.\n                    // I create an assignment called \"{{fish\" which\n                    // would get inserted in the template in the first pass\n                    // and cause the template to die on the second pass (unbalanced).\n                    html = this.treatStringsInContent(html, strings);\n                    js = this.treatStringsInContent(js, strings);\n                    return $.Deferred().resolve(html, js).promise();\n                }.bind(this));\n            }\n\n            return $.Deferred().resolve(html, js).promise();\n        }.bind(this))\n        .then(function(html, js) {\n            // This has to happen after the strings replacement because you can\n            // use the string helper in content for the user date helper.\n            if (this.requiredDates.length > 0) {\n                return UserDate.get(this.requiredDates).then(function(dates) {\n                    html = this.treatDatesInContent(html, dates);\n                    js = this.treatDatesInContent(js, dates);\n                    return $.Deferred().resolve(html, js).promise();\n                }.bind(this));\n            }\n\n            return $.Deferred().resolve(html, js).promise();\n        }.bind(this))\n        .then(function(html, js) {\n            pendingPromise.resolve();\n            return $.Deferred().resolve(html, js).promise();\n        });\n    };\n\n    /**\n     * Execute a block of JS returned from a template.\n     * Call this AFTER adding the template HTML into the DOM so the nodes can be found.\n     *\n     * @method runTemplateJS\n     * @param {string} source - A block of javascript.\n     */\n    var runTemplateJS = function(source) {\n        if (source.trim() !== '') {\n            var newscript = $('<script>').attr('type', 'text/javascript').html(source);\n            $('head').append(newscript);\n        }\n    };\n\n    /**\n     * Do some DOM replacement and trigger correct events and fire javascript.\n     *\n     * @method domReplace\n     * @private\n     * @param {JQuery} element - Element or selector to replace.\n     * @param {String} newHTML - HTML to insert / replace.\n     * @param {String} newJS - Javascript to run after the insertion.\n     * @param {Boolean} replaceChildNodes - Replace only the childnodes, alternative is to replace the entire node.\n     * @return {Array} The list of new DOM Nodes\n     * @fires event:filterContentUpdated\n     */\n    var domReplace = function(element, newHTML, newJS, replaceChildNodes) {\n        var replaceNode = $(element);\n        if (replaceNode.length) {\n            // First create the dom nodes so we have a reference to them.\n            var newNodes = $(newHTML);\n            var yuiNodes = null;\n            // Do the replacement in the page.\n            if (replaceChildNodes) {\n                // Cleanup any YUI event listeners attached to any of these nodes.\n                yuiNodes = new Y.NodeList(replaceNode.children().get());\n                yuiNodes.destroy(true);\n\n                // JQuery will cleanup after itself.\n                replaceNode.empty();\n                replaceNode.append(newNodes);\n            } else {\n                // Cleanup any YUI event listeners attached to any of these nodes.\n                yuiNodes = new Y.NodeList(replaceNode.get());\n                yuiNodes.destroy(true);\n\n                // JQuery will cleanup after itself.\n                replaceNode.replaceWith(newNodes);\n            }\n            // Run any javascript associated with the new HTML.\n            runTemplateJS(newJS);\n            // Notify all filters about the new content.\n            filterEvents.notifyFilterContentUpdated(newNodes);\n\n            return newNodes.get();\n        }\n\n        return [];\n    };\n\n    /**\n     * Scan a template source for partial tags and return a list of the found partials.\n     *\n     * @method scanForPartials\n     * @private\n     * @param {string} templateSource - source template to scan.\n     * @return {Array} List of partials.\n     */\n    Renderer.prototype.scanForPartials = function(templateSource) {\n        var tokens = mustache.parse(templateSource),\n            partials = [];\n\n        var findPartial = function(tokens, partials) {\n            var i, token;\n            for (i = 0; i < tokens.length; i++) {\n                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     * @private\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 {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    Renderer.prototype.cachePartials = function(templateName, parentage) {\n        var searchKey = this.currentThemeName + '/' + templateName;\n\n        if (searchKey in cachePartialPromises) {\n            return 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        parentage = parentage || [searchKey];\n\n        cachePartialPromises[searchKey] = $.Deferred();\n\n        this.getTemplate(templateName)\n        .then(function(templateSource) {\n            var partials = this.scanForPartials(templateSource);\n            var uniquePartials = partials.filter(function(partialName) {\n                // Check for recursion.\n\n                if (parentage.indexOf(this.currentThemeName + '/' + 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            }.bind(this));\n\n            // Fetch any partial which has not already been fetched.\n            var fetchThemAll = uniquePartials.map(function(partialName) {\n                parentage.push(this.currentThemeName + '/' + partialName);\n                return this.cachePartials(partialName, parentage);\n            }.bind(this));\n\n            // Resolve the templateName promise when all of the children are resolved.\n            return $.when.apply($, fetchThemAll)\n            .then(function() {\n                return cachePartialPromises[searchKey].resolve(templateSource);\n            });\n        }.bind(this))\n        .catch(cachePartialPromises[searchKey].reject);\n\n        return cachePartialPromises[searchKey];\n    };\n\n    /**\n     * Load a template and call doRender on it.\n     *\n     * @method render\n     * @private\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 {Object} context - Could be array, string or simple value for the context of the template.\n     * @param {string} themeName - Name of the current theme.\n     * @return {Promise} JQuery promise object resolved when the template has been rendered.\n     */\n    Renderer.prototype.render = function(templateName, context, themeName) {\n        if (typeof (themeName) === \"undefined\") {\n            // System context by default.\n            themeName = config.theme;\n        }\n\n        this.currentThemeName = themeName;\n\n        // Preload the module to do the icon rendering based on the theme iconsystem.\n        var modulename = config.iconsystemmodule;\n\n        var ready = $.Deferred();\n        require([modulename], function(System) {\n            var system = new System();\n            if (!(system instanceof IconSystem)) {\n                ready.reject('Invalid icon system specified' + config.iconsystem);\n            } else {\n                iconSystem = system;\n                system.init().then(ready.resolve).catch(notification.exception);\n            }\n        });\n\n        return ready.then(function() {\n                return this.cachePartials(templateName);\n            }.bind(this)).then(function(templateSource) {\n                return this.doRender(templateSource, context, themeName);\n            }.bind(this));\n    };\n\n    /**\n     * Prepend some HTML to a node and trigger events and fire javascript.\n     *\n     * @method domPrepend\n     * @private\n     * @param {jQuery|String} element - Element or selector to prepend HTML to\n     * @param {String} html - HTML to prepend\n     * @param {String} js - Javascript to run after we prepend the html\n     * @return {Array} The list of new DOM Nodes\n     * @fires event:filterContentUpdated\n     */\n    var domPrepend = function(element, html, js) {\n        var node = $(element);\n        if (node.length) {\n            // Prepend the html.\n            var newContent = $(html);\n            node.prepend(newContent);\n            // Run any javascript associated with the new HTML.\n            runTemplateJS(js);\n            // Notify all filters about the new content.\n            filterEvents.notifyFilterContentUpdated(node);\n\n            return newContent.get();\n        }\n\n        return [];\n    };\n\n    /**\n     * Append some HTML to a node and trigger events and fire javascript.\n     *\n     * @method domAppend\n     * @private\n     * @param {jQuery|String} element - Element or selector to append HTML to\n     * @param {String} html - HTML to append\n     * @param {String} js - Javascript to run after we append the html\n     * @return {Array} The list of new DOM Nodes\n     * @fires event:filterContentUpdated\n     */\n    var domAppend = function(element, html, js) {\n        var node = $(element);\n        if (node.length) {\n            // Append the html.\n            var newContent = $(html);\n            node.append(newContent);\n            // Run any javascript associated with the new HTML.\n            runTemplateJS(js);\n            // Notify all filters about the new content.\n            filterEvents.notifyFilterContentUpdated(node);\n\n            return newContent.get();\n        }\n\n        return [];\n    };\n\n    return /** @alias module:core/templates */ {\n        // Public variables and functions.\n        /**\n         * Every call to render creates a new instance of the class and calls render on it. This\n         * means each render call has it's own class variables.\n         *\n         * @method render\n         * @private\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 {Object} context - Could be array, string or simple value for the context of the template.\n         * @param {string} themeName - Name of the current theme.\n         * @return {Promise} JQuery promise object resolved when the template has been rendered.\n         */\n        render: function(templateName, context, themeName) {\n            var renderer = new Renderer();\n            return renderer.render(templateName, context, themeName);\n        },\n\n        /**\n         * Prefetch a set of templates without rendering them.\n         *\n         * @method getTemplate\n         * @param {Array} templateNames The list of templates to fetch\n         * @param {String} themeName\n         * @returns {Promise}\n         */\n        prefetchTemplates: function(templateNames, themeName) {\n            var renderer = new Renderer();\n\n            if (typeof themeName === \"undefined\") {\n                // System context by default.\n                themeName = config.theme;\n            }\n\n            return renderer.prefetchTemplates(templateNames, themeName);\n        },\n\n        /**\n         * Every call to render creates a new instance of the class and calls render on it. This\n         * means each render call has it's own class variables.\n         *\n         * This alernate to the standard .render() function returns the html and js in a single object suitable for a\n         * native Promise.\n         *\n         * @method renderForPromise\n         * @private\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 {Object} context - Could be array, string or simple value for the context of the template.\n         * @param {string} themeName - Name of the current theme.\n         * @return {Promise} JQuery promise object resolved when the template has been rendered.\n         */\n        renderForPromise: function(templateName, context, themeName) {\n            var renderer = new Renderer();\n            return renderer.render(templateName, context, themeName)\n            .then(function(html, js) {\n                return {\n                    html: html,\n                    js: js,\n                };\n            });\n        },\n\n        /**\n         * Every call to renderIcon creates a new instance of the class and calls renderIcon on it. This\n         * means each render call has it's own class variables.\n         *\n         * @method renderIcon\n         * @public\n         * @param {string} key - Icon key.\n         * @param {string} component - Icon component\n         * @param {string} title - Icon title\n         * @return {Promise} JQuery promise object resolved when the pix has been rendered.\n         */\n        renderPix: function(key, component, title) {\n            var renderer = new Renderer();\n            return renderer.renderIcon(\n                key,\n                getNormalisedComponent(component),\n                title\n            );\n        },\n\n        /**\n         * Execute a block of JS returned from a template.\n         * Call this AFTER adding the template HTML into the DOM so the nodes can be found.\n         *\n         * @method runTemplateJS\n         * @param {string} source - A block of javascript.\n         */\n        runTemplateJS: runTemplateJS,\n\n        /**\n         * Replace a node in the page with some new HTML and run the JS.\n         *\n         * @method replaceNodeContents\n         * @param {JQuery} element - Element or selector to replace.\n         * @param {String} newHTML - HTML to insert / replace.\n         * @param {String} newJS - Javascript to run after the insertion.\n         * @return {Array} The list of new DOM Nodes\n         */\n        replaceNodeContents: function(element, newHTML, newJS) {\n            return domReplace(element, newHTML, newJS, true);\n        },\n\n        /**\n         * Insert a node in the page with some new HTML and run the JS.\n         *\n         * @method replaceNode\n         * @param {JQuery} element - Element or selector to replace.\n         * @param {String} newHTML - HTML to insert / replace.\n         * @param {String} newJS - Javascript to run after the insertion.\n         * @return {Array} The list of new DOM Nodes\n         */\n        replaceNode: function(element, newHTML, newJS) {\n            return domReplace(element, newHTML, newJS, false);\n        },\n\n        /**\n         * Prepend some HTML to a node and trigger events and fire javascript.\n         *\n         * @method prependNodeContents\n         * @param {jQuery|String} element - Element or selector to prepend HTML to\n         * @param {String} html - HTML to prepend\n         * @param {String} js - Javascript to run after we prepend the html\n         * @return {Array} The list of new DOM Nodes\n         */\n        prependNodeContents: function(element, html, js) {\n            return domPrepend(element, html, js);\n        },\n\n        /**\n         * Append some HTML to a node and trigger events and fire javascript.\n         *\n         * @method appendNodeContents\n         * @param {jQuery|String} element - Element or selector to append HTML to\n         * @param {String} html - HTML to append\n         * @param {String} js - Javascript to run after we append the html\n         * @return {Array} The list of new DOM Nodes\n         */\n        appendNodeContents: function(element, html, js) {\n            return domAppend(element, html, js);\n        },\n    };\n});\n"],"names":["define","mustache","$","ajax","str","notification","coreurl","config","storage","IconSystem","filterEvents","Y","Log","Truncate","UserDate","Pending","uniqInstances","templateCache","templatePromises","cachePartialPromises","iconSystem","loadTemplateBuffer","isLoadingTemplates","disallowedNestedHelpers","getNormalisedComponent","component","getTemplatePromiseFromCache","searchKey","Deferred","resolve","promise","M","cfg","templaterev","cached","get","processLoadTemplateBuffer","length","templatesToLoad","slice","serverRequestsDeferred","requests","map","templateData","name","theme","templateDeferred","deferred","cachedPromise","push","methodname","args","template","themename","lang","attr","replace","index","then","promises","response","templateSource","templates","forEach","data","tempSearchKey","join","value","set","strings","cache_strings","key","source","catch","error","reject","call","when","apply","splice","Renderer","requiredStrings","requiredJS","requiredDates","currentThemeName","prototype","getTemplate","templateName","currentTheme","this","existingBufferRecords","filter","record","parts","split","shift","prefetchTemplates","templateNames","partialHelper","exception","Error","renderIcon","title","modulename","iconsystemmodule","ready","require","System","system","init","getTemplateName","bind","pixHelper","context","sectionText","helper","text","trim","jsHelper","stringHelper","param","match","parsedParam","JSON","parse","err","window","console","warn","message","cleanStringHelper","quoteHelper","content","stringify","shortenTextHelper","truncate","words","ellipsis","userDateHelper","timestamp","format","addHelperFunction","helperFunction","originalHelpers","reduce","carry","hasOwnProperty","helperName","result","addHelpers","themeName","uniqid","cleanstr","pix","js","quote","shortentext","userdate","globals","getJS","treatStringsInContent","treated","strIndex","walker","char","strFinal","isClean","pattern","search","substring","substr","parseInt","debug","escape","treatDatesInContent","dates","date","re","RegExp","doRender","iconTemplate","pendingPromise","render","html","get_strings","runTemplateJS","newscript","append","domReplace","element","newHTML","newJS","replaceChildNodes","replaceNode","newNodes","NodeList","children","destroy","empty","replaceWith","notifyFilterContentUpdated","scanForPartials","tokens","partials","findPartial","i","token","cachePartials","parentage","fetchThemAll","partialName","indexOf","iconsystem","renderer","renderForPromise","renderPix","replaceNodeContents","prependNodeContents","node","newContent","prepend","domPrepend","appendNodeContents","domAppend"],"mappings":"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"}
\ No newline at end of file
diff --git a/lib/amd/src/templates.js b/lib/amd/src/templates.js
index a2d06e790fe..7fc1c895984 100644
--- a/lib/amd/src/templates.js
+++ b/lib/amd/src/templates.js
@@ -558,8 +558,7 @@ function(
         }
 
         // Allow json formatted $a arguments.
-        // Added double quote after left curly bracket to differentiate between string and JSON string.
-        if (param.indexOf('{"') === 0) {
+        if (param.match(/^{\s*"/gm)) {
             // If it can't be parsed then the string is not a JSON format.
             try {
                 const parsedParam = JSON.parse(param);