diff --git a/lib/static/views.js b/lib/static/views.js index bad667eee2e40e0667572de7ded35d67ccdea2e4..784c9d31c4fdb23a2713aa015e84cf2b634e4e88 100644 --- a/lib/static/views.js +++ b/lib/static/views.js @@ -242,6 +242,25 @@ exports.setup = function(app) { }); } + // /common/js/templates.js is dynamically built each time + if (!config.get('use_minified_resources')) { + var templates = require('../templates'); + var dialogTemplatesPath = path.join(__dirname, '../../resources/static/dialog/views') + app.get('/common/js/templates.js', function(req, res) { + res.send(templates.generate(dialogTemplatesPath)); + }); + + var siteTemplatesPath = path.join(__dirname, "../../resources/views"); + var sitePartialTemplatesPath = path.join(__dirname, "../../resources/views/partial"); + app.get('/test/mocks/site-templates.js', function(req, res) { + // combine main templates and partials into one big set for development + // mode. + var siteTemplates = templates.generate(siteTemplatesPath, "site/"); + siteTemplates += templates.generate(sitePartialTemplatesPath, "partial/"); + res.send(siteTemplates); + }); + } + // REDIRECTS const REDIRECTS = { "/developers" : "https://developer.mozilla.org/docs/persona" diff --git a/lib/static_resources.js b/lib/static_resources.js index 72d9ea3397611465d5f7866ec461e67f48c1f97a..b4f013d038034bf3850599fc7ca6255118a3eee5 100644 --- a/lib/static_resources.js +++ b/lib/static_resources.js @@ -20,7 +20,6 @@ var common_js = [ '/common/js/lib/winchan.js', '/common/js/lib/underscore.js', '/common/js/lib/bidbundle.js', - '/common/js/lib/ejs.js', '/common/js/lib/micrajax.js', '/common/js/lib/urlparse.js', '/common/js/lib/gobbledygook.js', diff --git a/lib/templates.js b/lib/templates.js new file mode 100644 index 0000000000000000000000000000000000000000..e515eae0002c1503788c0a259911b10d6732e639 --- /dev/null +++ b/lib/templates.js @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +const +fs = require('fs'), +path = require('path'), +ejs = require('ejs'), +config = require('./configuration'); + +var bundles = {}; + +exports.generate = function generate(templatesDir, namePrefix, lastGen) { + if (!namePrefix) namePrefix = ""; + + var bundle = bundles[templatesDir] || (bundles[templatesDir] = {}); + lastGen = lastGen || bundle.lastGen || 0; + var templateData = bundle.data; + + var fileNames = fs.readdirSync(templatesDir); + var templates = []; + + // is a regen necessary? + try { + for (var i = 0; i < fileNames.length; i++) { + if (lastGen < fs.statSync(path.join(templatesDir, fileNames[i])).mtime) { + throw "newer"; + } + } + // no rebuild needed + console.log("templates [%s] up to date", templatesDir); + return templateData; + } catch (e) { + console.log("creating templates [%s]", templatesDir); + } + + for(var index = 0, max = fileNames.length; index < max; index++) { + var fileName = fileNames[index]; + if(fileName.match(/\.ejs$/)) { + var templateName = namePrefix + fileName.replace(/\.ejs/, ''); + var templateText = fs.readFileSync(path.join(templatesDir, fileName), "utf8"); + + // remove HTML comments + templateText = templateText.replace(/<!--[\s\S]*?-->/g, ''); + + templates[templateName] = ejs.compile(templateText, { + client: true, + compileDebug: !config.get('use_minified_resources') + }); + } + } + + templateData = "BrowserID.Templates = BrowserID.Templates || {};"; + for (var t in templates) { + if (templates.hasOwnProperty(t)) { + templateData += "\nBrowserID.Templates['" + t + "'] = " + String(templates[t]) + ";"; + } + } + + bundle.lastGen = Date.now(); + bundle.data = templateData; + return templateData; +}; diff --git a/lockdown.json b/lockdown.json index 3a66f7cf01718e65e3611b463a7d97420fae3b17..c1a5ac7268b982dcda3ee1de682e35583952393e 100644 --- a/lockdown.json +++ b/lockdown.json @@ -83,7 +83,7 @@ "0.2.0": "d46b5eb799ea82e51b8788f1ae37098b63119409" }, "ejs": { - "0.4.3": "8143c3656955b8934db5d9da83e9be73176f1f4f" + "0.8.3": "db8aac47ff80a7df82b4c82c126fe8970870626f" }, "esprima": { "0.9.9": "1b90925c975d632d7282939c3bb9c3a423c30490" diff --git a/package.json b/package.json index 33fb10b80c79bf1e28cb609a5e53358edbedbc1d..f4e57f1dd6f776500804df48075ffede9e4f566c 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "connect-cachify": "0.0.10", "connect-cookie-session": "0.0.2", "connect-logger-statsd": "0.0.1", - "ejs": "0.4.3", + "ejs": "0.8.3", "etagify": "0.0.2", "express": "2.5.0", "gobbledygook": "0.0.3", diff --git a/resources/static/common/js/lib/ejs.js b/resources/static/common/js/lib/ejs.js deleted file mode 100644 index d2396e0bd9beb2a29ff3a773f1c8ebd2ffb2b6f1..0000000000000000000000000000000000000000 --- a/resources/static/common/js/lib/ejs.js +++ /dev/null @@ -1,514 +0,0 @@ -(function(){ - - -var rsplit = function(string, regex) { - var result = regex.exec(string),retArr = new Array(), first_idx, last_idx, first_bit; - while (result != null) - { - first_idx = result.index; last_idx = regex.lastIndex; - if ((first_idx) != 0) - { - first_bit = string.substring(0,first_idx); - retArr.push(string.substring(0,first_idx)); - string = string.slice(first_idx); - } - retArr.push(result[0]); - string = string.slice(result[0].length); - result = regex.exec(string); - } - if (! string == '') - { - retArr.push(string); - } - return retArr; -}, -chop = function(string){ - return string.substr(0, string.length - 1); -}, -extend = function(d, s){ - for(var n in s){ - if(s.hasOwnProperty(n)) d[n] = s[n] - } -} - - -window.EJS = function( options ){ - options = typeof options == "string" ? {view: options} : options - this.set_options(options); - if(options.precompiled){ - this.template = {}; - this.template.process = options.precompiled; - EJS.update(this.name, this); - return; - } - if(options.element) - { - if(typeof options.element == 'string'){ - var name = options.element - options.element = document.getElementById( options.element ) - if(options.element == null) throw name+'does not exist!' - } - if(options.element.value){ - this.text = options.element.value - }else{ - this.text = options.element.innerHTML - } - this.name = options.element.id - this.type = '[' - }else if(options.url){ - options.url = EJS.endExt(options.url, this.extMatch); - this.name = this.name ? this.name : options.url; - var url = options.url - //options.view = options.absolute_url || options.view || options.; - var template = EJS.get(this.name /*url*/, this.cache); - if (template) return template; - if (template == EJS.INVALID_PATH) return null; - try{ - this.text = EJS.request( url+(this.cache ? '' : '?'+Math.random() )); - }catch(e){} - - if(this.text == null){ - throw( {type: 'EJS', message: 'There is no template at '+url} ); - } - //this.name = url; - } - var template = new EJS.Compiler(this.text, this.type); - - template.compile(options, this.name); - - - EJS.update(this.name, this); - this.template = template; -}; -/* @Prototype*/ -EJS.prototype = { - /** - * Renders an object with extra view helpers attached to the view. - * @param {Object} object data to be rendered - * @param {Object} extra_helpers an object with additonal view helpers - * @return {String} returns the result of the string - */ - render : function(object, extra_helpers){ - object = object || {}; - this._extra_helpers = extra_helpers; - var v = new EJS.Helpers(object, extra_helpers || {}); - return this.template.process.call(object, object,v); - }, - update : function(element, options){ - if(typeof element == 'string'){ - element = document.getElementById(element) - } - if(options == null){ - _template = this; - return function(object){ - EJS.prototype.update.call(_template, element, object) - } - } - if(typeof options == 'string'){ - params = {} - params.url = options - _template = this; - params.onComplete = function(request){ - var object = eval( request.responseText ) - EJS.prototype.update.call(_template, element, object) - } - EJS.ajax_request(params) - }else - { - element.innerHTML = this.render(options) - } - }, - out : function(){ - return this.template.out; - }, - /** - * Sets options on this view to be rendered with. - * @param {Object} options - */ - set_options : function(options){ - this.type = options.type || EJS.type; - this.cache = options.cache != null ? options.cache : EJS.cache; - this.text = options.text || null; - this.name = options.name || null; - this.ext = options.ext || EJS.ext; - this.extMatch = new RegExp(this.ext.replace(/\./, '\.')); - } -}; -EJS.endExt = function(path, match){ - if(!path) return null; - match.lastIndex = 0 - return path+ (match.test(path) ? '' : this.ext ) -} - - - - -/* @Static*/ -EJS.Scanner = function(source, left, right) { - - extend(this, - {left_delimiter: left +'%', - right_delimiter: '%'+right, - double_left: left+'%%', - double_right: '%%'+right, - left_equal: left+'%=', - // set - Persona addition. The backend understands <%-, which acts - // identical to the frontend's <%=. <%= on the backend escapes - // characters to their HTML code equivalents. For unit testing, we - // write backend templates on the front end, so we have to be able to - // process <%-. Creating an alias here. Using it wherever - // left_equal is found. - left_dash: left+'%-', - left_comment: left+'%#'}) - - this.SplitRegexp = left=='[' ? /(\[%%)|(%%\])|(\[%=)|(\[%#)|(\[%)|(%\]\n)|(%\])|(\n)/ : new RegExp('('+this.double_left+')|(%%'+this.double_right+')|('+this.left_equal+')|('+this.left_dash+')|('+this.left_comment+')|('+this.left_delimiter+')|('+this.right_delimiter+'\n)|('+this.right_delimiter+')|(\n)') ; - - this.source = source; - this.stag = null; - this.lines = 0; -}; - -EJS.Scanner.to_text = function(input){ - if(input == null || input === undefined) - return ''; - if(input instanceof Date) - return input.toDateString(); - if(input.toString) - return input.toString(); - return ''; -}; - -EJS.Scanner.prototype = { - scan: function(block) { - var scanline = this.scanline, - regex = this.SplitRegexp; - if (! this.source == '') - { - var source_split = rsplit(this.source, /\n/); - for(var i=0; i<source_split.length; i++) { - var item = source_split[i]; - this.scanline(item, regex, block); - } - } - }, - scanline: function(line, regex, block) { - this.lines++; - var line_split = rsplit(line, regex); - for(var i=0; i<line_split.length; i++) { - var token = line_split[i]; - if (token != null) { - try{ - block(token, this); - }catch(e){ - throw {type: 'EJS.Scanner', line: this.lines}; - } - } - } - } -}; - - -EJS.Buffer = function(pre_cmd, post_cmd) { - this.line = new Array(); - this.script = ""; - this.pre_cmd = pre_cmd; - this.post_cmd = post_cmd; - for (var i=0; i<this.pre_cmd.length; i++) - { - this.push(pre_cmd[i]); - } -}; -EJS.Buffer.prototype = { - - push: function(cmd) { - this.line.push(cmd); - }, - - cr: function() { - this.script = this.script + this.line.join('; '); - this.line = new Array(); - this.script = this.script + "\n"; - }, - - close: function() { - if (this.line.length > 0) - { - for (var i=0; i<this.post_cmd.length; i++){ - this.push(pre_cmd[i]); - } - this.script = this.script + this.line.join('; '); - this.line = null; - } - } - -}; - - -EJS.Compiler = function(source, left) { - this.pre_cmd = ['var ___ViewO = [];']; - this.post_cmd = new Array(); - this.source = ' '; - if (source != null) - { - if (typeof source == 'string') - { - source = source.replace(/\r\n/g, "\n"); - source = source.replace(/\r/g, "\n"); - this.source = source; - }else if (source.innerHTML){ - this.source = source.innerHTML; - } - if (typeof this.source != 'string'){ - this.source = ""; - } - } - left = left || '<'; - var right = '>'; - switch(left) { - case '[': - right = ']'; - break; - case '<': - break; - default: - throw left+' is not a supported deliminator'; - break; - } - this.scanner = new EJS.Scanner(this.source, left, right); - this.out = ''; -}; -EJS.Compiler.prototype = { - compile: function(options, name) { - options = options || {}; - this.out = ''; - var put_cmd = "___ViewO.push("; - var insert_cmd = put_cmd; - var buff = new EJS.Buffer(this.pre_cmd, this.post_cmd); - var content = ''; - var clean = function(content) - { - content = content.replace(/\\/g, '\\\\'); - content = content.replace(/\n/g, '\\n'); - content = content.replace(/"/g, '\\"'); - return content; - }; - this.scanner.scan(function(token, scanner) { - if (scanner.stag == null) - { - switch(token) { - case '\n': - content = content + "\n"; - buff.push(put_cmd + '"' + clean(content) + '");'); - buff.cr(); - content = ''; - break; - case scanner.left_delimiter: - case scanner.left_equal: - case scanner.left_dash: - case scanner.left_comment: - scanner.stag = token; - if (content.length > 0) - { - buff.push(put_cmd + '"' + clean(content) + '")'); - } - content = ''; - break; - case scanner.double_left: - content = content + scanner.left_delimiter; - break; - default: - content = content + token; - break; - } - } - else { - switch(token) { - case scanner.right_delimiter: - switch(scanner.stag) { - case scanner.left_delimiter: - if (content[content.length - 1] == '\n') - { - content = chop(content); - buff.push(content); - buff.cr(); - } - else { - buff.push(content); - } - break; - case scanner.left_dash: - case scanner.left_equal: - buff.push(insert_cmd + "(EJS.Scanner.to_text(" + content + ")))"); - break; - } - scanner.stag = null; - content = ''; - break; - case scanner.double_right: - content = content + scanner.right_delimiter; - break; - default: - content = content + token; - break; - } - } - }); - if (content.length > 0) - { - // Chould be content.dump in Ruby - buff.push(put_cmd + '"' + clean(content) + '")'); - } - buff.close(); - this.out = buff.script + ";"; - var to_be_evaled = '/*'+name+'*/this.process = function(_CONTEXT,_VIEW) { try { with(_VIEW) { with (_CONTEXT) {'+this.out+" return ___ViewO.join('');}}}catch(e){e.lineNumber=null;throw e;}};"; - - try{ - eval(to_be_evaled); - }catch(e){ - if(typeof JSLINT != 'undefined'){ - JSLINT(this.out); - for(var i = 0; i < JSLINT.errors.length; i++){ - var error = JSLINT.errors[i]; - if(error.reason != "Unnecessary semicolon."){ - error.line++; - var e = new Error(); - e.lineNumber = error.line; - e.message = error.reason; - if(options.view) - e.fileName = options.view; - throw e; - } - } - }else{ - throw e; - } - } - } -}; - - -//type, cache, folder -/** - * Sets default options for all views - * @param {Object} options Set view with the following options - * <table class="options"> - <tbody><tr><th>Option</th><th>Default</th><th>Description</th></tr> - <tr> - <td>type</td> - <td>'<'</td> - <td>type of magic tags. Options are '<' or '[' - </td> - </tr> - <tr> - <td>cache</td> - <td>true in production mode, false in other modes</td> - <td>true to cache template. - </td> - </tr> - </tbody></table> - * - */ -EJS.config = function(options){ - EJS.cache = options.cache != null ? options.cache : EJS.cache; - EJS.type = options.type != null ? options.type : EJS.type; - EJS.ext = options.ext != null ? options.ext : EJS.ext; - - var templates_directory = EJS.templates_directory || {}; //nice and private container - EJS.templates_directory = templates_directory; - EJS.get = function(path, cache){ - if(cache == false) return null; - if(templates_directory[path]) return templates_directory[path]; - return null; - }; - - EJS.update = function(path, template) { - if(path == null) return; - templates_directory[path] = template ; - }; - - EJS.INVALID_PATH = -1; -}; -EJS.config( {cache: true, type: '<', ext: '.ejs' } ); - - - -/** - * @constructor - * By adding functions to EJS.Helpers.prototype, those functions will be available in the - * views. - * @init Creates a view helper. This function is called internally. You should never call it. - * @param {Object} data The data passed to the view. Helpers have access to it through this._data - */ -EJS.Helpers = function(data, extras){ - this._data = data; - this._extras = extras; - extend(this, extras ); -}; -/* @prototype*/ -EJS.Helpers.prototype = { - /** - * Renders a new view. If data is passed in, uses that to render the view. - * @param {Object} options standard options passed to a new view. - * @param {optional:Object} data - * @return {String} - */ - view: function(options, data, helpers){ - if(!helpers) helpers = this._extras - if(!data) data = this._data; - return new EJS(options).render(data, helpers); - }, - /** - * For a given value, tries to create a human representation. - * @param {Object} input the value being converted. - * @param {Object} null_text what text should be present if input == null or undefined, defaults to '' - * @return {String} - */ - to_text: function(input, null_text) { - if(input == null || input === undefined) return null_text || ''; - if(input instanceof Date) return input.toDateString(); - if(input.toString) return input.toString().replace(/\n/g, '<br />').replace(/''/g, "'"); - return ''; - } -}; - EJS.newRequest = function(){ - var factories = [function() { return new ActiveXObject("Msxml2.XMLHTTP"); },function() { return new XMLHttpRequest(); },function() { return new ActiveXObject("Microsoft.XMLHTTP"); }]; - for(var i = 0; i < factories.length; i++) { - try { - var request = factories[i](); - if (request != null) return request; - } - catch(e) { continue;} - } - } - - EJS.request = function(path){ - var request = new EJS.newRequest() - request.open("GET", path, false); - - try{request.send(null);} - catch(e){return null;} - - if ( request.status == 404 || request.status == 2 ||(request.status == 0 && request.responseText == '') ) return null; - - return request.responseText - } - EJS.ajax_request = function(params){ - params.method = ( params.method ? params.method : 'GET') - - var request = new EJS.newRequest(); - request.onreadystatechange = function(){ - if(request.readyState == 4){ - if(request.status == 200){ - params.onComplete(request) - }else - { - params.onComplete(request) - } - } - } - request.open(params.method, params.url) - request.send(null) - } - - -})(); diff --git a/resources/static/common/js/renderer.js b/resources/static/common/js/renderer.js index 4f74c0c414b83e8af803fdc1be5f61bbdcfd174d..ad8dae95a7a0dfa43f767ffa7901a74353637f19 100644 --- a/resources/static/common/js/renderer.js +++ b/resources/static/common/js/renderer.js @@ -7,34 +7,23 @@ BrowserID.Renderer = (function() { "use strict"; var bid = BrowserID, - dom = bid.DOM, - templateCache = {}; + dom = bid.DOM; function getTemplateHTML(templateName, vars) { - var config, - templateText = bid.Templates[templateName], - vars = vars || {}; - - if(templateText) { - config = { - text: templateText - }; - } - else { - // TODO - be able to set the directory - config = { - url: "/dialog/views/" + templateName + ".ejs" - }; - } - - var template = templateCache[templateName]; - if(!template) { - template = new EJS(config); - templateCache[templateName] = template; + var templateFn = bid.Templates[templateName]; + if (!templateFn) throw "Template not found: " + templateName; + + var localVars = _.extend({}, vars); + if(!localVars.partial) { + localVars.partial = function(name) { + // partials are not supported by the client side EJS. Create + // a standin that does what partial rendering would do on the backend. + return getTemplateHTML(name, vars); + } } - var html = template.render(vars); - return html; + // arguments are: locals, filters (which cant be used client-side), escapeFn + return templateFn.call(null, localVars); } function render(target, templateName, vars) { diff --git a/resources/static/dialog/views/authenticate.ejs b/resources/static/dialog/views/authenticate.ejs index 285c4f58a1112e0d360486a909ba613c164e0a09..ad963538c07c8799f5e569e554b4873d3fa98703 100644 --- a/resources/static/dialog/views/authenticate.ejs +++ b/resources/static/dialog/views/authenticate.ejs @@ -4,7 +4,7 @@ <div class="form_section"> <p class="start"> - <%= format(gettext('%s uses Persona instead of usernames to sign you in.'), ["<strong>" + siteName +"</strong>"]) %> + <%- format(gettext('%s uses Persona instead of usernames to sign you in.'), ["<strong>" + siteName + "</strong>"]) %> </p> @@ -58,7 +58,7 @@ <p class="submit tospp"> - <%= format(gettext("By proceeding, you agree to %(site)'s <a %(terms)>Terms</a> and <a %(privacy)>Privacy Policy</a>."), + <%- format(gettext("By proceeding, you agree to %(site)'s <a %(terms)>Terms</a> and <a %(privacy)>Privacy Policy</a>."), { site: "Persona", terms: 'href="https://login.persona.org/tos" target="_new"', privacy: 'href="https://login.persona.org/privacy" target="_new"' }) %> diff --git a/resources/static/dialog/views/confirm_email.ejs b/resources/static/dialog/views/confirm_email.ejs index a796667bc6c500bd03bee8a8ede2f2926e40412c..f11114fe33b8bc3647f706b14ecc7175b7627de0 100644 --- a/resources/static/dialog/views/confirm_email.ejs +++ b/resources/static/dialog/views/confirm_email.ejs @@ -5,10 +5,10 @@ <h2><%= gettext('Confirm your email address') %></h2> <p> - <%= format(gettext('Check your email at %s.'), ["<strong>" + email + "</strong>"]) %> + <%- format(gettext('Check your email at %s.'), ["<strong>" + escape(email) + "</strong>"]) %> </p> <p> - <%= format(gettext('Click the link in the confirmation email. You\'ll then immediately be signed in to %s.'), ["<strong>" + siteName + "</strong>"]) %> + <%- format(gettext('Click the link in the confirmation email. You\'ll then immediately be signed in to %s.'), ["<strong>" + siteName + "</strong>"]) %> </p> diff --git a/resources/static/dialog/views/error.ejs b/resources/static/dialog/views/error.ejs index 14f7c8f39c28e2b44bf2f1387247e7695ff3eec3..c32abe1793583a8453a84ce6de4501acf6bb1c68 100644 --- a/resources/static/dialog/views/error.ejs +++ b/resources/static/dialog/views/error.ejs @@ -13,7 +13,7 @@ <h2 id="error_403"> <%= gettext("Persona requires cookies to remember you.") %> </h2> - <%= format(gettext("Please close this window, <a %s>enable cookies</a> and try again"), [" target='_blank' href='http://support.mozilla.org/kb/Websites%20say%20cookies%20are%20blocked'"]) %> + <%- format(gettext("Please close this window, <a %s>enable cookies</a> and try again"), [" target='_blank' href='http://support.mozilla.org/kb/Websites%20say%20cookies%20are%20blocked'"]) %> <% } else if(typeof title === "string") { %> <h2> <span class="emphasis"><%= title %></span> diff --git a/resources/static/dialog/views/required_email.ejs b/resources/static/dialog/views/required_email.ejs index 500327fbfee2c46573f4bf473542d07848b2d8b5..2934b42982d63e4199d31bd06a1b9526da1e8c44 100644 --- a/resources/static/dialog/views/required_email.ejs +++ b/resources/static/dialog/views/required_email.ejs @@ -63,7 +63,7 @@ </p> <% if (personaTOSPP) { %> <p class="tospp"> - <%= format(gettext("By proceeding, you agree to %(site)'s <a %(terms)>Terms</a> and <a %(privacy)>Privacy Policy</a>."), + <%- format(gettext("By proceeding, you agree to %(site)'s <a %(terms)>Terms</a> and <a %(privacy)>Privacy Policy</a>."), { site: "Persona", terms: 'href="https://login.persona.org/tos" target="_new"', privacy: 'href="https://login.persona.org/privacy" target="_new"' }) %> diff --git a/resources/static/dialog/views/rp_info.ejs b/resources/static/dialog/views/rp_info.ejs index 4ae1e5a072a978a33d7676d706c272d0d75c83d0..43a4b3140cc82e70349b8321af6418cbea4e1847 100644 --- a/resources/static/dialog/views/rp_info.ejs +++ b/resources/static/dialog/views/rp_info.ejs @@ -19,7 +19,7 @@ <% if(privacyPolicy && termsOfService) { %> <p id="rptospp" class="tospp"> - <%= format(gettext("By proceeding, you agree to %(site)'s <a %(terms)>Terms</a> and <a %(privacy)>Privacy Policy</a>."), + <%- format(gettext("By proceeding, you agree to %(site)'s <a %(terms)>Terms</a> and <a %(privacy)>Privacy Policy</a>."), { terms: 'href="' + termsOfService + '" id="rp_tos" target="_blank"', privacy: 'href="' + privacyPolicy + '" id="rp_pp" target="_blank"', diff --git a/resources/static/dialog/views/set_password.ejs b/resources/static/dialog/views/set_password.ejs index 74a75f3d11d4da5eacea04ecc4e7506a94e415ea..ed213f78304faa1a4913dda802d58e8fe0a2c85a 100644 --- a/resources/static/dialog/views/set_password.ejs +++ b/resources/static/dialog/views/set_password.ejs @@ -71,7 +71,7 @@ <% if (personaTOSPP) { %> <p id="persona_tospp" class="submit tospp"> - <%= format(gettext("By proceeding, you agree to %(site)'s <a %(terms)>Terms</a> and <a %(privacy)>Privacy Policy</a>."), + <%- format(gettext("By proceeding, you agree to %(site)'s <a %(terms)>Terms</a> and <a %(privacy)>Privacy Policy</a>."), { site: "Persona", terms: 'href="https://login.persona.org/tos" target="_new"', privacy: 'href="https://login.persona.org/privacy" target="_new"' }) %> diff --git a/resources/static/dialog/views/test_template_with_partial.ejs b/resources/static/dialog/views/test_template_with_partial.ejs new file mode 100644 index 0000000000000000000000000000000000000000..11c1a6c41043aef2d0a499fa90039be1a26e5a2a --- /dev/null +++ b/resources/static/dialog/views/test_template_with_partial.ejs @@ -0,0 +1,6 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<%- partial('test_template_no_input') %> + diff --git a/resources/static/dialog/views/verify_primary_user.ejs b/resources/static/dialog/views/verify_primary_user.ejs index 695924ac81a007098eea328f7f365730cf6de3d4..76f26652ef056d4d04386c8ecbf058314f3de672 100644 --- a/resources/static/dialog/views/verify_primary_user.ejs +++ b/resources/static/dialog/views/verify_primary_user.ejs @@ -22,7 +22,7 @@ <% if (personaTOSPP) { %> <p id="persona_tospp" class="submit tospp"> - <%= format(gettext("By proceeding, you agree to %(site)'s <a %(terms)>Terms</a> and <a %(privacy)>Privacy Policy</a>."), + <%- format(gettext("By proceeding, you agree to %(site)'s <a %(terms)>Terms</a> and <a %(privacy)>Privacy Policy</a>."), { site: "Persona", terms: 'href="https://login.persona.org/tos" target="_new"', privacy: 'href="https://login.persona.org/privacy" target="_new"' }) %> diff --git a/resources/static/pages/js/manage_account.js b/resources/static/pages/js/manage_account.js index 119febf71a1c0f2fb6c8466b5a6b9fa704911452..e75f0610f0b1ed09b6020e76ebee51f02db15add 100644 --- a/resources/static/pages/js/manage_account.js +++ b/resources/static/pages/js/manage_account.js @@ -79,16 +79,18 @@ BrowserID.manageAccount = (function() { dom.setInner(list, ""); - // Set up to use mustache style templating, the normal Django style blows - // up the node templates - _.templateSettings = { - interpolate : /\{\{(.+?)\}\}/g - }; + function substitute(text, values, re) { + re = re || /\{\{([^\{\}]+)\}\}/g; + return String(text).replace(re, function(m, name) { + return (values[name] != null) ? values[name] : ''; + }); + } + var template = dom.getInner("#templateUser"); _(emails).each(function(item) { - var e = item.address, - identity = _.template(template, { email: e }); + var e = _.escape(item.address); + var identity = substitute(template, { email: e }); var idEl = dom.appendTo(identity, list), deleteButton = dom.getDescendentElements(".delete", idEl); @@ -211,8 +213,7 @@ BrowserID.manageAccount = (function() { var self=this, oncomplete = options.ready, - template = new EJS({ text: dom.getInner("#templateManage") }), - manage = template.render({}); + manage = dom.getInner("#templateManage"); dom.insertAfter(manage, "#hAlign"); diff --git a/resources/static/test/cases/common/js/renderer.js b/resources/static/test/cases/common/js/renderer.js index 2c3b8f116e19dfcd174b7a7d4e88bcf89dbac0bb..9932204f591187321024754c7407f42a546072dc 100644 --- a/resources/static/test/cases/common/js/renderer.js +++ b/resources/static/test/cases/common/js/renderer.js @@ -20,12 +20,6 @@ } }); - test("render template loaded using XHR", function() { - renderer.render("#formWrap .contents", "test_template_with_input"); - - ok($("#templateInput").length, "template written when loaded using XHR"); - }); - test("render template from memory", function() { renderer.render("#formWrap .contents", "inMemoryTemplate"); @@ -39,6 +33,14 @@ }); + test("render template with partial", function() { + equal($("#focusButton").length, 0, "template not yet loaded"); + + renderer.render("#formWrap .contents", "test_template_with_partial"); + + ok($("#focusButton").length, "template loaded with partial"); + }); + }()); diff --git a/resources/static/test/cases/pages/js/manage_account.js b/resources/static/test/cases/pages/js/manage_account.js index 42cbd863bda92036f04215201e4c32a16fd8f18b..e9e6cd9823ea689eb515ac3cb3e879c7b2788155 100644 --- a/resources/static/test/cases/pages/js/manage_account.js +++ b/resources/static/test/cases/pages/js/manage_account.js @@ -9,6 +9,7 @@ var bid = BrowserID, xhr = bid.Mocks.xhr, errorScreen = bid.Screens.error, + user = bid.User, network = bid.Network, storage = bid.Storage, testHelpers = bid.TestHelpers, @@ -82,12 +83,11 @@ xhr.useResult("multiple"); createController(mocks, function() { - equal($("#emailList").children().length, 2, "there two children added"); - - var firstLI = $("#testuser2_testuser_com"); - var secondLI = $("#testuser_testuser_com"); - - equal(firstLI.next().is(secondLI), true, "names are in alphabetical order"); + var sortedEmails = user.getSortedEmailKeypairs(); + _.each(sortedEmails, function(addressInfo, index) { + var displayedAddress = $("#emailList .email").get(index).innerHTML; + equal(displayedAddress, addressInfo.address, "emails are displayed in sorted order"); + }); start(); }); diff --git a/resources/static/test/mocks/templates.js b/resources/static/test/mocks/templates.js index b41be3c7874e2bca81ce352b7645b8926c2a3691..5cccd45ee28727b9274d383c84a4fe704b8a2843 100644 --- a/resources/static/test/mocks/templates.js +++ b/resources/static/test/mocks/templates.js @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ BrowserID.Templates = { - inMemoryTemplate: "<div id='templateInput'></div>" + inMemoryTemplate: function (locals, filters, escape) { + return '<div id="templateInput"></div>' + } }; - diff --git a/resources/views/index.ejs b/resources/views/index.ejs index 77c2b4af563974f63d7e8a87db4f56f03e9e4a4a..68c5cdcb37a290b933dcb1aab5553b8502f22aec 100644 --- a/resources/views/index.ejs +++ b/resources/views/index.ejs @@ -19,9 +19,14 @@ </div> </div> + <!-- + These "templates" below aren't available as EJS client-side, since all + EJS tags are processed when this view is served by the server. + --> + <script type="text/html" id="templateUser"> - <li class="identity cf" id="{{ email.replace('@', '_').replace('.', '_') }}"> - <div class="email">{{ email }}</div> + <li class="identity cf"> + <div class="email">{{email}}</div> <button class="delete"><%- gettext('remove') %></button> </li> </script> diff --git a/resources/views/test.ejs b/resources/views/test.ejs index 01012a8017554c028823666e2d74654f979677b8..c4662d1d1a44911fd5fce20e552abfb0a3eee59e 100644 --- a/resources/views/test.ejs +++ b/resources/views/test.ejs @@ -68,7 +68,6 @@ <script src="/include.js"></script> <script src="/common/js/lib/jquery-1.7.1.min.js"></script> <script src="/common/js/lib/underscore.js"></script> - <script src="/common/js/lib/ejs.js"></script> <script src="/common/js/lib/gobbledygook.js"></script> <script src="/common/js/javascript-extensions.js"></script> <script src="/common/js/lib/bidbundle.js"></script> @@ -84,11 +83,13 @@ <script src="mocks/mocks.js"></script> <script src="mocks/xhr.js"></script> <script src="mocks/templates.js"></script> + <script src="mocks/site-templates.js"></script> <script src="mocks/provisioning.js"></script> <script src="mocks/window.js"></script> <script src="mocks/winchan.js"></script> <script src="mocks/cachify.js"></script> + <script src="/common/js/templates.js"></script> <script src="/common/js/renderer.js"></script> <script src="/common/js/class.js"></script> <script src="/common/js/mediator.js"></script> diff --git a/scripts/create_templates.js b/scripts/create_templates.js index 831fd61ec37d831c8bdf17f663379d4350b31f78..6d91d995aac99e5ebe3aca1a1675cdac2c6863fb 100755 --- a/scripts/create_templates.js +++ b/scripts/create_templates.js @@ -6,42 +6,21 @@ const fs = require("fs"), -path = require('path'); +path = require('path'), +templates = require('../lib/templates'); +var existsSync = fs.existsSync || path.existsSync; var dir = process.env.TEMPLATE_DIR || process.cwd(); var output_dir = process.env.BUILD_DIR || dir; - -var templates = {}; +var outputFile = path.join(output_dir, "templates.js"); function generateTemplates() { - var fileNames = fs.readdirSync(dir) - - // is a regen even neccesary? - try { - var lastGen = fs.statSync(path.join(output_dir, "templates.js")).mtime; - for (var i = 0; i < fileNames.length; i++) { - if (lastGen < fs.statSync(path.join(dir, fileNames[i])).mtime) { - throw "newer"; - } - }; - // no rebuild needed - console.log("templates.js is up to date"); - return; - } catch (e) { - console.log("creating templates.js"); - } - - for(var index = 0, max = fileNames.length; index < max; index++) { - var fileName = fileNames[index]; - if(fileName.match(/\.ejs$/)) { - var templateName = fileName.replace(/\.ejs/, ''); - templates[templateName] = fs.readFileSync(dir + "/" + fileName, "utf8") - } + var lastGen = existsSync(outputFile) ? fs.statSync(outputFile).mtime : 0; + var templateData = templates.generate(dir, null, lastGen); + if (templateData) { + // no data most likely means we're already up-to-date + fs.writeFileSync(path.join(output_dir, "templates.js"), templateData, "utf8"); } - - var templateData = "BrowserID.Templates =" + JSON.stringify(templates) + ";"; - - fs.writeFileSync(output_dir + "/templates.js", templateData, "utf8"); }; // run or export the function diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 9af5bec429e8dd82748b8a95b986d203adc4a619..93e64ff06830bcb0b2cc4c842a62c9a764f03392 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -28,7 +28,6 @@ function copy(src, dest) { } copy('../node_modules/jwcrypto/bidbundle.js', '../resources/static/common/js/lib/bidbundle.js'); -relativeLink('../resources/views', '../resources/static/dialog/views/site'); // generate ephemeral keys diff --git a/tests/static-resource-test.js b/tests/static-resource-test.js index 8a91b133109db90b3c8a4414c105a5ddd234b66d..ad7fc916e3e81344616195c588ba63e478df30bf 100755 --- a/tests/static-resource-test.js +++ b/tests/static-resource-test.js @@ -55,9 +55,9 @@ suite.addBatch({ assert.equal(files[minFile][nonLocalizedIndex], res[minRes][nonLocalizedIndex]); }); - // Fragile - filename with :locale... - // When fixing this test case... console.log(res[Object.keys(res)[0]]); - var localeIndex = 9; + + // testing :locale has been replaced + var localeIndex = res[minRes].indexOf('/i18n/:locale/client.json'); assert.notEqual(files[minFile][localeIndex], res[minRes][localeIndex]); var counter = 0;