--- a/web/res/js/mustache.js Wed Dec 11 11:04:27 2019 +0100
+++ b/web/res/js/mustache.js Wed Dec 11 14:30:18 2019 +0100
@@ -12,7 +12,7 @@
define(['exports'], factory); // AMD
} else {
global.Mustache = {};
- factory(Mustache); // script, wsh, asp
+ factory(global.Mustache); // script, wsh, asp
}
}(this, function mustacheFactory (mustache) {
@@ -45,6 +45,19 @@
return obj != null && typeof obj === 'object' && (propName in obj);
}
+ /**
+ * Safe way of detecting whether or not the given thing is a primitive and
+ * whether it has the given property
+ */
+ function primitiveHasOwnProperty (primitive, propName) {
+ return (
+ primitive != null
+ && typeof primitive !== 'object'
+ && primitive.hasOwnProperty
+ && primitive.hasOwnProperty(propName)
+ );
+ }
+
// Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
// See https://github.com/janl/mustache.js/issues/189
var regExpTest = RegExp.prototype.test;
@@ -63,11 +76,13 @@
'>': '>',
'"': '"',
"'": ''',
- '/': '/'
+ '/': '/',
+ '`': '`',
+ '=': '='
};
function escapeHtml (string) {
- return String(string).replace(/[&<>"'\/]/g, function fromEntityMap (s) {
+ return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
return entityMap[s];
});
}
@@ -99,16 +114,22 @@
* Tokens that are the root node of a subtree contain two more elements: 1) an
* array of tokens in the subtree and 2) the index in the original template at
* which the closing tag for that section begins.
+ *
+ * Tokens for partials also contain two more elements: 1) a string value of
+ * indendation prior to that tag and 2) the index of that tag on that line -
+ * eg a value of 2 indicates the partial is the third tag on this line.
*/
function parseTemplate (template, tags) {
if (!template)
return [];
-
+ var lineHasNonSpace = false;
var sections = []; // Stack to hold section tokens
var tokens = []; // Buffer to hold the tokens
var spaces = []; // Indices of whitespace tokens on the current line
var hasTag = false; // Is there a {{tag}} on the current line?
var nonSpace = false; // Is there a non-space char on the current line?
+ var indentation = ''; // Tracks indentation for tags that use it
+ var tagIndex = 0; // Stores a count of number of tags encountered on a line
// Strips all whitespace tokens array for the current line
// if there was a {{#tag}} on it and otherwise only space.
@@ -154,16 +175,23 @@
if (isWhitespace(chr)) {
spaces.push(tokens.length);
+ indentation += chr;
} else {
nonSpace = true;
+ lineHasNonSpace = true;
+ indentation += ' ';
}
tokens.push([ 'text', chr, start, start + 1 ]);
start += 1;
// Check for whitespace on the current line.
- if (chr === '\n')
+ if (chr === '\n') {
stripSpace();
+ indentation = '';
+ tagIndex = 0;
+ lineHasNonSpace = false;
+ }
}
}
@@ -195,7 +223,12 @@
if (!scanner.scan(closingTagRe))
throw new Error('Unclosed tag at ' + scanner.pos);
- token = [ type, value, start, scanner.pos ];
+ if (type == '>') {
+ token = [ type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace ];
+ } else {
+ token = [ type, value, start, scanner.pos ];
+ }
+ tagIndex++;
tokens.push(token);
if (type === '#' || type === '^') {
@@ -217,6 +250,8 @@
}
}
+ stripSpace();
+
// Make sure there are no open sections when we're done.
openSection = sections.pop();
@@ -267,19 +302,19 @@
token = tokens[i];
switch (token[0]) {
- case '#':
- case '^':
- collector.push(token);
- sections.push(token);
- collector = token[4] = [];
- break;
- case '/':
- section = sections.pop();
- section[5] = token[2];
- collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;
- break;
- default:
- collector.push(token);
+ case '#':
+ case '^':
+ collector.push(token);
+ sections.push(token);
+ collector = token[4] = [];
+ break;
+ case '/':
+ section = sections.pop();
+ section[5] = token[2];
+ collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;
+ break;
+ default:
+ collector.push(token);
}
}
@@ -329,16 +364,16 @@
var index = this.tail.search(re), match;
switch (index) {
- case -1:
- match = this.tail;
- this.tail = '';
- break;
- case 0:
- match = '';
- break;
- default:
- match = this.tail.substring(0, index);
- this.tail = this.tail.substring(index);
+ case -1:
+ match = this.tail;
+ this.tail = '';
+ break;
+ case 0:
+ match = '';
+ break;
+ default:
+ match = this.tail.substring(0, index);
+ this.tail = this.tail.substring(index);
}
this.pos += match.length;
@@ -375,11 +410,11 @@
if (cache.hasOwnProperty(name)) {
value = cache[name];
} else {
- var context = this, names, index, lookupHit = false;
+ var context = this, intermediateValue, names, index, lookupHit = false;
while (context) {
if (name.indexOf('.') > 0) {
- value = context.view;
+ intermediateValue = context.view;
names = name.split('.');
index = 0;
@@ -393,20 +428,51 @@
*
* This is specially necessary for when the value has been set to
* `undefined` and we want to avoid looking up parent contexts.
+ *
+ * In the case where dot notation is used, we consider the lookup
+ * to be successful even if the last "object" in the path is
+ * not actually an object but a primitive (e.g., a string, or an
+ * integer), because it is sometimes useful to access a property
+ * of an autoboxed primitive, such as the length of a string.
**/
- while (value != null && index < names.length) {
+ while (intermediateValue != null && index < names.length) {
if (index === names.length - 1)
- lookupHit = hasProperty(value, names[index]);
+ lookupHit = (
+ hasProperty(intermediateValue, names[index])
+ || primitiveHasOwnProperty(intermediateValue, names[index])
+ );
- value = value[names[index++]];
+ intermediateValue = intermediateValue[names[index++]];
}
} else {
- value = context.view[name];
+ intermediateValue = context.view[name];
+
+ /**
+ * Only checking against `hasProperty`, which always returns `false` if
+ * `context.view` is not an object. Deliberately omitting the check
+ * against `primitiveHasOwnProperty` if dot notation is not used.
+ *
+ * Consider this example:
+ * ```
+ * Mustache.render("The length of a football field is {{#length}}{{length}}{{/length}}.", {length: "100 yards"})
+ * ```
+ *
+ * If we were to check also against `primitiveHasOwnProperty`, as we do
+ * in the dot notation case, then render call would return:
+ *
+ * "The length of a football field is 9."
+ *
+ * rather than the expected:
+ *
+ * "The length of a football field is 100 yards."
+ **/
lookupHit = hasProperty(context.view, name);
}
- if (lookupHit)
+ if (lookupHit) {
+ value = intermediateValue;
break;
+ }
context = context.parent;
}
@@ -437,15 +503,17 @@
};
/**
- * Parses and caches the given `template` and returns the array of tokens
+ * Parses and caches the given `template` according to the given `tags` or
+ * `mustache.tags` if `tags` is omitted, and returns the array of tokens
* that is generated from the parse.
*/
Writer.prototype.parse = function parse (template, tags) {
var cache = this.cache;
- var tokens = cache[template];
+ var cacheKey = template + ':' + (tags || mustache.tags).join(':');
+ var tokens = cache[cacheKey];
if (tokens == null)
- tokens = cache[template] = parseTemplate(template, tags);
+ tokens = cache[cacheKey] = parseTemplate(template, tags);
return tokens;
};
@@ -458,11 +526,15 @@
* names and templates of partials that are used in the template. It may
* also be a function that is used to load partial templates on the fly
* that takes a single argument: the name of the partial.
+ *
+ * If the optional `tags` argument is given here it must be an array with two
+ * string values: the opening and closing tags used in the template (e.g.
+ * [ "<%", "%>" ]). The default is to mustache.tags.
*/
- Writer.prototype.render = function render (template, view, partials) {
- var tokens = this.parse(template);
+ Writer.prototype.render = function render (template, view, partials, tags) {
+ var tokens = this.parse(template, tags);
var context = (view instanceof Context) ? view : new Context(view);
- return this.renderTokens(tokens, context, partials, template);
+ return this.renderTokens(tokens, context, partials, template, tags);
};
/**
@@ -474,7 +546,7 @@
* If the template doesn't use higher-order sections, this argument may
* be omitted.
*/
- Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate) {
+ Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate, tags) {
var buffer = '';
var token, symbol, value;
@@ -485,7 +557,7 @@
if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate);
else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate);
- else if (symbol === '>') value = this.renderPartial(token, context, partials, originalTemplate);
+ else if (symbol === '>') value = this.renderPartial(token, context, partials, tags);
else if (symbol === '&') value = this.unescapedValue(token, context);
else if (symbol === 'name') value = this.escapedValue(token, context);
else if (symbol === 'text') value = this.rawValue(token);
@@ -540,12 +612,31 @@
return this.renderTokens(token[4], context, partials, originalTemplate);
};
- Writer.prototype.renderPartial = function renderPartial (token, context, partials) {
+ Writer.prototype.indentPartial = function indentPartial (partial, indentation, lineHasNonSpace) {
+ var filteredIndentation = indentation.replace(/[^ \t]/g, '');
+ var partialByNl = partial.split('\n');
+ for (var i = 0; i < partialByNl.length; i++) {
+ if (partialByNl[i].length && (i > 0 || !lineHasNonSpace)) {
+ partialByNl[i] = filteredIndentation + partialByNl[i];
+ }
+ }
+ return partialByNl.join('\n');
+ };
+
+ Writer.prototype.renderPartial = function renderPartial (token, context, partials, tags) {
if (!partials) return;
var value = isFunction(partials) ? partials(token[1]) : partials[token[1]];
- if (value != null)
- return this.renderTokens(this.parse(value), context, partials, value);
+ if (value != null) {
+ var lineHasNonSpace = token[6];
+ var tagIndex = token[5];
+ var indentation = token[4];
+ var indentedValue = value;
+ if (tagIndex == 0 && indentation) {
+ indentedValue = this.indentPartial(value, indentation, lineHasNonSpace);
+ }
+ return this.renderTokens(this.parse(indentedValue, tags), context, partials, indentedValue);
+ }
};
Writer.prototype.unescapedValue = function unescapedValue (token, context) {
@@ -565,7 +656,7 @@
};
mustache.name = 'mustache.js';
- mustache.version = '2.1.3';
+ mustache.version = '3.1.0';
mustache.tags = [ '{{', '}}' ];
// All high-level mustache.* functions use this writer.
@@ -589,16 +680,18 @@
/**
* Renders the `template` with the given `view` and `partials` using the
- * default writer.
+ * default writer. If the optional `tags` argument is given here it must be an
+ * array with two string values: the opening and closing tags used in the
+ * template (e.g. [ "<%", "%>" ]). The default is to mustache.tags.
*/
- mustache.render = function render (template, view, partials) {
+ mustache.render = function render (template, view, partials, tags) {
if (typeof template !== 'string') {
throw new TypeError('Invalid template! Template should be a "string" ' +
'but "' + typeStr(template) + '" was given as the first ' +
'argument for mustache#render(template, view, partials)');
}
- return defaultWriter.render(template, view, partials);
+ return defaultWriter.render(template, view, partials, tags);
};
// This is here for backwards compatibility with 0.4.x.,
@@ -624,4 +717,5 @@
mustache.Context = Context;
mustache.Writer = Writer;
+ return mustache;
}));