mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-08 02:52:39 -05:00
✨ Helper Proxy & single express-hbs instance (#8225)
refs #8126, #8221, #8223 ✨ New 'Proxy' for all helper requires - this is not currently enforced, but could be, much like apps - the proxy object is HUGE - changed date to use SafeString, this should have been there anyway - use the proxy for all helpers, including those in apps 😁 ✨ 🎨 Single instance of hbs for theme + for errors - we now have theme/engine instead of requiring express-hbs everywhere - only error-handler still also requires express-hbs, this is so that we can render errors without extra crud - TODO: remove the asset helper after #8126 IF it is not needed, or else remove the TODO 🎨 Cleanup visibility utils 🎨 Clean up the proxy a little bit 🚨 Unskip test as it now works! 🎨 Minor amends as per comments
This commit is contained in:
parent
d4836af18a
commit
243b387063
57 changed files with 467 additions and 413 deletions
|
@ -7,7 +7,9 @@
|
|||
// Here's the list of all supported extended components: https://www.ampproject.org/docs/reference/extended.html
|
||||
// By default supported AMP HTML tags (no additional script tag necessary):
|
||||
// amp-img, amp-ad, amp-embed, amp-video and amp-pixel.
|
||||
var hbs = require('express-hbs');
|
||||
// (less) dirty requires
|
||||
var proxy = require('../../../../helpers/proxy'),
|
||||
SafeString = proxy.SafeString;
|
||||
|
||||
function ampComponents() {
|
||||
var components = [],
|
||||
|
@ -29,7 +31,7 @@ function ampComponents() {
|
|||
components.push('<script async custom-element="amp-audio" src="https://cdn.ampproject.org/v0/amp-audio-0.1.js"></script>');
|
||||
}
|
||||
|
||||
return new hbs.handlebars.SafeString(components.join('\n'));
|
||||
return new SafeString(components.join('\n'));
|
||||
}
|
||||
|
||||
module.exports = ampComponents;
|
||||
|
|
|
@ -6,16 +6,19 @@
|
|||
//
|
||||
// Converts normal HTML into AMP HTML with Amperize module and uses a cache to return it from
|
||||
// there if available. The cacheId is a combination of `updated_at` and the `slug`.
|
||||
var hbs = require('express-hbs'),
|
||||
Promise = require('bluebird'),
|
||||
moment = require('moment'),
|
||||
logging = require('../../../../logging'),
|
||||
i18n = require('../../../../i18n'),
|
||||
errors = require('../../../../errors'),
|
||||
makeAbsoluteUrl = require('../../../../utils/make-absolute-urls'),
|
||||
utils = require('../../../../utils'),
|
||||
amperizeCache = {},
|
||||
allowedAMPTags = [],
|
||||
var Promise = require('bluebird'),
|
||||
moment = require('moment'),
|
||||
|
||||
// (less) dirty requires
|
||||
proxy = require('../../../../helpers/proxy'),
|
||||
SafeString = proxy.SafeString,
|
||||
logging = proxy.logging,
|
||||
i18n = proxy.i18n,
|
||||
errors = proxy.errors,
|
||||
makeAbsoluteUrl = require('../../../../utils/make-absolute-urls'),
|
||||
utils = require('../../../../utils'),
|
||||
amperizeCache = {},
|
||||
allowedAMPTags = [],
|
||||
allowedAMPAttributes = {},
|
||||
amperize,
|
||||
cleanHTML,
|
||||
|
@ -190,7 +193,7 @@ function ampContent() {
|
|||
selfClosing: ['source', 'track']
|
||||
});
|
||||
|
||||
return new hbs.handlebars.SafeString(cleanHTML);
|
||||
return new SafeString(cleanHTML);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
// We use the name input_password to match the helper for consistency:
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
|
||||
// Dirty requires
|
||||
var hbs = require('express-hbs'),
|
||||
utils = require('../../../../helpers/utils'),
|
||||
input_password;
|
||||
// (less) dirty requires
|
||||
var proxy = require('../../../../helpers/proxy'),
|
||||
SafeString = proxy.SafeString,
|
||||
templates = proxy.templates;
|
||||
|
||||
input_password = function (options) {
|
||||
module.exports = function input_password(options) {
|
||||
options = options || {};
|
||||
options.hash = options.hash || {};
|
||||
|
||||
|
@ -23,14 +23,12 @@ input_password = function (options) {
|
|||
extras += ' placeholder="' + options.hash.placeholder + '"';
|
||||
}
|
||||
|
||||
output = utils.inputTemplate({
|
||||
output = templates.input({
|
||||
type: 'password',
|
||||
name: 'password',
|
||||
className: className,
|
||||
extras: extras
|
||||
});
|
||||
|
||||
return new hbs.handlebars.SafeString(output);
|
||||
return new SafeString(output);
|
||||
};
|
||||
|
||||
module.exports = input_password;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
var _ = require('lodash'),
|
||||
api = require('../api'),
|
||||
helpers = require('../helpers'),
|
||||
helpers = require('../helpers/register'),
|
||||
filters = require('../filters'),
|
||||
i18n = require('../i18n'),
|
||||
generateProxyFunctions;
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
// We use the name input_email to match the helper for consistency:
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
|
||||
// Dirty requires
|
||||
var hbs = require('express-hbs'),
|
||||
utils = require('../../../../helpers/utils'),
|
||||
input_email;
|
||||
// (less) dirty requires
|
||||
var proxy = require('../../../../helpers/proxy'),
|
||||
SafeString = proxy.SafeString,
|
||||
templates = proxy.templates;
|
||||
|
||||
input_email = function (options) {
|
||||
module.exports = function input_email(options) {
|
||||
options = options || {};
|
||||
options.hash = options.hash || {};
|
||||
|
||||
|
@ -31,14 +31,13 @@ input_email = function (options) {
|
|||
extras += ' value="' + options.hash.value + '"';
|
||||
}
|
||||
|
||||
output = utils.inputTemplate({
|
||||
output = templates.input({
|
||||
type: 'email',
|
||||
name: 'email',
|
||||
className: className,
|
||||
extras: extras
|
||||
});
|
||||
|
||||
return new hbs.handlebars.SafeString(output);
|
||||
return new SafeString(output);
|
||||
};
|
||||
|
||||
module.exports = input_email;
|
||||
|
|
|
@ -5,20 +5,19 @@
|
|||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
var _ = require('lodash'),
|
||||
|
||||
// Dirty requires
|
||||
hbs = require('express-hbs'),
|
||||
config = require('../../../../config'),
|
||||
template = require('../../../../helpers/template'),
|
||||
utils = require('../../../../helpers/utils'),
|
||||
globalUtils = require('../../../../utils'),
|
||||
// (Less) dirty requires
|
||||
proxy = require('../../../../helpers/proxy'),
|
||||
templates = proxy.templates,
|
||||
config = proxy.config,
|
||||
url = proxy.url,
|
||||
SafeString = proxy.SafeString,
|
||||
|
||||
params = ['error', 'success', 'email'],
|
||||
|
||||
subscribe_form,
|
||||
subscribeScript;
|
||||
|
||||
function makeHidden(name, extras) {
|
||||
return utils.inputTemplate({
|
||||
return templates.input({
|
||||
type: 'hidden',
|
||||
name: name,
|
||||
className: name,
|
||||
|
@ -39,19 +38,17 @@ subscribeScript =
|
|||
'})(window,document,\'querySelector\',\'value\');' +
|
||||
'</script>';
|
||||
|
||||
subscribe_form = function (options) {
|
||||
module.exports = function subscribe_form(options) {
|
||||
var root = options.data.root,
|
||||
data = _.merge({}, options.hash, _.pick(root, params), {
|
||||
action: globalUtils.url.urlJoin('/', globalUtils.url.getSubdir(), config.get('routeKeywords').subscribe, '/'),
|
||||
script: new hbs.handlebars.SafeString(subscribeScript),
|
||||
hidden: new hbs.handlebars.SafeString(
|
||||
action: url.urlJoin('/', url.getSubdir(), config.get('routeKeywords').subscribe, '/'),
|
||||
script: new SafeString(subscribeScript),
|
||||
hidden: new SafeString(
|
||||
makeHidden('confirm') +
|
||||
makeHidden('location', root.subscribed_url ? 'value=' + root.subscribed_url : '') +
|
||||
makeHidden('referrer', root.subscribed_referrer ? 'value=' + root.subscribed_referrer : '')
|
||||
)
|
||||
});
|
||||
|
||||
return template.execute('subscribe_form', data, options);
|
||||
return templates.execute('subscribe_form', data, options);
|
||||
};
|
||||
|
||||
module.exports = subscribe_form;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var visibilityFilter = require('../../utils/visibility-filter');
|
||||
var visibilityFilter = require('../../utils/visibility').filter;
|
||||
|
||||
function getKeywords(data) {
|
||||
if (data.post && data.post.tags && data.post.tags.length > 0) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
var config = require('../../config'),
|
||||
hbs = require('express-hbs'),
|
||||
escapeExpression = require('../../themes/engine').escapeExpression,
|
||||
socialUrls = require('../../utils/social-urls'),
|
||||
escapeExpression = hbs.handlebars.Utils.escapeExpression,
|
||||
_ = require('lodash');
|
||||
|
||||
function schemaImageObject(metaDataVal) {
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
// Usage: `{{asset "css/screen.css"}}`, `{{asset "css/screen.css" ghost="true"}}`
|
||||
//
|
||||
// Returns the path to the specified asset. The ghost flag outputs the asset path for the Ghost admin
|
||||
var proxy = require('./proxy'),
|
||||
config = proxy.config,
|
||||
getAssetUrl = proxy.metaData.getAssetUrl,
|
||||
SafeString = proxy.SafeString;
|
||||
|
||||
var config = require('../config'),
|
||||
getAssetUrl = require('../data/meta/asset_url'),
|
||||
hbs = require('express-hbs');
|
||||
|
||||
function asset(path, options) {
|
||||
module.exports = function asset(path, options) {
|
||||
var isAdmin,
|
||||
minify;
|
||||
|
||||
|
@ -20,9 +20,7 @@ function asset(path, options) {
|
|||
minify = false;
|
||||
}
|
||||
|
||||
return new hbs.handlebars.SafeString(
|
||||
return new SafeString(
|
||||
getAssetUrl(path, isAdmin, minify)
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = asset;
|
||||
};
|
||||
|
|
|
@ -10,15 +10,16 @@
|
|||
// Block helper: `{{#author}}{{/author}}`
|
||||
// This is the default handlebars behaviour of dropping into the author object scope
|
||||
|
||||
var hbs = require('express-hbs'),
|
||||
_ = require('lodash'),
|
||||
utils = require('../utils'),
|
||||
localUtils = require('./utils'),
|
||||
author;
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
SafeString = proxy.SafeString,
|
||||
handlebars = proxy.hbs.handlebars,
|
||||
templates = proxy.templates,
|
||||
url = proxy.url;
|
||||
|
||||
author = function (options) {
|
||||
module.exports = function author(options) {
|
||||
if (options.fn) {
|
||||
return hbs.handlebars.helpers.with.call(this, this.author, options);
|
||||
return handlebars.helpers.with.call(this, this.author, options);
|
||||
}
|
||||
|
||||
var autolink = _.isString(options.hash.autolink) && options.hash.autolink === 'false' ? false : true,
|
||||
|
@ -26,8 +27,8 @@ author = function (options) {
|
|||
|
||||
if (this.author && this.author.name) {
|
||||
if (autolink) {
|
||||
output = localUtils.linkTemplate({
|
||||
url: utils.url.urlFor('author', {author: this.author}),
|
||||
output = templates.link({
|
||||
url: url.urlFor('author', {author: this.author}),
|
||||
text: _.escape(this.author.name)
|
||||
});
|
||||
} else {
|
||||
|
@ -35,7 +36,5 @@ author = function (options) {
|
|||
}
|
||||
}
|
||||
|
||||
return new hbs.handlebars.SafeString(output);
|
||||
return new SafeString(output);
|
||||
};
|
||||
|
||||
module.exports = author;
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
// We use the name body_class to match the helper for consistency:
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
|
||||
var hbs = require('express-hbs'),
|
||||
_ = require('lodash'),
|
||||
body_class;
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
SafeString = proxy.SafeString;
|
||||
|
||||
body_class = function (options) {
|
||||
module.exports = function body_class(options) {
|
||||
var classes = [],
|
||||
context = options.data.root.context,
|
||||
post = this.post,
|
||||
|
@ -43,7 +43,6 @@ body_class = function (options) {
|
|||
}
|
||||
|
||||
classes = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, '');
|
||||
return new hbs.handlebars.SafeString(classes.trim());
|
||||
return new SafeString(classes.trim());
|
||||
};
|
||||
|
||||
module.exports = body_class;
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
//
|
||||
// Enables tag-safe truncation of content by characters or words.
|
||||
|
||||
var hbs = require('express-hbs'),
|
||||
_ = require('lodash'),
|
||||
downsize = require('downsize'),
|
||||
content;
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
downsize = require('downsize'),
|
||||
SafeString = proxy.SafeString;
|
||||
|
||||
content = function (options) {
|
||||
module.exports = function content(options) {
|
||||
var truncateOptions = (options || {}).hash || {};
|
||||
truncateOptions = _.pick(truncateOptions, ['words', 'characters']);
|
||||
_.keys(truncateOptions).map(function (key) {
|
||||
|
@ -19,12 +19,10 @@ content = function (options) {
|
|||
});
|
||||
|
||||
if (truncateOptions.hasOwnProperty('words') || truncateOptions.hasOwnProperty('characters')) {
|
||||
return new hbs.handlebars.SafeString(
|
||||
return new SafeString(
|
||||
downsize(this.html, truncateOptions)
|
||||
);
|
||||
}
|
||||
|
||||
return new hbs.handlebars.SafeString(this.html);
|
||||
return new SafeString(this.html);
|
||||
};
|
||||
|
||||
module.exports = content;
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
//
|
||||
// Formats a date using moment-timezone.js. Formats published_at by default but will also take a date as a parameter
|
||||
|
||||
var moment = require('moment-timezone'),
|
||||
date,
|
||||
timezone;
|
||||
var proxy = require('./proxy'),
|
||||
moment = require('moment-timezone'),
|
||||
SafeString = proxy.SafeString;
|
||||
|
||||
module.exports = function (date, options) {
|
||||
var timezone, format, timeago, timeNow;
|
||||
|
||||
date = function (date, options) {
|
||||
if (!options && date.hasOwnProperty('hash')) {
|
||||
options = date;
|
||||
date = undefined;
|
||||
|
@ -23,17 +25,15 @@ date = function (date, options) {
|
|||
// ensure that context is undefined, not null, as that can cause errors
|
||||
date = date === null ? undefined : date;
|
||||
|
||||
var f = options.hash.format || 'MMM DD, YYYY',
|
||||
timeago = options.hash.timeago,
|
||||
timeNow = moment().tz(timezone);
|
||||
format = options.hash.format || 'MMM DD, YYYY';
|
||||
timeago = options.hash.timeago;
|
||||
timeNow = moment().tz(timezone);
|
||||
|
||||
if (timeago) {
|
||||
date = timezone ? moment(date).tz(timezone).from(timeNow) : moment(date).fromNow();
|
||||
date = timezone ? moment(date).tz(timezone).from(timeNow) : moment(date).fromNow();
|
||||
} else {
|
||||
date = timezone ? moment(date).tz(timezone).format(f) : moment(date).format(f);
|
||||
date = timezone ? moment(date).tz(timezone).format(format) : moment(date).format(format);
|
||||
}
|
||||
|
||||
return date;
|
||||
return new SafeString(date);
|
||||
};
|
||||
|
||||
module.exports = date;
|
||||
|
|
|
@ -4,12 +4,10 @@
|
|||
//
|
||||
// Returns URI encoded string
|
||||
|
||||
var hbs = require('express-hbs'),
|
||||
encode;
|
||||
var proxy = require('./proxy'),
|
||||
SafeString = proxy.SafeString;
|
||||
|
||||
encode = function (string, options) {
|
||||
module.exports = function encode(string, options) {
|
||||
var uri = string || options;
|
||||
return new hbs.handlebars.SafeString(encodeURIComponent(uri));
|
||||
return new SafeString(encodeURIComponent(uri));
|
||||
};
|
||||
|
||||
module.exports = encode;
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
//
|
||||
// Defaults to words="50"
|
||||
|
||||
var hbs = require('express-hbs'),
|
||||
_ = require('lodash'),
|
||||
getMetaDataExcerpt = require('../data/meta/excerpt');
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
SafeString = proxy.SafeString,
|
||||
getMetaDataExcerpt = proxy.metaData.getMetaDataExcerpt;
|
||||
|
||||
function excerpt(options) {
|
||||
module.exports = function excerpt(options) {
|
||||
var truncateOptions = (options || {}).hash || {};
|
||||
|
||||
truncateOptions = _.pick(truncateOptions, ['words', 'characters']);
|
||||
|
@ -17,9 +18,7 @@ function excerpt(options) {
|
|||
truncateOptions[key] = parseInt(truncateOptions[key], 10);
|
||||
});
|
||||
|
||||
return new hbs.handlebars.SafeString(
|
||||
return new SafeString(
|
||||
getMetaDataExcerpt(String(this.html), truncateOptions)
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = excerpt;
|
||||
};
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
// We use the name facebook_url to match the helper for consistency:
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
|
||||
var socialUrls = require('../utils/social-urls'),
|
||||
findKey = require('./utils').findKey,
|
||||
facebook_url;
|
||||
var proxy = require('./proxy'),
|
||||
socialUrls = proxy.socialUrls,
|
||||
findKey = proxy.utils.findKey;
|
||||
|
||||
facebook_url = function (username, options) {
|
||||
module.exports = function facebook_url(username, options) {
|
||||
if (!options) {
|
||||
options = username;
|
||||
username = findKey('facebook', this, options.data.blog);
|
||||
|
@ -22,5 +22,3 @@ facebook_url = function (username, options) {
|
|||
|
||||
return null;
|
||||
};
|
||||
|
||||
module.exports = facebook_url;
|
||||
|
|
|
@ -2,23 +2,21 @@
|
|||
// Usage: `{{#foreach data}}{{/foreach}}`
|
||||
//
|
||||
// Block helper designed for looping through posts
|
||||
var hbs = require('express-hbs'),
|
||||
_ = require('lodash'),
|
||||
logging = require('../logging'),
|
||||
i18n = require('../i18n'),
|
||||
visibilityFilter = require('../utils/visibility-filter'),
|
||||
utils = require('./utils'),
|
||||
|
||||
hbsUtils = hbs.handlebars.Utils,
|
||||
foreach;
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
logging = proxy.logging,
|
||||
i18n = proxy.i18n,
|
||||
visibilityUtils = proxy.visibility,
|
||||
hbsUtils = proxy.hbs.Utils,
|
||||
createFrame = proxy.hbs.handlebars.createFrame;
|
||||
|
||||
function filterItemsByVisibility(items, options) {
|
||||
var visibility = utils.parseVisibility(options);
|
||||
var visibility = visibilityUtils.parser(options);
|
||||
|
||||
return visibilityFilter(items, visibility, !!options.hash.visibility);
|
||||
return visibilityUtils.filter(items, visibility, !!options.hash.visibility);
|
||||
}
|
||||
|
||||
foreach = function (items, options) {
|
||||
module.exports = function foreach(items, options) {
|
||||
if (!options) {
|
||||
logging.warn(i18n.t('warnings.helpers.foreach.iteratorNeeded'));
|
||||
}
|
||||
|
@ -53,7 +51,7 @@ foreach = function (items, options) {
|
|||
}
|
||||
|
||||
if (options.data) {
|
||||
data = hbs.handlebars.createFrame(options.data);
|
||||
data = createFrame(options.data);
|
||||
}
|
||||
|
||||
function execIteration(field, index, last) {
|
||||
|
@ -73,9 +71,9 @@ foreach = function (items, options) {
|
|||
}
|
||||
|
||||
output = output + fn(items[field], {
|
||||
data: data,
|
||||
blockParams: hbsUtils.blockParams([items[field], field], [contextPath + field, null])
|
||||
});
|
||||
data: data,
|
||||
blockParams: hbsUtils.blockParams([items[field], field], [contextPath + field, null])
|
||||
});
|
||||
}
|
||||
|
||||
function iterateCollection(context) {
|
||||
|
@ -109,5 +107,3 @@ foreach = function (items, options) {
|
|||
|
||||
return output;
|
||||
};
|
||||
|
||||
module.exports = foreach;
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
// # Get Helper
|
||||
// Usage: `{{#get "posts" limit="5"}}`, `{{#get "tags" limit="all"}}`
|
||||
// Fetches data from the API
|
||||
var _ = require('lodash'),
|
||||
hbs = require('express-hbs'),
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
jsonpath = require('jsonpath'),
|
||||
|
||||
logging = require('../logging'),
|
||||
i18n = require('../i18n'),
|
||||
api = require('../api'),
|
||||
labs = require('../utils/labs'),
|
||||
logging = proxy.logging,
|
||||
i18n = proxy.i18n,
|
||||
createFrame = proxy.hbs.handlebars.createFrame,
|
||||
|
||||
api = proxy.api,
|
||||
labs = proxy.labs,
|
||||
resources,
|
||||
pathAliases,
|
||||
get;
|
||||
|
@ -96,7 +98,7 @@ get = function get(resource, options) {
|
|||
options.data = options.data || {};
|
||||
|
||||
var self = this,
|
||||
data = hbs.handlebars.createFrame(options.data),
|
||||
data = createFrame(options.data),
|
||||
apiOptions = options.hash,
|
||||
apiMethod;
|
||||
|
||||
|
|
|
@ -5,14 +5,13 @@
|
|||
//
|
||||
// We use the name ghost_foot to match the helper for consistency:
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
var hbs = require('express-hbs'),
|
||||
SafeString = hbs.handlebars.SafeString,
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
filters = require('../filters'),
|
||||
settingsCache = require('../settings/cache'),
|
||||
ghost_foot;
|
||||
SafeString = proxy.SafeString,
|
||||
filters = proxy.filters,
|
||||
settingsCache = proxy.settingsCache;
|
||||
|
||||
ghost_foot = function ghost_foot() {
|
||||
module.exports = function ghost_foot() {
|
||||
var foot = [],
|
||||
codeInjection = settingsCache.get('ghost_foot');
|
||||
|
||||
|
@ -26,5 +25,3 @@ ghost_foot = function ghost_foot() {
|
|||
return new SafeString(foot.join(' ').trim());
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = ghost_foot;
|
||||
|
|
|
@ -6,19 +6,19 @@
|
|||
// We use the name ghost_head to match the helper for consistency:
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
|
||||
var getMetaData = require('../data/meta'),
|
||||
hbs = require('express-hbs'),
|
||||
escapeExpression = hbs.handlebars.Utils.escapeExpression,
|
||||
SafeString = hbs.handlebars.SafeString,
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
filters = require('../filters'),
|
||||
assetHelper = require('./asset'),
|
||||
config = require('../config'),
|
||||
Promise = require('bluebird'),
|
||||
labs = require('../utils/labs'),
|
||||
utils = require('../utils'),
|
||||
api = require('../api'),
|
||||
settingsCache = require('../settings/cache');
|
||||
|
||||
getMetaData = proxy.metaData.get,
|
||||
escapeExpression = proxy.escapeExpression,
|
||||
SafeString = proxy.SafeString,
|
||||
filters = proxy.filters,
|
||||
labs = proxy.labs,
|
||||
api = proxy.api,
|
||||
settingsCache = proxy.settingsCache,
|
||||
config = proxy.config,
|
||||
url = proxy.url;
|
||||
|
||||
function getClient() {
|
||||
if (labs.isSet('publicAPI') === true) {
|
||||
|
@ -66,6 +66,10 @@ function finaliseStructuredData(metaData) {
|
|||
}
|
||||
|
||||
function getAjaxHelper(clientId, clientSecret) {
|
||||
// @TODO: swap this for a direct utility, rather than using the helper? see #8221
|
||||
// Note: this is here because the asset helper isn't registered when this file is first loaded
|
||||
var assetHelper = proxy.hbs.handlebars.helpers.asset;
|
||||
|
||||
return '<script type="text/javascript" src="' +
|
||||
assetHelper('shared/ghost-url.js', {hash: {minifyInProduction: true}}) + '"></script>\n' +
|
||||
'<script type="text/javascript">\n' +
|
||||
|
@ -76,7 +80,7 @@ function getAjaxHelper(clientId, clientSecret) {
|
|||
'</script>';
|
||||
}
|
||||
|
||||
function ghost_head(options) {
|
||||
module.exports = function ghost_head(options) {
|
||||
// if server error page do nothing
|
||||
if (this.statusCode >= 500) {
|
||||
return;
|
||||
|
@ -97,7 +101,7 @@ function ghost_head(options) {
|
|||
blogIcon = settingsCache.get('icon'),
|
||||
// CASE: blog icon is not set in config, we serve the default
|
||||
iconType = !blogIcon ? 'x-icon' : blogIcon.match(/\/favicon\.ico$/i) ? 'x-icon' : 'png',
|
||||
favicon = !blogIcon ? '/favicon.ico' : utils.url.urlFor('image', {image: blogIcon});
|
||||
favicon = !blogIcon ? '/favicon.ico' : url.urlFor('image', {image: blogIcon});
|
||||
|
||||
return Promise.props(fetch).then(function (response) {
|
||||
client = response.client;
|
||||
|
@ -162,6 +166,4 @@ function ghost_head(options) {
|
|||
}).then(function (head) {
|
||||
return new SafeString(head.join('\n ').trim());
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = ghost_head;
|
||||
};
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
//
|
||||
// Checks if a post has a particular property
|
||||
|
||||
var _ = require('lodash'),
|
||||
logging = require('../logging'),
|
||||
i18n = require('../i18n'),
|
||||
has;
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
logging = proxy.logging,
|
||||
i18n = proxy.i18n;
|
||||
|
||||
has = function (options) {
|
||||
module.exports = function has(options) {
|
||||
options = options || {};
|
||||
options.hash = options.hash || {};
|
||||
|
||||
|
@ -33,7 +33,7 @@ has = function (options) {
|
|||
}
|
||||
|
||||
function evaluateAuthorList(expr, author) {
|
||||
var authorList = expr.split(',').map(function (v) {
|
||||
var authorList = expr.split(',').map(function (v) {
|
||||
return v.trim().toLocaleLowerCase();
|
||||
});
|
||||
|
||||
|
@ -53,5 +53,3 @@ has = function (options) {
|
|||
}
|
||||
return options.inverse(this);
|
||||
};
|
||||
|
||||
module.exports = has;
|
||||
|
|
|
@ -4,15 +4,13 @@
|
|||
// Returns the URL for the current object scope i.e. If inside a post scope will return image permalink
|
||||
// `absolute` flag outputs absolute URL, else URL is relative.
|
||||
|
||||
var utils = require('../utils'),
|
||||
image;
|
||||
var proxy = require('./proxy'),
|
||||
url = proxy.url;
|
||||
|
||||
image = function (options) {
|
||||
module.exports = function image(options) {
|
||||
var absolute = options && options.hash.absolute;
|
||||
|
||||
if (this.image) {
|
||||
return utils.url.urlFor('image', {image: this.image}, absolute);
|
||||
return url.urlFor('image', {image: this.image}, absolute);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = image;
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
var hbs = require('express-hbs'),
|
||||
Promise = require('bluebird'),
|
||||
errors = require('../errors'),
|
||||
config = require('../config'),
|
||||
coreHelpers = {},
|
||||
registerHelpers;
|
||||
|
||||
// @TODO think about a config option for this e.g. theme.devmode?
|
||||
if (config.get('env') !== 'production') {
|
||||
hbs.handlebars.logger.level = 0;
|
||||
}
|
||||
var coreHelpers = {},
|
||||
register = require('./register'),
|
||||
registerThemeHelper = register.registerThemeHelper,
|
||||
registerAsyncThemeHelper = register.registerAsyncThemeHelper,
|
||||
registerAllCoreHelpers;
|
||||
|
||||
coreHelpers.asset = require('./asset');
|
||||
coreHelpers.author = require('./author');
|
||||
|
@ -39,38 +33,7 @@ coreHelpers.title = require('./title');
|
|||
coreHelpers.twitter_url = require('./twitter_url');
|
||||
coreHelpers.url = require('./url');
|
||||
|
||||
// Register an async handlebars helper for a given handlebars instance
|
||||
function registerAsyncHelper(hbs, name, fn) {
|
||||
hbs.registerAsyncHelper(name, function (context, options, cb) {
|
||||
// Handle the case where we only get context and cb
|
||||
if (!cb) {
|
||||
cb = options;
|
||||
options = undefined;
|
||||
}
|
||||
|
||||
// Wrap the function passed in with a when.resolve so it can return either a promise or a value
|
||||
Promise.resolve(fn.call(this, context, options)).then(function (result) {
|
||||
cb(result);
|
||||
}).catch(function (err) {
|
||||
throw new errors.IncorrectUsageError({
|
||||
err: err,
|
||||
context: 'registerAsyncThemeHelper: ' + name
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Register a handlebars helper for themes
|
||||
function registerThemeHelper(name, fn) {
|
||||
hbs.registerHelper(name, fn);
|
||||
}
|
||||
|
||||
// Register an async handlebars helper for themes
|
||||
function registerAsyncThemeHelper(name, fn) {
|
||||
registerAsyncHelper(hbs, name, fn);
|
||||
}
|
||||
|
||||
registerHelpers = function () {
|
||||
registerAllCoreHelpers = function registerAllCoreHelpers() {
|
||||
// Register theme helpers
|
||||
registerThemeHelper('asset', coreHelpers.asset);
|
||||
registerThemeHelper('author', coreHelpers.author);
|
||||
|
@ -105,6 +68,4 @@ registerHelpers = function () {
|
|||
};
|
||||
|
||||
module.exports = coreHelpers;
|
||||
module.exports.loadCoreHelpers = registerHelpers;
|
||||
module.exports.registerThemeHelper = registerThemeHelper;
|
||||
module.exports.registerAsyncThemeHelper = registerAsyncThemeHelper;
|
||||
module.exports.loadCoreHelpers = registerAllCoreHelpers;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// # Is Helper
|
||||
// Usage: `{{#is "paged"}}`, `{{#is "index, paged"}}`
|
||||
// Checks whether we're in a given context.
|
||||
var _ = require('lodash'),
|
||||
logging = require('../logging'),
|
||||
i18n = require('../i18n'),
|
||||
is;
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
logging = proxy.logging,
|
||||
i18n = proxy.i18n;
|
||||
|
||||
is = function (context, options) {
|
||||
module.exports = function is(context, options) {
|
||||
options = options || {};
|
||||
|
||||
var currentContext = options.data.root.context;
|
||||
|
@ -30,4 +30,3 @@ is = function (context, options) {
|
|||
return options.inverse(this);
|
||||
};
|
||||
|
||||
module.exports = is;
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
// We use the name meta_description to match the helper for consistency:
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
|
||||
var getMetaDataDescription = require('../data/meta/description');
|
||||
var proxy = require('./proxy'),
|
||||
getMetaDataDescription = proxy.metaData.getMetaDataDescription;
|
||||
|
||||
function meta_description(options) {
|
||||
options = options || {};
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
// We use the name meta_title to match the helper for consistency:
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
|
||||
var getMetaDataTitle = require('../data/meta/title');
|
||||
var proxy = require('./proxy'),
|
||||
getMetaDataTitle = proxy.metaData.getMetaDataTitle;
|
||||
|
||||
function meta_title(options) {
|
||||
return getMetaDataTitle(this, options.data.root);
|
||||
|
|
|
@ -2,15 +2,14 @@
|
|||
// `{{navigation}}`
|
||||
// Outputs navigation menu of static urls
|
||||
|
||||
var _ = require('lodash'),
|
||||
hbs = require('express-hbs'),
|
||||
i18n = require('../i18n'),
|
||||
errors = require('../errors'),
|
||||
template = require('./template'),
|
||||
navigation;
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
SafeString = proxy.SafeString,
|
||||
i18n = proxy.i18n,
|
||||
errors = proxy.errors,
|
||||
templates = proxy.templates;
|
||||
|
||||
navigation = function (options) {
|
||||
/*jshint unused:false*/
|
||||
module.exports = function navigation(options) {
|
||||
var navigationData = options.data.blog.navigation,
|
||||
currentUrl = options.data.root.relativeUrl,
|
||||
self = this,
|
||||
|
@ -58,7 +57,7 @@ navigation = function (options) {
|
|||
|
||||
// {{navigation}} should no-op if no data passed in
|
||||
if (navigationData.length === 0) {
|
||||
return new hbs.SafeString('');
|
||||
return new SafeString('');
|
||||
}
|
||||
|
||||
output = navigationData.map(function (e) {
|
||||
|
@ -73,7 +72,6 @@ navigation = function (options) {
|
|||
|
||||
data = _.merge({}, {navigation: output});
|
||||
|
||||
return template.execute('navigation', data, options);
|
||||
return templates.execute('navigation', data, options);
|
||||
};
|
||||
|
||||
module.exports = navigation;
|
||||
|
|
|
@ -8,15 +8,13 @@
|
|||
//
|
||||
// We use the name page_url to match the helper for consistency:
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
var getPaginatedUrl = require('../data/meta/paginated_url'),
|
||||
page_url;
|
||||
var proxy = require('./proxy'),
|
||||
getPaginatedUrl = proxy.metaData.getPaginatedUrl;
|
||||
|
||||
page_url = function (page, options) {
|
||||
module.exports = function page_url(page, options) {
|
||||
if (!options) {
|
||||
options = page;
|
||||
page = 1;
|
||||
}
|
||||
return getPaginatedUrl(page, options.data.root);
|
||||
};
|
||||
|
||||
module.exports = page_url;
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
// `{{pagination}}`
|
||||
// Outputs previous and next buttons, along with info about the current page
|
||||
|
||||
var _ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n'),
|
||||
template = require('./template'),
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
errors = proxy.errors,
|
||||
i18n = proxy.i18n,
|
||||
templates = proxy.templates,
|
||||
pagination;
|
||||
|
||||
pagination = function (options) {
|
||||
|
@ -37,7 +38,7 @@ pagination = function (options) {
|
|||
|
||||
var data = _.merge({}, this.pagination);
|
||||
|
||||
return template.execute('pagination', data, options);
|
||||
return templates.execute('pagination', data, options);
|
||||
};
|
||||
|
||||
module.exports = pagination;
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
// The 3rd argument is the string that will be output if the variable's value is 1
|
||||
// The 4th argument is the string that will be output if the variable's value is 2+
|
||||
|
||||
var hbs = require('express-hbs'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n'),
|
||||
_ = require('lodash'),
|
||||
plural;
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
errors = proxy.errors,
|
||||
i18n = proxy.i18n,
|
||||
SafeString = proxy.SafeString;
|
||||
|
||||
plural = function (number, options) {
|
||||
module.exports = function plural(number, options) {
|
||||
if (_.isUndefined(options.hash) || _.isUndefined(options.hash.empty) ||
|
||||
_.isUndefined(options.hash.singular) || _.isUndefined(options.hash.plural)) {
|
||||
throw new errors.IncorrectUsageError({
|
||||
|
@ -23,12 +23,11 @@ plural = function (number, options) {
|
|||
}
|
||||
|
||||
if (number === 0) {
|
||||
return new hbs.handlebars.SafeString(options.hash.empty.replace('%', number));
|
||||
return new SafeString(options.hash.empty.replace('%', number));
|
||||
} else if (number === 1) {
|
||||
return new hbs.handlebars.SafeString(options.hash.singular.replace('%', number));
|
||||
return new SafeString(options.hash.singular.replace('%', number));
|
||||
} else if (number >= 2) {
|
||||
return new hbs.handlebars.SafeString(options.hash.plural.replace('%', number));
|
||||
return new SafeString(options.hash.plural.replace('%', number));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = plural;
|
||||
|
|
|
@ -6,19 +6,20 @@
|
|||
// We use the name body_class to match the helper for consistency:
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
|
||||
var hbs = require('express-hbs'),
|
||||
_ = require('lodash'),
|
||||
post_class;
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
SafeString = proxy.SafeString;
|
||||
|
||||
post_class = function (options) {
|
||||
/*jshint unused:false*/
|
||||
module.exports = function post_class() {
|
||||
var classes = ['post'],
|
||||
tags = this.post && this.post.tags ? this.post.tags : this.tags || [],
|
||||
featured = this.post && this.post.featured ? this.post.featured : this.featured || false,
|
||||
page = this.post && this.post.page ? this.post.page : this.page || false;
|
||||
|
||||
if (tags) {
|
||||
classes = classes.concat(tags.map(function (tag) { return 'tag-' + tag.slug; }));
|
||||
classes = classes.concat(tags.map(function (tag) {
|
||||
return 'tag-' + tag.slug;
|
||||
}));
|
||||
}
|
||||
|
||||
if (featured) {
|
||||
|
@ -29,8 +30,8 @@ post_class = function (options) {
|
|||
classes.push('page');
|
||||
}
|
||||
|
||||
classes = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, '');
|
||||
return new hbs.handlebars.SafeString(classes.trim());
|
||||
classes = _.reduce(classes, function (memo, item) {
|
||||
return memo + ' ' + item;
|
||||
}, '');
|
||||
return new SafeString(classes.trim());
|
||||
};
|
||||
|
||||
module.exports = post_class;
|
||||
|
|
|
@ -3,12 +3,15 @@
|
|||
// `{{#prev_post}}<a href ="{{url}}>previous post</a>{{/prev_post}}'
|
||||
// `{{#next_post}}<a href ="{{url absolute="true">next post</a>{{/next_post}}'
|
||||
|
||||
var api = require('../api'),
|
||||
schema = require('../data/schema').checks,
|
||||
Promise = require('bluebird'),
|
||||
fetch, prevNext;
|
||||
var proxy = require('./proxy'),
|
||||
Promise = require('bluebird'),
|
||||
|
||||
fetch = function (apiOptions, options) {
|
||||
api = proxy.api,
|
||||
isPost = proxy.checks.isPost,
|
||||
|
||||
fetch;
|
||||
|
||||
fetch = function fetch(apiOptions, options) {
|
||||
return api.posts.read(apiOptions).then(function (result) {
|
||||
var related = result.posts[0];
|
||||
|
||||
|
@ -25,19 +28,17 @@ fetch = function (apiOptions, options) {
|
|||
// If prevNext method is called without valid post data then we must return a promise, if there is valid post data
|
||||
// then the promise is handled in the api call.
|
||||
|
||||
prevNext = function (options) {
|
||||
module.exports = function prevNext(options) {
|
||||
options = options || {};
|
||||
|
||||
var apiOptions = {
|
||||
include: options.name === 'prev_post' ? 'previous,previous.author,previous.tags' : 'next,next.author,next.tags'
|
||||
};
|
||||
|
||||
if (schema.isPost(this) && this.status === 'published') {
|
||||
if (isPost(this) && this.status === 'published') {
|
||||
apiOptions.slug = this.slug;
|
||||
return fetch(apiOptions, options);
|
||||
} else {
|
||||
return Promise.resolve(options.inverse(this));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = prevNext;
|
||||
|
|
81
core/server/helpers/proxy.js
Normal file
81
core/server/helpers/proxy.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
// This file defines everything that helpers "require"
|
||||
// With the exception of modules like lodash, Bluebird
|
||||
// We can later refactor to enforce this something like we do in apps
|
||||
var hbs = require('../themes/engine'),
|
||||
_ = require('lodash'),
|
||||
settingsCache = require('../settings/cache'),
|
||||
config = require('../config');
|
||||
|
||||
// Direct requires:
|
||||
// - lodash
|
||||
// - bluebird
|
||||
// - downsize
|
||||
// - moment-timezone
|
||||
// - jsonpath
|
||||
|
||||
module.exports = {
|
||||
hbs: hbs,
|
||||
SafeString: hbs.SafeString,
|
||||
escapeExpression: hbs.escapeExpression,
|
||||
|
||||
// TODO: Expose less of the API to make this safe
|
||||
api: require('../api'),
|
||||
// TODO: Only expose "get"
|
||||
settingsCache: settingsCache,
|
||||
|
||||
// These 3 are kind of core and required all the time
|
||||
errors: require('../errors'),
|
||||
i18n: require('../i18n'),
|
||||
logging: require('../logging'),
|
||||
|
||||
// This is used to detect if "isPost" is true in prevNext.
|
||||
checks: require('../data/schema').checks,
|
||||
|
||||
// Config!
|
||||
// Keys used:
|
||||
// minifyAssets in asset helper
|
||||
// isPrivacyDisabled & referrerPolicy used in ghost_head
|
||||
// Subscribe app uses routeKeywords
|
||||
config: {
|
||||
get: config.get.bind(config),
|
||||
isPrivacyDisabled: config.isPrivacyDisabled.bind(config)
|
||||
},
|
||||
|
||||
// Labs utils for enabling/disabling helpers
|
||||
labs: require('../utils/labs'),
|
||||
|
||||
// System for apps to hook into one day maybe
|
||||
filters: require('../filters'),
|
||||
|
||||
// Things required from data/meta
|
||||
metaData: {
|
||||
get: require('../data/meta'), // ghost_head
|
||||
getAssetUrl: require('../data/meta/asset_url'), // asset
|
||||
getMetaDataExcerpt: require('../data/meta/excerpt'), // excerpt
|
||||
getMetaDataDescription: require('../data/meta/description'), // meta_desc
|
||||
getMetaDataTitle: require('../data/meta/title'), // meta_title
|
||||
getPaginatedUrl: require('../data/meta/paginated_url'), // page_url
|
||||
getMetaDataUrl: require('../data/meta/url') // url
|
||||
},
|
||||
|
||||
// The local template thing, should this be merged with the channels one?
|
||||
templates: require('./template'),
|
||||
|
||||
// Various utils, needs cleaning up / simplifying
|
||||
socialUrls: require('../utils/social-urls'),
|
||||
url: require('../utils').url,
|
||||
utils: {
|
||||
findKey: function findKey(key /* ...objects... */) {
|
||||
var objects = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
return _.reduceRight(objects, function (result, object) {
|
||||
if (object && _.has(object, key) && !_.isEmpty(object[key])) {
|
||||
result = object[key];
|
||||
}
|
||||
|
||||
return result;
|
||||
}, null);
|
||||
}
|
||||
},
|
||||
visibility: require('../utils/visibility')
|
||||
};
|
34
core/server/helpers/register.js
Normal file
34
core/server/helpers/register.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
var hbs = require('../themes/engine'),
|
||||
Promise = require('bluebird'),
|
||||
errors = require('../errors');
|
||||
|
||||
// Register an async handlebars helper for a given handlebars instance
|
||||
function asyncHelperWrapper(hbs, name, fn) {
|
||||
hbs.registerAsyncHelper(name, function returnAsync(context, options, cb) {
|
||||
// Handle the case where we only get context and cb
|
||||
if (!cb) {
|
||||
cb = options;
|
||||
options = undefined;
|
||||
}
|
||||
|
||||
// Wrap the function passed in with a when.resolve so it can return either a promise or a value
|
||||
Promise.resolve(fn.call(this, context, options)).then(function (result) {
|
||||
cb(result);
|
||||
}).catch(function (err) {
|
||||
throw new errors.IncorrectUsageError({
|
||||
err: err,
|
||||
context: 'registerAsyncThemeHelper: ' + name
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Register a handlebars helper for themes
|
||||
module.exports.registerThemeHelper = function registerThemeHelper(name, fn) {
|
||||
hbs.registerHelper(name, fn);
|
||||
};
|
||||
|
||||
// Register an async handlebars helper for themes
|
||||
module.exports.registerAsyncThemeHelper = function registerAsyncThemeHelper(name, fn) {
|
||||
asyncHelperWrapper(hbs, name, fn);
|
||||
};
|
|
@ -6,14 +6,15 @@
|
|||
//
|
||||
// Note that the standard {{#each tags}} implementation is unaffected by this helper
|
||||
|
||||
var hbs = require('express-hbs'),
|
||||
_ = require('lodash'),
|
||||
utils = require('../utils'),
|
||||
localUtils = require('./utils'),
|
||||
visibilityFilter = require('../utils/visibility-filter'),
|
||||
tags;
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
|
||||
tags = function (options) {
|
||||
SafeString = proxy.SafeString,
|
||||
templates = proxy.templates,
|
||||
url = proxy.url,
|
||||
visibilityUtils = proxy.visibility;
|
||||
|
||||
module.exports = function tags(options) {
|
||||
options = options || {};
|
||||
options.hash = options.hash || {};
|
||||
|
||||
|
@ -24,18 +25,18 @@ tags = function (options) {
|
|||
limit = options.hash.limit ? parseInt(options.hash.limit, 10) : undefined,
|
||||
from = options.hash.from ? parseInt(options.hash.from, 10) : 1,
|
||||
to = options.hash.to ? parseInt(options.hash.to, 10) : undefined,
|
||||
visibility = localUtils.parseVisibility(options),
|
||||
visibility = visibilityUtils.parser(options),
|
||||
output = '';
|
||||
|
||||
function createTagList(tags) {
|
||||
function processTag(tag) {
|
||||
return autolink ? localUtils.linkTemplate({
|
||||
url: utils.url.urlFor('tag', {tag: tag}),
|
||||
return autolink ? templates.link({
|
||||
url: url.urlFor('tag', {tag: tag}),
|
||||
text: _.escape(tag.name)
|
||||
}) : _.escape(tag.name);
|
||||
}
|
||||
|
||||
return visibilityFilter(tags, visibility, !!options.hash.visibility, processTag);
|
||||
return visibilityUtils.filter(tags, visibility, !!options.hash.visibility, processTag);
|
||||
}
|
||||
|
||||
if (this.tags && this.tags.length) {
|
||||
|
@ -49,7 +50,5 @@ tags = function (options) {
|
|||
output = prefix + output + suffix;
|
||||
}
|
||||
|
||||
return new hbs.handlebars.SafeString(output);
|
||||
return new SafeString(output);
|
||||
};
|
||||
|
||||
module.exports = tags;
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
var templates = {},
|
||||
hbs = require('express-hbs'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n');
|
||||
var templates = {},
|
||||
|
||||
_ = require('lodash'),
|
||||
hbs = require('../themes/engine'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n');
|
||||
|
||||
// ## Template utils
|
||||
|
||||
// Execute a template helper
|
||||
// All template helpers are register as partial view.
|
||||
templates.execute = function (name, context, options) {
|
||||
templates.execute = function execute(name, context, options) {
|
||||
var partial = hbs.handlebars.partials[name];
|
||||
|
||||
if (partial === undefined) {
|
||||
|
@ -21,7 +23,12 @@ templates.execute = function (name, context, options) {
|
|||
hbs.registerPartial(partial);
|
||||
}
|
||||
|
||||
return new hbs.handlebars.SafeString(partial(context, options));
|
||||
return new hbs.SafeString(partial(context, options));
|
||||
};
|
||||
|
||||
templates.asset = _.template('<%= source %>?v=<%= version %>');
|
||||
templates.link = _.template('<a href="<%= url %>"><%= text %></a>');
|
||||
templates.script = _.template('<script src="<%= source %>?v=<%= version %>"></script>');
|
||||
templates.input = _.template('<input class="<%= className %>" type="<%= type %>" name="<%= name %>" <%= extras %> />');
|
||||
|
||||
module.exports = templates;
|
||||
|
|
|
@ -3,11 +3,10 @@
|
|||
//
|
||||
// Overrides the standard behaviour of `{[title}}` to ensure the content is correctly escaped
|
||||
|
||||
var hbs = require('express-hbs'),
|
||||
title;
|
||||
var proxy = require('./proxy'),
|
||||
SafeString = proxy.SafeString,
|
||||
escapeExpression = proxy.escapeExpression;
|
||||
|
||||
title = function () {
|
||||
return new hbs.handlebars.SafeString(hbs.handlebars.Utils.escapeExpression(this.title || ''));
|
||||
module.exports = function title() {
|
||||
return new SafeString(escapeExpression(this.title || ''));
|
||||
};
|
||||
|
||||
module.exports = title;
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
// We use the name twitter_url to match the helper for consistency:
|
||||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
|
||||
var socialUrls = require('../utils/social-urls'),
|
||||
findKey = require('./utils').findKey,
|
||||
twitter_url;
|
||||
var proxy = require('./proxy'),
|
||||
socialUrls = proxy.socialUrls,
|
||||
findKey = proxy.utils.findKey;
|
||||
|
||||
twitter_url = function twitter_url(username, options) {
|
||||
module.exports = function twitter_url(username, options) {
|
||||
if (!options) {
|
||||
options = username;
|
||||
username = findKey('twitter', this, options.data.blog);
|
||||
|
@ -22,5 +22,3 @@ twitter_url = function twitter_url(username, options) {
|
|||
|
||||
return null;
|
||||
};
|
||||
|
||||
module.exports = twitter_url;
|
||||
|
|
|
@ -4,16 +4,15 @@
|
|||
// Returns the URL for the current object scope i.e. If inside a post scope will return post permalink
|
||||
// `absolute` flag outputs absolute URL, else URL is relative
|
||||
|
||||
var hbs = require('express-hbs'),
|
||||
getMetaDataUrl = require('../data/meta/url');
|
||||
var proxy = require('./proxy'),
|
||||
SafeString = proxy.SafeString,
|
||||
getMetaDataUrl = proxy.metaData.getMetaDataUrl;
|
||||
|
||||
function url(options) {
|
||||
module.exports = function url(options) {
|
||||
var absolute = options && options.hash.absolute,
|
||||
url = getMetaDataUrl(this, absolute);
|
||||
outputUrl = getMetaDataUrl(this, absolute);
|
||||
|
||||
url = encodeURI(decodeURI(url));
|
||||
outputUrl = encodeURI(decodeURI(outputUrl));
|
||||
|
||||
return new hbs.SafeString(url);
|
||||
}
|
||||
|
||||
module.exports = url;
|
||||
return new SafeString(outputUrl);
|
||||
};
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
var _ = require('lodash'),
|
||||
utils;
|
||||
|
||||
utils = {
|
||||
assetTemplate: _.template('<%= source %>?v=<%= version %>'),
|
||||
linkTemplate: _.template('<a href="<%= url %>"><%= text %></a>'),
|
||||
scriptTemplate: _.template('<script src="<%= source %>?v=<%= version %>"></script>'),
|
||||
inputTemplate: _.template('<input class="<%= className %>" type="<%= type %>" name="<%= name %>" <%= extras %> />'),
|
||||
// @TODO this can probably be made more generic and used in more places
|
||||
findKey: function findKey(key, object, data) {
|
||||
if (object && _.has(object, key) && !_.isEmpty(object[key])) {
|
||||
return object[key];
|
||||
}
|
||||
|
||||
if (data && _.has(data, key) && !_.isEmpty(data[key])) {
|
||||
return data[key];
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
parseVisibility: function parseVisibility(options) {
|
||||
if (!options.hash.visibility) {
|
||||
return ['public'];
|
||||
}
|
||||
|
||||
return _.map(options.hash.visibility.split(','), _.trim);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = utils;
|
|
@ -4,9 +4,18 @@ var _ = require('lodash'),
|
|||
errors = require('../errors'),
|
||||
i18n = require('../i18n'),
|
||||
templates = require('../controllers/frontend/templates'),
|
||||
escapeExpression = hbs.Utils.escapeExpression,
|
||||
_private = {},
|
||||
errorHandler = {};
|
||||
|
||||
_private.createHbsEngine = function createHbsEngine() {
|
||||
var engine = hbs.create();
|
||||
// @TODO get rid of this after #8126
|
||||
engine.registerHelper('asset', require('../helpers/asset'));
|
||||
|
||||
return engine.express4();
|
||||
};
|
||||
|
||||
/**
|
||||
* This function splits the stack into pieces, that are then rendered using the following handlebars code:
|
||||
* ```
|
||||
|
@ -111,7 +120,7 @@ _private.HTMLErrorRenderer = function HTMLErrorRender(err, req, res, /*jshint un
|
|||
// It can be that something went wrong with the theme or otherwise loading handlebars
|
||||
// This ensures that no matter what res.render will work here
|
||||
if (_.isEmpty(req.app.engines)) {
|
||||
req.app.engine('hbs', hbs.express3());
|
||||
req.app.engine('hbs', _private.createHbsEngine());
|
||||
}
|
||||
|
||||
res.render(templates.error(err.statusCode), templateData, function renderResponse(err, html) {
|
||||
|
@ -124,9 +133,9 @@ _private.HTMLErrorRenderer = function HTMLErrorRender(err, req, res, /*jshint un
|
|||
return res.status(500).send(
|
||||
'<h1>' + i18n.t('errors.errors.oopsErrorTemplateHasError') + '</h1>' +
|
||||
'<p>' + i18n.t('errors.errors.encounteredError') + '</p>' +
|
||||
'<pre>' + hbs.handlebars.Utils.escapeExpression(err.message || err) + '</pre>' +
|
||||
'<pre>' + escapeExpression(err.message || err) + '</pre>' +
|
||||
'<br ><p>' + i18n.t('errors.errors.whilstTryingToRender') + '</p>' +
|
||||
err.statusCode + ' ' + '<pre>' + hbs.handlebars.Utils.escapeExpression(err.message || err) + '</pre>'
|
||||
err.statusCode + ' ' + '<pre>' + escapeExpression(err.message || err) + '</pre>'
|
||||
);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -20,8 +20,7 @@ var _ = require('lodash'),
|
|||
join = require('path').join,
|
||||
themeConfig = require('./config'),
|
||||
config = require('../config'),
|
||||
// @TODO: remove this require
|
||||
hbs = require('express-hbs'),
|
||||
engine = require('./engine'),
|
||||
// Current instance of ActiveTheme
|
||||
currentActiveTheme;
|
||||
|
||||
|
@ -62,17 +61,13 @@ class ActiveTheme {
|
|||
}
|
||||
|
||||
get partialsPath() {
|
||||
return join(this.path, 'partials');
|
||||
return this._partials.length > 0 ? join(this.path, 'partials') : null;
|
||||
}
|
||||
|
||||
get mounted() {
|
||||
return this._mounted;
|
||||
}
|
||||
|
||||
hasPartials() {
|
||||
return this._partials.length > 0;
|
||||
}
|
||||
|
||||
hasTemplate(templateName) {
|
||||
return this._templates.indexOf(templateName) > -1;
|
||||
}
|
||||
|
@ -82,17 +77,6 @@ class ActiveTheme {
|
|||
}
|
||||
|
||||
mount(blogApp) {
|
||||
let hbsOptions = {
|
||||
partialsDir: [config.get('paths').helperTemplates],
|
||||
onCompile: function onCompile(exhbs, source) {
|
||||
return exhbs.handlebars.compile(source, {preventIndent: true});
|
||||
}
|
||||
};
|
||||
|
||||
if (this.hasPartials()) {
|
||||
hbsOptions.partialsDir.push(this.partialsPath);
|
||||
}
|
||||
|
||||
// reset the asset hash
|
||||
// @TODO: set this on the theme instead of globally, or use proper file-based hash
|
||||
config.set('assetHash', null);
|
||||
|
@ -100,7 +84,7 @@ class ActiveTheme {
|
|||
blogApp.cache = {};
|
||||
// Set the views and engine
|
||||
blogApp.set('views', this.path);
|
||||
blogApp.engine('hbs', hbs.express3(hbsOptions));
|
||||
blogApp.engine('hbs', engine.configure(this.partialsPath));
|
||||
|
||||
this._mounted = true;
|
||||
}
|
||||
|
|
27
core/server/themes/engine.js
Normal file
27
core/server/themes/engine.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
var hbs = require('express-hbs'),
|
||||
config = require('../config'),
|
||||
instance = hbs.create();
|
||||
|
||||
// @TODO think about a config option for this e.g. theme.devmode?
|
||||
if (config.get('env') !== 'production') {
|
||||
instance.handlebars.logger.level = 0;
|
||||
}
|
||||
|
||||
instance.escapeExpression = instance.handlebars.Utils.escapeExpression;
|
||||
|
||||
instance.configure = function configure(partialsPath) {
|
||||
var hbsOptions = {
|
||||
partialsDir: [config.get('paths').helperTemplates],
|
||||
onCompile: function onCompile(exhbs, source) {
|
||||
return exhbs.handlebars.compile(source, {preventIndent: true});
|
||||
}
|
||||
};
|
||||
|
||||
if (partialsPath) {
|
||||
hbsOptions.partialsDir.push(partialsPath);
|
||||
}
|
||||
|
||||
return instance.express4(hbsOptions);
|
||||
};
|
||||
|
||||
module.exports = instance;
|
|
@ -1,5 +1,5 @@
|
|||
var _ = require('lodash'),
|
||||
hbs = require('express-hbs'),
|
||||
hbs = require('./engine'),
|
||||
utils = require('../utils'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n'),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
var settingsCache = require('../settings/cache'),
|
||||
_ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
hbs = require('express-hbs'),
|
||||
SafeString = require('../themes/engine').SafeString,
|
||||
errors = require('../errors'),
|
||||
logging = require('../logging'),
|
||||
i18n = require('../i18n'),
|
||||
|
@ -32,7 +32,7 @@ labs.enabledHelper = function enabledHelper(options, callback) {
|
|||
|
||||
logging.error(new errors.GhostError(errDetails));
|
||||
|
||||
errString = new hbs.handlebars.SafeString(
|
||||
errString = new SafeString(
|
||||
'<script>console.error("' + _.values(errDetails).join(' ') + '");</script>'
|
||||
);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ var _ = require('lodash');
|
|||
* @param {Function} [fn]
|
||||
* @returns {Array|Object} filtered items
|
||||
*/
|
||||
module.exports = function visibilityFilter(items, visibility, explicit, fn) {
|
||||
module.exports.filter = function visibilityFilter(items, visibility, explicit, fn) {
|
||||
var memo = _.isArray(items) ? [] : {};
|
||||
|
||||
if (_.includes(visibility, 'all')) {
|
||||
|
@ -27,3 +27,11 @@ module.exports = function visibilityFilter(items, visibility, explicit, fn) {
|
|||
return memo;
|
||||
}, memo);
|
||||
};
|
||||
|
||||
module.exports.parser = function visibilityParser(options) {
|
||||
if (!options.hash.visibility) {
|
||||
return ['public'];
|
||||
}
|
||||
|
||||
return _.map(options.hash.visibility.split(','), _.trim);
|
||||
};
|
|
@ -4,7 +4,7 @@ var should = require('should'),
|
|||
EventEmitter = require('events').EventEmitter,
|
||||
_ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
helpers = require('../../server/helpers'),
|
||||
helpers = require('../../server/helpers/register'),
|
||||
filters = require('../../server/filters'),
|
||||
i18n = require('../../server/i18n'),
|
||||
|
||||
|
@ -218,7 +218,7 @@ describe('Apps', function () {
|
|||
});
|
||||
|
||||
it('allows helper registration with permission', function () {
|
||||
var registerSpy = sandbox.spy(helpers, 'registerThemeHelper'),
|
||||
var registerSpy = sandbox.stub(helpers, 'registerThemeHelper'),
|
||||
appProxy = new AppProxy({
|
||||
name: 'TestApp',
|
||||
permissions: {
|
||||
|
@ -234,7 +234,7 @@ describe('Apps', function () {
|
|||
});
|
||||
|
||||
it('does not allow helper registration without permission', function () {
|
||||
var registerSpy = sandbox.spy(helpers, 'registerThemeHelper'),
|
||||
var registerSpy = sandbox.stub(helpers, 'registerThemeHelper'),
|
||||
appProxy = new AppProxy({
|
||||
name: 'TestApp',
|
||||
permissions: {
|
||||
|
@ -255,7 +255,7 @@ describe('Apps', function () {
|
|||
});
|
||||
|
||||
it('does allow INTERNAL app to register helper without permission', function () {
|
||||
var registerSpy = sandbox.spy(helpers, 'registerThemeHelper'),
|
||||
var registerSpy = sandbox.stub(helpers, 'registerThemeHelper'),
|
||||
appProxy = new AppProxy({
|
||||
name: 'TestApp',
|
||||
permissions: {},
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('{{date}} helper', function () {
|
|||
var rendered = helpers.date.call({published_at: d}, context);
|
||||
|
||||
should.exist(rendered);
|
||||
rendered.should.equal(moment(d).tz(timezones).format(format));
|
||||
String(rendered).should.equal(moment(d).tz(timezones).format(format));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -57,7 +57,7 @@ describe('{{date}} helper', function () {
|
|||
var rendered = helpers.date.call({published_at: d}, context);
|
||||
|
||||
should.exist(rendered);
|
||||
rendered.should.equal(moment(d).tz(timezones).from(timeNow));
|
||||
String(rendered).should.equal(moment(d).tz(timezones).from(timeNow));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,7 +3,8 @@ var should = require('should'), // jshint ignore:line
|
|||
_ = require('lodash'),
|
||||
|
||||
// Stuff we are testing
|
||||
helpers = require('../../../server/helpers'),
|
||||
helpers = require.main.require('core/server/helpers'),
|
||||
handlebars = require.main.require('core/server/themes/engine').handlebars,
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
|
@ -250,8 +251,7 @@ describe('{{#foreach}} helper', function () {
|
|||
});
|
||||
|
||||
describe('(compile)', function () {
|
||||
var handlebars = require('express-hbs').handlebars,
|
||||
objectHash = {
|
||||
var objectHash = {
|
||||
posts: {
|
||||
first: {title: 'first'},
|
||||
second: {title: 'second'},
|
||||
|
|
|
@ -3,7 +3,8 @@ var should = require('should'), // jshint ignore:line
|
|||
|
||||
// Stuff we are testing
|
||||
helpers = require('../../../server/helpers'),
|
||||
settingsCache = require('../../../server/settings/cache'),
|
||||
proxy = require('../../../server/helpers/proxy'),
|
||||
settingsCache = proxy.settingsCache,
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
|
|
|
@ -5,9 +5,10 @@ var should = require('should'), // jshint ignore:line
|
|||
moment = require('moment'),
|
||||
configUtils = require('../../utils/configUtils'),
|
||||
helpers = require('../../../server/helpers'),
|
||||
api = require('../../../server/api'),
|
||||
labs = require('../../../server/utils/labs'),
|
||||
settingsCache = require('../../../server/settings/cache'),
|
||||
proxy = require('../../../server/helpers/proxy'),
|
||||
settingsCache = proxy.settingsCache,
|
||||
api = proxy.api,
|
||||
labs = proxy.labs,
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
var should = require('should'),
|
||||
hbs = require('express-hbs'),
|
||||
hbs = require('../../../server/themes/engine'),
|
||||
|
||||
configUtils = require('../../utils/configUtils'),
|
||||
path = require('path'),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
var should = require('should'), // jshint ignore:line
|
||||
hbs = require('express-hbs'),
|
||||
hbs = require('../../../server/themes/engine'),
|
||||
configUtils = require('../../utils/configUtils'),
|
||||
path = require('path'),
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||||
var should = require('should'), // jshint ignore:line
|
||||
_ = require('lodash'),
|
||||
hbs = require('express-hbs'),
|
||||
hbs = require.main.require('core/server/themes/engine'),
|
||||
|
||||
// Stuff we are testing
|
||||
helpers = require.main.require('core/server/helpers');
|
||||
|
@ -22,7 +22,7 @@ describe('Helpers', function () {
|
|||
});
|
||||
|
||||
// This will work when we finish refactoring
|
||||
it.skip('should have exactly the right helpers', function () {
|
||||
it('should have exactly the right helpers', function () {
|
||||
var foundHelpers, missingHelpers, unexpectedHelpers;
|
||||
|
||||
foundHelpers = _.keys(hbs.handlebars.helpers);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
var should = require('should'),
|
||||
hbs = require('express-hbs'),
|
||||
hbs = require.main.require('core/server/themes/engine'),
|
||||
|
||||
// Stuff we are testing
|
||||
template = require('../../server/helpers/template');
|
||||
template = require.main.require('core/server/helpers/template');
|
||||
|
||||
describe('Helpers Template', function () {
|
||||
it('can execute a template', function () {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
var should = require('should'), // jshint ignore:line
|
||||
sinon = require('sinon'),
|
||||
hbs = require('express-hbs'),
|
||||
|
||||
config = require('../../../server/config'),
|
||||
// is only exposed via themes.getActive()
|
||||
activeTheme = require('../../../server/themes/active'),
|
||||
engine = require('../../../server/themes/engine'),
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
|
@ -15,11 +15,11 @@ describe('Themes', function () {
|
|||
|
||||
describe('Active', function () {
|
||||
describe('Mount', function () {
|
||||
var hbsStub, configStub,
|
||||
var engineStub, configStub,
|
||||
fakeBlogApp, fakeLoadedTheme, fakeCheckedTheme;
|
||||
|
||||
beforeEach(function () {
|
||||
hbsStub = sandbox.stub(hbs, 'express3');
|
||||
engineStub = sandbox.stub(engine, 'configure');
|
||||
configStub = sandbox.stub(config, 'set');
|
||||
|
||||
fakeBlogApp = {
|
||||
|
@ -58,11 +58,9 @@ describe('Themes', function () {
|
|||
fakeBlogApp.set.calledOnce.should.be.true();
|
||||
fakeBlogApp.set.calledWith('views', 'my/fake/theme/path').should.be.true();
|
||||
|
||||
// Check handlebars was initialised correctly
|
||||
hbsStub.calledOnce.should.be.true();
|
||||
hbsStub.firstCall.args[0].should.be.an.Object().and.have.property('partialsDir');
|
||||
hbsStub.firstCall.args[0].partialsDir.should.be.an.Array().with.lengthOf(2);
|
||||
hbsStub.firstCall.args[0].partialsDir[1].should.eql('my/fake/theme/path/partials');
|
||||
// Check handlebars was configured correctly
|
||||
engineStub.calledOnce.should.be.true();
|
||||
engineStub.calledWith('my/fake/theme/path/partials').should.be.true();
|
||||
|
||||
// Check the theme is now mounted
|
||||
activeTheme.get().mounted.should.be.true();
|
||||
|
@ -91,10 +89,9 @@ describe('Themes', function () {
|
|||
fakeBlogApp.set.calledOnce.should.be.true();
|
||||
fakeBlogApp.set.calledWith('views', 'my/fake/theme/path').should.be.true();
|
||||
|
||||
// Check handlebars was initialised correctly
|
||||
hbsStub.calledOnce.should.be.true();
|
||||
hbsStub.firstCall.args[0].should.be.an.Object().and.have.property('partialsDir');
|
||||
hbsStub.firstCall.args[0].partialsDir.should.have.lengthOf(1);
|
||||
// Check handlebars was configured correctly
|
||||
engineStub.calledOnce.should.be.true();
|
||||
engineStub.calledWith().should.be.true();
|
||||
|
||||
// Check the theme is now mounted
|
||||
activeTheme.get().mounted.should.be.true();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
var should = require('should'), // jshint ignore:line
|
||||
sinon = require('sinon'),
|
||||
hbs = require('express-hbs'),
|
||||
hbs = require('../../../server/themes/engine'),
|
||||
|
||||
themes = require('../../../server/themes'),
|
||||
// is only exposed via themes.getActive()
|
||||
|
|
Loading…
Add table
Reference in a new issue