0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-17 23:44:39 -05:00

Plugin API Refactor: Filter and Theme Helpers

issue #769

- Refactor doFilter to allow returning a promise from a filter handler
  and to also return a promise itself
- Move the logic out of the registerThemeHelper calls and into their own methods so
  we could test them in isolation.
- Assign the server to the ghost instance so the initPlugins method can
  get access to it.
This commit is contained in:
Jacob Gable 2013-09-14 16:34:12 -05:00 committed by Hannah Wolfe
parent 6634c4673d
commit 507174a00b
7 changed files with 718 additions and 546 deletions

View file

@ -30,6 +30,8 @@ var config = require('../config'),
instance, instance,
defaults; defaults;
when.pipeline = require('when/pipeline');
// ## Default values // ## Default values
/** /**
* A hash of default values to use instead of 'magic' numbers/strings. * A hash of default values to use instead of 'magic' numbers/strings.
@ -182,8 +184,6 @@ Ghost.prototype.init = function () {
return when.join( return when.join(
// Check for or initialise a dbHash. // Check for or initialise a dbHash.
initDbHashAndFirstRun(), initDbHashAndFirstRun(),
// Initialize plugins
self.initPlugins(),
// Initialize the permissions actions and objects // Initialize the permissions actions and objects
permissions.init() permissions.init()
); );
@ -282,6 +282,19 @@ Ghost.prototype.registerThemeHelper = function (name, fn) {
hbs.registerHelper(name, fn); hbs.registerHelper(name, fn);
}; };
// Register an async handlebars helper for themes
Ghost.prototype.registerAsyncThemeHelper = function (name, fn) {
hbs.registerAsyncHelper(name, function (options, cb) {
// Wrap the function passed in with a when.resolve so it can
// return either a promise or a value
when.resolve(fn(options)).then(function (result) {
cb(result);
}).otherwise(function (err) {
errors.logAndThrowError(err, "registerAsyncThemeHelper: " + name);
});
});
};
// Register a new filter callback function // Register a new filter callback function
Ghost.prototype.registerFilter = function (name, priority, fn) { Ghost.prototype.registerFilter = function (name, priority, fn) {
// Curry the priority optional parameter to a default of 5 // Curry the priority optional parameter to a default of 5
@ -312,42 +325,61 @@ Ghost.prototype.unregisterFilter = function (name, priority, fn) {
}; };
// Execute filter functions in priority order // Execute filter functions in priority order
Ghost.prototype.doFilter = function (name, args, callback) { Ghost.prototype.doFilter = function (name, args) {
var callbacks = this.filterCallbacks[name]; var callbacks = this.filterCallbacks[name],
priorityCallbacks = [];
// Bug out early if no callbacks by that name // Bug out early if no callbacks by that name
if (!callbacks) { if (!callbacks) {
return callback(args); return when.resolve(args);
} }
// For each priorityLevel
_.times(defaults.maxPriority + 1, function (priority) { _.times(defaults.maxPriority + 1, function (priority) {
// Bug out if no handlers on this priority // Add a function that runs its priority level callbacks in a pipeline
if (!_.isArray(callbacks[priority])) { priorityCallbacks.push(function (currentArgs) {
return; // Bug out if no handlers on this priority
} if (!_.isArray(callbacks[priority])) {
return when.resolve(currentArgs);
// Call each handler for this priority level
_.each(callbacks[priority], function (filterHandler) {
try {
args = filterHandler(args);
} catch (e) {
// If a filter causes an error, we log it so that it can be debugged, but do not throw the error
errors.logError(e);
} }
// Call each handler for this priority level, allowing for promises or values
return when.pipeline(callbacks[priority], currentArgs);
}); });
}); });
callback(args); return when.pipeline(priorityCallbacks, args);
}; };
// Initialise plugins. Will load from config.activePlugins by default // Initialise plugins. Will load from config.activePlugins by default
Ghost.prototype.initPlugins = function (pluginsToLoad) { Ghost.prototype.initPlugins = function (pluginsToLoad) {
pluginsToLoad = pluginsToLoad || models.Settings.activePlugins; pluginsToLoad = pluginsToLoad || JSON.parse(this.settings('activePlugins'));
var self = this; var self = this;
return plugins.init(this, pluginsToLoad).then(function (loadedPlugins) { // If no activePlugins defined in config settings, look in database settings.
// Extend the loadedPlugins onto the available plugins if (!_.isArray(pluginsToLoad)) {
_.extend(self.availablePlugins, loadedPlugins); // The value will be resolved in the promise
pluginsToLoad = models.Settings.read("activePlugins").then(function (activePluginsSetting) {
var settingValue = activePluginsSetting.get('value') || '[]';
try {
// We have to parse the value because it's a string
settingValue = JSON.parse(settingValue) || [];
} catch (e) {
return when.reject(new Error("Failed to parse activePlugins setting value: " + e.message));
}
// Resolve with the array value
return when.resolve(settingValue);
});
}
return when(pluginsToLoad).then(function (pluginsToLoadValue) {
return plugins.init(self, pluginsToLoad).then(function (loadedPlugins) {
// Extend the loadedPlugins onto the available plugins
_.extend(self.availablePlugins, loadedPlugins);
});
}, errors.logAndThrowError); }, errors.logAndThrowError);
}; };

View file

@ -445,22 +445,30 @@ when(ghost.init()).then(function () {
loading.resolve(); loading.resolve();
} }
// ## Start Ghost App // Expose the express server on the ghost instance.
if (getSocket()) { ghost.server = server;
// Make sure the socket is gone before trying to create another
fs.unlink(getSocket(), function (err) { // Initialize plugins then start the server
ghost.initPlugins().then(function () {
// ## Start Ghost App
if (getSocket()) {
// Make sure the socket is gone before trying to create another
fs.unlink(getSocket(), function (err) {
server.listen(
getSocket(),
startGhost
);
fs.chmod(getSocket(), '0744');
});
} else {
server.listen( server.listen(
getSocket(), ghost.config().server.port,
ghost.config().server.host,
startGhost startGhost
); );
fs.chmod(getSocket(), '0744'); }
});
} else { });
server.listen(
ghost.config().server.port,
ghost.config().server.host,
startGhost
);
}
}, errors.logAndThrowError); }, errors.logAndThrowError);

View file

@ -41,7 +41,6 @@ frontendControllers = {
} }
api.posts.browse(options).then(function (page) { api.posts.browse(options).then(function (page) {
var maxPage = page.pages; var maxPage = page.pages;
// A bit of a hack for situations with no content. // A bit of a hack for situations with no content.
@ -56,7 +55,7 @@ frontendControllers = {
} }
// Render the page of posts // Render the page of posts
ghost.doFilter('prePostsRender', page.posts, function (posts) { ghost.doFilter('prePostsRender', page.posts).then(function (posts) {
res.render('index', {posts: posts, pagination: {page: page.page, prev: page.prev, next: page.next, limit: page.limit, total: page.total, pages: page.pages}}); res.render('index', {posts: posts, pagination: {page: page.page, prev: page.prev, next: page.next, limit: page.limit, total: page.total, pages: page.pages}});
}); });
}).otherwise(function (err) { }).otherwise(function (err) {
@ -66,7 +65,7 @@ frontendControllers = {
'single': function (req, res, next) { 'single': function (req, res, next) {
api.posts.read({'slug': req.params.slug}).then(function (post) { api.posts.read({'slug': req.params.slug}).then(function (post) {
if (post) { if (post) {
ghost.doFilter('prePostsRender', post, function (post) { ghost.doFilter('prePostsRender', post).then(function (post) {
res.render('post', {post: post}); res.render('post', {post: post});
}); });
} else { } else {
@ -117,7 +116,7 @@ frontendControllers = {
return res.redirect('/rss/' + maxPage + '/'); return res.redirect('/rss/' + maxPage + '/');
} }
ghost.doFilter('prePostsRender', page.posts, function (posts) { ghost.doFilter('prePostsRender', page.posts).then(function (posts) {
posts.forEach(function (post) { posts.forEach(function (post) {
var item = { var item = {
title: _.escape(post.title), title: _.escape(post.title),
@ -148,7 +147,6 @@ frontendControllers = {
return next(new Error(err)); return next(new Error(err));
}); });
} }
}; };
module.exports = frontendControllers; module.exports = frontendControllers;

View file

@ -1,480 +1,538 @@
var _ = require('underscore'), var _ = require('underscore'),
moment = require('moment'), moment = require('moment'),
downsize = require('downsize'), downsize = require('downsize'),
when = require('when'), when = require('when'),
hbs = require('express-hbs'), hbs = require('express-hbs'),
packageInfo = require('../../../package.json'), errors = require('../errorHandling'),
errors = require('../errorHandling'), models = require('../models'),
models = require('../models'), packageInfo = require('../../../package.json'),
coreHelpers; version = packageInfo.version,
scriptTemplate = _.template("<script src='/built/scripts/<%= name %>?v=<%= version %>'></script>"),
isProduction = process.env.NODE_ENV === 'production',
coreHelpers = {},
registerHelpers;
/**
* [ description]
* @todo ghost core helpers + a way for themes to register them
* @param {Object} context date object
* @param {*} options
* @return {Object} A Moment time / date object
*/
coreHelpers.date = function (context, options) {
if (!options && context.hasOwnProperty('hash')) {
options = context;
context = undefined;
coreHelpers = function (ghost) { // set to published_at by default, if it's available
var paginationHelper, // otherwise, this will print the current date
scriptTemplate = _.template("<script src='/built/scripts/<%= name %>?v=<%= version %>'></script>"), if (this.published_at) {
isProduction = process.env.NODE_ENV === 'production', context = this.published_at;
version = encodeURIComponent(packageInfo.version);
/**
* [ description]
* @todo ghost core helpers + a way for themes to register them
* @param {Object} context date object
* @param {*} options
* @return {Object} A Moment time / date object
*/
ghost.registerThemeHelper('date', function (context, options) {
if (!options && context.hasOwnProperty('hash')) {
options = context;
context = undefined;
// set to published_at by default, if it's available
// otherwise, this will print the current date
if (this.published_at) {
context = this.published_at;
}
} }
}
var f = options.hash.format || 'MMM Do, YYYY', var f = options.hash.format || 'MMM Do, YYYY',
timeago = options.hash.timeago, timeago = options.hash.timeago,
date; date;
if (timeago) { if (timeago) {
date = moment(context).fromNow(); date = moment(context).fromNow();
} else { } else {
date = moment(context).format(f); date = moment(context).format(f);
} }
return date; return date;
}); };
// //
// ### URI Encoding helper // ### URI Encoding helper
// //
// *Usage example:* // *Usage example:*
// `{{encode uri}}` // `{{encode uri}}`
// //
// Returns URI encoded string // Returns URI encoded string
// //
ghost.registerThemeHelper('encode', function (context, str) { coreHelpers.encode = function (context, str) {
var uri = context || str; var uri = context || str;
return new hbs.handlebars.SafeString(encodeURIComponent(uri)); return new hbs.handlebars.SafeString(encodeURIComponent(uri));
}); };
// ### Page URL Helper // ### Page URL Helper
// //
// *Usage example:* // *Usage example:*
// `{{pageUrl 2}}` // `{{pageUrl 2}}`
// //
// Returns the URL for the page specified in the current object // Returns the URL for the page specified in the current object
// context. // context.
// //
ghost.registerThemeHelper('pageUrl', function (context, block) { coreHelpers.pageUrl = function (context, block) {
return context === 1 ? '/' : ('/page/' + context + '/'); return context === 1 ? '/' : ('/page/' + context + '/');
}); };
// ### URL helper // ### URL helper
// //
// *Usage example:* // *Usage example:*
// `{{url}}` // `{{url}}`
// `{{url absolute}}` // `{{url absolute}}`
// //
// Returns the URL for the current object context // Returns the URL for the current object context
// i.e. If inside a post context will return post permalink // i.e. If inside a post context will return post permalink
// absolute flag outputs absolute URL, else URL is relative // absolute flag outputs absolute URL, else URL is relative
ghost.registerThemeHelper('url', function (options) { coreHelpers.url = function (options) {
var output = ''; var output = '';
if (options && options.hash.absolute) { if (options && options.hash.absolute) {
output += ghost.config().url; output += coreHelpers.ghost.config().url;
} }
if (models.isPost(this)) { if (models.isPost(this)) {
output += '/' + this.slug + '/'; output += '/' + this.slug + '/';
} }
return output; return output;
}); };
// ### Author Helper // ### Author Helper
// //
// *Usage example:* // *Usage example:*
// `{{author}}` // `{{author}}`
// //
// Returns the full name of the author of a given post, or a blank string // Returns the full name of the author of a given post, or a blank string
// if the author could not be determined. // if the author could not be determined.
// //
ghost.registerThemeHelper('author', function (context, options) { coreHelpers.author = function (context, options) {
return this.author ? this.author.name : ''; return this.author ? this.author.name : '';
}); };
// ### Tags Helper // ### Tags Helper
// //
// *Usage example:* // *Usage example:*
// `{{tags}}` // `{{tags}}`
// `{{tags separator=" - "}}` // `{{tags separator=' - '}}`
// //
// Returns a string of the tags on the post. // Returns a string of the tags on the post.
// By default, tags are separated by commas. // By default, tags are separated by commas.
// //
// Note that the standard {{#each tags}} implementation is unaffected by this helper // Note that the standard {{#each tags}} implementation is unaffected by this helper
// and can be used for more complex templates. // and can be used for more complex templates.
ghost.registerThemeHelper('tags', function (options) { coreHelpers.tags = function (options) {
var separator = _.isString(options.hash.separator) ? options.hash.separator : ', ', var separator = _.isString(options.hash.separator) ? options.hash.separator : ', ',
prefix = _.isString(options.hash.prefix) ? options.hash.prefix : '', prefix = _.isString(options.hash.prefix) ? options.hash.prefix : '',
suffix = _.isString(options.hash.suffix) ? options.hash.suffix : '', suffix = _.isString(options.hash.suffix) ? options.hash.suffix : '',
output = '', output = '',
tagNames = _.pluck(this.tags, 'name'); tagNames = _.pluck(this.tags, 'name');
if (tagNames.length) { if (tagNames.length) {
output = prefix + tagNames.join(separator) + suffix; output = prefix + tagNames.join(separator) + suffix;
} }
return output; return output;
}); };
// ### Content Helper // ### Content Helper
// //
// *Usage example:* // *Usage example:*
// `{{content}}` // `{{content}}`
// `{{content words=20}}` // `{{content words=20}}`
// `{{content characters=256}}` // `{{content characters=256}}`
// //
// Turns content html into a safestring so that the user doesn't have to // Turns content html into a safestring so that the user doesn't have to
// escape it or tell handlebars to leave it alone with a triple-brace. // escape it or tell handlebars to leave it alone with a triple-brace.
// //
// Enables tag-safe truncation of content by characters or words. // Enables tag-safe truncation of content by characters or words.
// //
// **returns** SafeString content html, complete or truncated. // **returns** SafeString content html, complete or truncated.
// //
ghost.registerThemeHelper('content', function (options) { coreHelpers.content = function (options) {
var truncateOptions = (options || {}).hash || {}; var truncateOptions = (options || {}).hash || {};
truncateOptions = _.pick(truncateOptions, ['words', 'characters']); truncateOptions = _.pick(truncateOptions, ['words', 'characters']);
if (truncateOptions.words || truncateOptions.characters) {
return new hbs.handlebars.SafeString(
downsize(this.html, truncateOptions)
);
}
return new hbs.handlebars.SafeString(this.html);
});
// ### Excerpt Helper
//
// *Usage example:*
// `{{excerpt}}`
// `{{excerpt words=50}}`
// `{{excerpt characters=256}}`
//
// Attempts to remove all HTML from the string, and then shortens the result according to the provided option.
//
// Defaults to words=50
//
// **returns** SafeString truncated, HTML-free content.
//
ghost.registerThemeHelper('excerpt', function (options) {
var truncateOptions = (options || {}).hash || {},
excerpt;
truncateOptions = _.pick(truncateOptions, ['words', 'characters']);
/*jslint regexp:true */
excerpt = String(this.html).replace(/<\/?[^>]+>/gi, '');
excerpt = excerpt.replace(/(\r\n|\n|\r)+/gm, ' ');
/*jslint regexp:false */
if (!truncateOptions.words && !truncateOptions.characters) {
truncateOptions.words = 50;
}
if (truncateOptions.words || truncateOptions.characters) {
return new hbs.handlebars.SafeString( return new hbs.handlebars.SafeString(
downsize(excerpt, truncateOptions) downsize(this.html, truncateOptions)
); );
}
return new hbs.handlebars.SafeString(this.html);
};
// ### Excerpt Helper
//
// *Usage example:*
// `{{excerpt}}`
// `{{excerpt words=50}}`
// `{{excerpt characters=256}}`
//
// Attempts to remove all HTML from the string, and then shortens the result according to the provided option.
//
// Defaults to words=50
//
// **returns** SafeString truncated, HTML-free content.
//
coreHelpers.excerpt = function (options) {
var truncateOptions = (options || {}).hash || {},
excerpt;
truncateOptions = _.pick(truncateOptions, ['words', 'characters']);
/*jslint regexp:true */
excerpt = String(this.html).replace(/<\/?[^>]+>/gi, '');
excerpt = excerpt.replace(/(\r\n|\n|\r)+/gm, ' ');
/*jslint regexp:false */
if (!truncateOptions.words && !truncateOptions.characters) {
truncateOptions.words = 50;
}
return new hbs.handlebars.SafeString(
downsize(excerpt, truncateOptions)
);
};
// ### Filestorage helper
//
// *Usage example:*
// `{{fileStorage}}`
//
// Returns the config value for fileStorage.
coreHelpers.fileStorage = function (context, options) {
if (coreHelpers.ghost.config().hasOwnProperty('fileStorage')) {
return coreHelpers.ghost.config().fileStorage.toString();
}
return "true";
};
coreHelpers.ghostScriptTags = function () {
var scriptFiles = [];
if (isProduction) {
scriptFiles.push("ghost.min.js");
} else {
scriptFiles = [
'vendor.js',
'helpers.js',
'templates.js',
'models.js',
'views.js'
];
}
scriptFiles = _.map(scriptFiles, function (fileName) {
return scriptTemplate({
name: fileName,
version: version
});
}); });
// ### Filestorage helper return scriptFiles.join('');
// };
// *Usage example:*
// `{{fileStorage}}` /*
// * Asynchronous Theme Helpers (Registered with ghost.registerAsyncThemeHelper)
// Returns the config value for fileStorage. */
ghost.registerThemeHelper('fileStorage', function (context, options) {
if (ghost.config().hasOwnProperty('fileStorage')) { coreHelpers.body_class = function (options) {
return ghost.config().fileStorage.toString(); var classes = [],
} tags = this.post && this.post.tags ? this.post.tags : this.tags || [],
return "true"; page = this.post && this.post.page ? this.post.page : this.page || false;
if (_.isString(this.path) && this.path.match(/\/page/)) {
classes.push('archive-template');
} else if (!this.path || this.path === '/' || this.path === '') {
classes.push('home-template');
} else {
classes.push('post-template');
}
if (tags) {
classes = classes.concat(tags.map(function (tag) { return 'tag-' + tag.slug; }));
}
if (page) {
classes.push('page');
}
return coreHelpers.ghost.doFilter('body_class', classes).then(function (classes) {
var classString = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, '');
return new hbs.handlebars.SafeString(classString.trim());
}); });
};
ghost.registerThemeHelper('body_class', function (options) { coreHelpers.post_class = function (options) {
var classes = [], var classes = ['post'],
tags = this.post && this.post.tags ? this.post.tags : this.tags || [], tags = this.post && this.post.tags ? this.post.tags : this.tags || [],
page = this.post && this.post.page ? this.post.page : this.page || false; 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 (_.isString(this.path) && this.path.match(/\/page/)) { if (tags) {
classes.push('archive-template'); classes = classes.concat(tags.map(function (tag) { return 'tag-' + tag.slug; }));
} else if (!this.path || this.path === '/' || this.path === '') { }
classes.push('home-template');
if (featured) {
classes.push('featured');
}
if (page) {
classes.push('page');
}
return coreHelpers.ghost.doFilter('post_class', classes).then(function (classes) {
var classString = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, '');
return new hbs.handlebars.SafeString(classString.trim());
});
};
coreHelpers.ghost_head = function (options) {
var head = [],
majorMinor = /^(\d+\.)?(\d+)/,
trimmedVersion = this.version;
trimmedVersion = trimmedVersion ? trimmedVersion.match(majorMinor)[0] : '?';
head.push('<meta name="generator" content="Ghost ' + trimmedVersion + '" />');
head.push('<link rel="alternate" type="application/rss+xml" title="RSS" href="/rss/">');
return coreHelpers.ghost.doFilter('ghost_head', head).then(function (head) {
var headString = _.reduce(head, function (memo, item) { return memo + '\n' + item; }, '');
return new hbs.handlebars.SafeString(headString.trim());
});
};
coreHelpers.ghost_foot = function (options) {
var foot = [];
foot.push('<script src="/shared/vendor/jquery/jquery.js"></script>');
return coreHelpers.ghost.doFilter('ghost_foot', foot).then(function (foot) {
var footString = _.reduce(foot, function (memo, item) { return memo + ' ' + item; }, '');
return new hbs.handlebars.SafeString(footString.trim());
});
};
coreHelpers.meta_title = function (options) {
var title,
blog;
if (_.isString(this.path)) {
if (!this.path || this.path === '/' || this.path === '' || this.path.match(/\/page/)) {
blog = coreHelpers.ghost.blogGlobals();
title = blog.title;
} else { } else {
classes.push('post-template'); title = this.post.title;
} }
}
if (tags) { return coreHelpers.ghost.doFilter('meta_title', title).then(function (title) {
classes = classes.concat(tags.map(function (tag) { return 'tag-' + tag.slug; })); title = title || "";
} return new hbs.handlebars.SafeString(title.trim());
if (page) {
classes.push('page');
}
return ghost.doFilter('body_class', classes, function (classes) {
var classString = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, '');
return new hbs.handlebars.SafeString(classString.trim());
});
}); });
};
ghost.registerThemeHelper('post_class', function (options) { coreHelpers.meta_description = function (options) {
var classes = ['post'], var description,
tags = this.post && this.post.tags ? this.post.tags : this.tags || [], blog;
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) { if (_.isString(this.path)) {
classes = classes.concat(tags.map(function (tag) { return 'tag-' + tag.slug; })); if (!this.path || this.path === '/' || this.path === '' || this.path.match(/\/page/)) {
} blog = coreHelpers.ghost.blogGlobals();
description = blog.description;
if (featured) {
classes.push('featured');
}
if (page) {
classes.push('page');
}
return ghost.doFilter('post_class', classes, function (classes) {
var classString = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, '');
return new hbs.handlebars.SafeString(classString.trim());
});
});
ghost.registerThemeHelper('ghost_head', function (options) {
var head = [],
majorMinor = /^(\d+\.)?(\d+)/,
trimmedVersion = this.version.match(majorMinor)[0];
head.push('<meta name="generator" content="Ghost ' + trimmedVersion + '" />');
head.push('<link rel="alternate" type="application/rss+xml" title="RSS" href="/rss/">');
return ghost.doFilter('ghost_head', head, function (head) {
var headString = _.reduce(head, function (memo, item) { return memo + '\n' + item; }, '');
return new hbs.handlebars.SafeString(headString.trim());
});
});
ghost.registerThemeHelper('meta_title', function (options) {
var title, blog;
blog = ghost.blogGlobals();
if (_.isString(this.path)) {
if (!this.path || this.path === '/' || this.path === '' || this.path.match(/\/page/)) {
blog = ghost.blogGlobals();
title = blog.title;
} else {
title = this.post ? this.post.title : '';
}
}
return ghost.doFilter('meta_title', title, function (title) {
return new hbs.handlebars.SafeString(title.trim());
});
});
ghost.registerThemeHelper('meta_description', function (options) {
var description, blog;
blog = ghost.blogGlobals();
if (_.isString(this.path)) {
if (!this.path || this.path === '/' || this.path === '' || this.path.match(/\/page/)) {
blog = ghost.blogGlobals();
description = blog.description;
} else {
description = '';
}
}
return ghost.doFilter('meta_description', description, function (description) {
return new hbs.handlebars.SafeString(description.trim());
});
});
ghost.registerThemeHelper('ghost_foot', function (options) {
var foot = [];
foot.push('<script src="/shared/vendor/jquery/jquery.js"></script>');
return ghost.doFilter('ghost_foot', foot, function (foot) {
var footString = _.reduce(foot, function (memo, item) { return memo + ' ' + item; }, '');
return new hbs.handlebars.SafeString(footString.trim());
});
});
/**
* [ description]
*
* @param String key
* @param String default translation
* @param {Object} options
* @return String A correctly internationalised string
*/
ghost.registerThemeHelper('e', function (key, defaultString, options) {
var output;
if (ghost.settings('defaultLang') === 'en' && _.isEmpty(options.hash) && !ghost.settings('forceI18n')) {
output = defaultString;
} else { } else {
output = ghost.polyglot().t(key, options.hash); description = '';
} }
}
return output; return coreHelpers.ghost.doFilter('meta_description', description).then(function (description) {
description = description || "";
return new hbs.handlebars.SafeString(description.trim());
}); });
};
ghost.registerThemeHelper('json', function (object, options) { /**
return JSON.stringify(object); * Localised string helpers
}); *
* @param String key
* @param String default translation
* @param {Object} options
* @return String A correctly internationalised string
*/
coreHelpers.e = function (key, defaultString, options) {
var output;
ghost.registerThemeHelper('foreach', function (context, options) { if (coreHelpers.ghost.settings('defaultLang') === 'en' && _.isEmpty(options.hash) && !coreHelpers.ghost.settings('forceI18n')) {
var fn = options.fn, output = defaultString;
inverse = options.inverse, } else {
i = 0, output = coreHelpers.ghost.polyglot().t(key, options.hash);
j = 0, }
columns = options.hash.columns,
key,
ret = '',
data;
if (options.data) { return output;
data = hbs.handlebars.createFrame(options.data); };
coreHelpers.json = function (object, options) {
return JSON.stringify(object);
};
coreHelpers.foreach = function (context, options) {
var fn = options.fn,
inverse = options.inverse,
i = 0,
j = 0,
columns = options.hash.columns,
key,
ret = "",
data;
if (options.data) {
data = hbs.handlebars.createFrame(options.data);
}
function setKeys(_data, _i, _j, _columns) {
if (_i === 0) {
_data.first = true;
} }
if (_i === _j - 1) {
function setKeys(_data, _i, _j, _columns) { _data.last = true;
if (_i === 0) {
_data.first = true;
}
if (_i === _j - 1) {
_data.last = true;
}
// first post is index zero but still needs to be odd
if (_i % 2 === 1) {
_data.even = true;
} else {
_data.odd = true;
}
if (_i % _columns === 0) {
_data.rowStart = true;
} else if (_i % _columns === (_columns - 1)) {
_data.rowEnd = true;
}
return _data;
} }
if (context && typeof context === 'object') { // first post is index zero but still needs to be odd
if (context instanceof Array) { if (_i % 2 === 1) {
for (j = context.length; i < j; i += 1) { _data.even = true;
} else {
_data.odd = true;
}
if (_i % _columns === 0) {
_data.rowStart = true;
} else if (_i % _columns === (_columns - 1)) {
_data.rowEnd = true;
}
return _data;
}
if (context && typeof context === 'object') {
if (context instanceof Array) {
for (j = context.length; i < j; i += 1) {
if (data) {
data.index = i;
data.first = data.rowEnd = data.rowStart = data.last = data.even = data.odd = false;
data = setKeys(data, i, j, columns);
}
ret = ret + fn(context[i], { data: data });
}
} else {
for (key in context) {
if (context.hasOwnProperty(key)) {
j += 1;
}
}
for (key in context) {
if (context.hasOwnProperty(key)) {
if (data) { if (data) {
data.index = i; data.key = key;
data.first = data.rowEnd = data.rowStart = data.last = data.even = data.odd = false; data.first = data.rowEnd = data.rowStart = data.last = data.even = data.odd = false;
data = setKeys(data, i, j, columns); data = setKeys(data, i, j, columns);
} }
ret = ret + fn(context[i], { data: data }); ret = ret + fn(context[key], {data: data});
} i += 1;
} else {
for (key in context) {
if (context.hasOwnProperty(key)) {
j += 1;
}
}
for (key in context) {
if (context.hasOwnProperty(key)) {
if (data) {
data.key = key;
data.first = data.rowEnd = data.rowStart = data.last = data.even = data.odd = false;
data = setKeys(data, i, j, columns);
}
ret = ret + fn(context[key], {data: data});
i += 1;
}
} }
} }
} }
}
if (i === 0) { if (i === 0) {
ret = inverse(this); ret = inverse(this);
} }
return ret; return ret;
}); };
// A helper for inserting the javascript tags with version hashes // ## Template driven helpers
ghost.registerThemeHelper('ghostScriptTags', function () { // Template driven helpers require that their template is loaded before they can be registered.
var scriptFiles = []; coreHelpers.paginationTemplate = null;
if (isProduction) { // ### Pagination Helper
scriptFiles.push("ghost.min.js"); // `{{pagination}}`
} else { // Outputs previous and next buttons, along with info about the current page
scriptFiles = [ coreHelpers.pagination = function (options) {
'vendor.js', if (!_.isObject(this.pagination) || _.isFunction(this.pagination)) {
'helpers.js', errors.logAndThrowError('pagination data is not an object or is a function');
'templates.js', return;
'models.js', }
'views.js' if (_.isUndefined(this.pagination.page) || _.isUndefined(this.pagination.pages)
]; || _.isUndefined(this.pagination.total) || _.isUndefined(this.pagination.limit)) {
} errors.logAndThrowError('All values must be defined for page, pages, limit and total');
return;
}
if ((!_.isUndefined(this.pagination.next) && !_.isNumber(this.pagination.next))
|| (!_.isUndefined(this.pagination.prev) && !_.isNumber(this.pagination.prev))) {
errors.logAndThrowError('Invalid value, Next/Prev must be a number');
return;
}
if (!_.isNumber(this.pagination.page) || !_.isNumber(this.pagination.pages)
|| !_.isNumber(this.pagination.total) || !_.isNumber(this.pagination.limit)) {
errors.logAndThrowError('Invalid value, check page, pages, limit and total are numbers');
return;
}
return new hbs.handlebars.SafeString(coreHelpers.paginationTemplate(this.pagination));
};
scriptFiles = _.map(scriptFiles, function (fileName) { coreHelpers.helperMissing = function (arg) {
return scriptTemplate({ if (arguments.length === 2) {
name: fileName, return undefined;
version: version }
}); errors.logError('Missing helper: "' + arg + '"');
}); };
return scriptFiles.join(''); registerHelpers = function (ghost) {
}); var paginationHelper;
// ## Template driven helpers // Expose this so our helpers can use it in their code.
// Template driven helpers require that their template is loaded before they can be registered. coreHelpers.ghost = ghost;
ghost.registerThemeHelper('date', coreHelpers.date);
ghost.registerThemeHelper('encode', coreHelpers.encode);
ghost.registerThemeHelper('pageUrl', coreHelpers.pageUrl);
ghost.registerThemeHelper('url', coreHelpers.url);
ghost.registerThemeHelper('author', coreHelpers.author);
ghost.registerThemeHelper('tags', coreHelpers.tags);
ghost.registerThemeHelper('content', coreHelpers.content);
ghost.registerThemeHelper('excerpt', coreHelpers.excerpt);
ghost.registerThemeHelper('fileStorage', coreHelpers.fileStorage);
ghost.registerThemeHelper('ghostScriptTags', coreHelpers.ghostScriptTags);
ghost.registerThemeHelper('e', coreHelpers.e);
ghost.registerThemeHelper('json', coreHelpers.json);
ghost.registerThemeHelper('foreach', coreHelpers.foreach);
ghost.registerThemeHelper('helperMissing', coreHelpers.helperMissing);
ghost.registerAsyncThemeHelper('body_class', coreHelpers.body_class);
ghost.registerAsyncThemeHelper('post_class', coreHelpers.post_class);
ghost.registerAsyncThemeHelper('meta_title', coreHelpers.meta_title);
ghost.registerAsyncThemeHelper('meta_description', coreHelpers.meta_description);
ghost.registerAsyncThemeHelper('ghost_head', coreHelpers.ghost_head);
ghost.registerAsyncThemeHelper('ghost_foot', coreHelpers.ghost_foot);
// ### Pagination Helper
// `{{pagination}}`
// Outputs previous and next buttons, along with info about the current page
paginationHelper = ghost.loadTemplate('pagination').then(function (templateFn) { paginationHelper = ghost.loadTemplate('pagination').then(function (templateFn) {
ghost.registerThemeHelper('pagination', function (options) { coreHelpers.paginationTemplate = templateFn;
if (!_.isObject(this.pagination) || _.isFunction(this.pagination)) {
errors.logAndThrowError('pagination data is not an object or is a function'); ghost.registerThemeHelper('pagination', coreHelpers.pagination);
return;
}
if (_.isUndefined(this.pagination.page) || _.isUndefined(this.pagination.pages)
|| _.isUndefined(this.pagination.total) || _.isUndefined(this.pagination.limit)) {
errors.logAndThrowError('All values must be defined for page, pages, limit and total');
return;
}
if ((!_.isUndefined(this.pagination.next) && !_.isNumber(this.pagination.next))
|| (!_.isUndefined(this.pagination.prev) && !_.isNumber(this.pagination.prev))) {
errors.logAndThrowError('Invalid value, Next/Prev must be a number');
return;
}
if (!_.isNumber(this.pagination.page) || !_.isNumber(this.pagination.pages)
|| !_.isNumber(this.pagination.total) || !_.isNumber(this.pagination.limit)) {
errors.logAndThrowError('Invalid value, check page, pages, limit and total are numbers');
return;
}
return new hbs.handlebars.SafeString(templateFn(this.pagination));
});
}); });
ghost.registerThemeHelper('helperMissing', function (arg) {
if (arguments.length === 2) {
return undefined;
}
errors.logError("Missing helper: '" + arg + "'");
});
// Return once the template-driven helpers have loaded // Return once the template-driven helpers have loaded
return when.join( return when.join(
paginationHelper paginationHelper
); );
}; };
module.exports.loadCoreHelpers = coreHelpers; module.exports = coreHelpers;
module.exports.loadCoreHelpers = registerHelpers;

View file

@ -94,7 +94,7 @@ describe("Ghost API", function () {
ghost.registerFilter(filterName, 2, testFilterHandler2); ghost.registerFilter(filterName, 2, testFilterHandler2);
ghost.registerFilter(filterName, 9, testFilterHandler3); ghost.registerFilter(filterName, 9, testFilterHandler3);
ghost.doFilter(filterName, null, function () { ghost.doFilter(filterName, null).then(function () {
testFilterHandler1.calledBefore(testFilterHandler2).should.equal(true); testFilterHandler1.calledBefore(testFilterHandler2).should.equal(true);
testFilterHandler2.calledBefore(testFilterHandler3).should.equal(true); testFilterHandler2.calledBefore(testFilterHandler3).should.equal(true);
@ -105,6 +105,51 @@ describe("Ghost API", function () {
}); });
}); });
it("executes filters that return a promise", function (done) {
var filterName = 'testprioritypromise',
testFilterHandler1 = sinon.spy(function (args) {
return when.promise(function (resolve) {
process.nextTick(function () {
args.filter1 = true;
resolve(args);
});
});
}),
testFilterHandler2 = sinon.spy(function (args) {
args.filter2 = true;
return args;
}),
testFilterHandler3 = sinon.spy(function (args) {
return when.promise(function (resolve) {
process.nextTick(function () {
args.filter3 = true;
resolve(args);
});
});
});
ghost.registerFilter(filterName, 0, testFilterHandler1);
ghost.registerFilter(filterName, 2, testFilterHandler2);
ghost.registerFilter(filterName, 9, testFilterHandler3);
ghost.doFilter(filterName, { test: true }).then(function (newArgs) {
testFilterHandler1.calledBefore(testFilterHandler2).should.equal(true);
testFilterHandler2.calledBefore(testFilterHandler3).should.equal(true);
testFilterHandler3.called.should.equal(true);
newArgs.filter1.should.equal(true);
newArgs.filter2.should.equal(true);
newArgs.filter3.should.equal(true);
done();
});
});
it("can compile a template", function (done) { it("can compile a template", function (done) {
var template = path.join(process.cwd(), testTemplatePath, 'test.hbs'); var template = path.join(process.cwd(), testTemplatePath, 'test.hbs');

View file

@ -4,13 +4,13 @@ var testUtils = require('./testUtils'),
sinon = require('sinon'), sinon = require('sinon'),
_ = require("underscore"), _ = require("underscore"),
when = require('when'), when = require('when'),
knex = require('../../server/models/base').Knex,
errors = require('../../server/errorHandling'), errors = require('../../server/errorHandling'),
// Stuff we are testing // Stuff we are testing
plugins = require('../../server/plugins'), plugins = require('../../server/plugins'),
GhostPlugin = plugins.GhostPlugin, GhostPlugin = plugins.GhostPlugin,
loader = require('../../server/plugins/loader'); loader = require('../../server/plugins/loader');
describe('Plugins', function () { describe('Plugins', function () {
var sandbox; var sandbox;

View file

@ -29,7 +29,7 @@ describe('Core Helpers', function () {
it('can render content', function () { it('can render content', function () {
var html = "Hello World", var html = "Hello World",
rendered = handlebars.helpers.content.call({html: html}); rendered = helpers.content.call({html: html});
should.exist(rendered); should.exist(rendered);
rendered.string.should.equal(html); rendered.string.should.equal(html);
@ -38,7 +38,7 @@ describe('Core Helpers', function () {
it('can truncate html by word', function () { it('can truncate html by word', function () {
var html = "<p>Hello <strong>World! It's me!</strong></p>", var html = "<p>Hello <strong>World! It's me!</strong></p>",
rendered = ( rendered = (
handlebars.helpers.content helpers.content
.call( .call(
{html: html}, {html: html},
{"hash":{"words": 2}} {"hash":{"words": 2}}
@ -52,7 +52,7 @@ describe('Core Helpers', function () {
it('can truncate html by character', function () { it('can truncate html by character', function () {
var html = "<p>Hello <strong>World! It's me!</strong></p>", var html = "<p>Hello <strong>World! It's me!</strong></p>",
rendered = ( rendered = (
handlebars.helpers.content helpers.content
.call( .call(
{html: html}, {html: html},
{"hash":{"characters": 8}} {"hash":{"characters": 8}}
@ -72,14 +72,14 @@ describe('Core Helpers', function () {
it("Returns the full name of the author from the context",function() { it("Returns the full name of the author from the context",function() {
var data = {"author":{"name":"abc123"}}, var data = {"author":{"name":"abc123"}},
result = handlebars.helpers.author.call(data); result = helpers.author.call(data);
String(result).should.equal("abc123"); String(result).should.equal("abc123");
}); });
it("Returns a blank string where author data is missing",function() { it("Returns a blank string where author data is missing",function() {
var data = {"author": null}, var data = {"author": null},
result = handlebars.helpers.author.call(data); result = helpers.author.call(data);
String(result).should.equal(""); String(result).should.equal("");
}); });
@ -110,7 +110,7 @@ describe('Core Helpers', function () {
it('can render excerpt', function () { it('can render excerpt', function () {
var html = "Hello World", var html = "Hello World",
rendered = handlebars.helpers.excerpt.call({html: html}); rendered = helpers.excerpt.call({html: html});
should.exist(rendered); should.exist(rendered);
rendered.string.should.equal(html); rendered.string.should.equal(html);
@ -123,7 +123,7 @@ describe('Core Helpers', function () {
+ "< test > those<<< test >>> who mistake it &lt;for&gt; binary.", + "< test > those<<< test >>> who mistake it &lt;for&gt; binary.",
expected = "There are 10 types of people in the world: those who understand trinary, those who don't " expected = "There are 10 types of people in the world: those who understand trinary, those who don't "
+ "and those>> who mistake it &lt;for&gt; binary.", + "and those>> who mistake it &lt;for&gt; binary.",
rendered = handlebars.helpers.excerpt.call({html: html}); rendered = helpers.excerpt.call({html: html});
should.exist(rendered); should.exist(rendered);
rendered.string.should.equal(expected); rendered.string.should.equal(expected);
@ -134,7 +134,7 @@ describe('Core Helpers', function () {
var html = "<p>Hello <strong>World! It's me!</strong></p>", var html = "<p>Hello <strong>World! It's me!</strong></p>",
expected = "Hello World", expected = "Hello World",
rendered = ( rendered = (
handlebars.helpers.excerpt.call( helpers.excerpt.call(
{html: html}, {html: html},
{"hash": {"words": 2}} {"hash": {"words": 2}}
) )
@ -148,7 +148,7 @@ describe('Core Helpers', function () {
var html = "<p>Hello <strong>World! It's me!</strong></p>", var html = "<p>Hello <strong>World! It's me!</strong></p>",
expected = "Hello Wo", expected = "Hello Wo",
rendered = ( rendered = (
handlebars.helpers.excerpt.call( helpers.excerpt.call(
{html: html}, {html: html},
{"hash": {"characters": 8}} {"hash": {"characters": 8}}
) )
@ -164,35 +164,48 @@ describe('Core Helpers', function () {
should.exist(handlebars.helpers.body_class); should.exist(handlebars.helpers.body_class);
}); });
it('can render class string', function () { it('can render class string', function (done) {
var rendered = handlebars.helpers.body_class.call({}); helpers.body_class.call({}).then(function (rendered) {
should.exist(rendered); should.exist(rendered);
rendered.string.should.equal('home-template'); rendered.string.should.equal('home-template');
done();
}, done);
}); });
it('can render class string for context', function () { it('can render class string for context', function (done) {
var rendered1 = handlebars.helpers.body_class.call({path: '/'}), when.all([
rendered2 = handlebars.helpers.body_class.call({path: '/a-post-title'}), helpers.body_class.call({path: '/'}),
rendered3 = handlebars.helpers.body_class.call({path: '/page/4'}); helpers.body_class.call({path: '/a-post-title'}),
helpers.body_class.call({path: '/page/4'})
]).then(function (rendered) {
rendered.length.should.equal(3);
should.exist(rendered1); should.exist(rendered[0]);
should.exist(rendered2); should.exist(rendered[1]);
should.exist(rendered3); should.exist(rendered[2]);
rendered1.string.should.equal('home-template'); rendered[0].string.should.equal('home-template');
rendered2.string.should.equal('post-template'); rendered[1].string.should.equal('post-template');
rendered3.string.should.equal('archive-template'); rendered[2].string.should.equal('archive-template');
done();
});
}); });
it('can render class for static page', function () { it('can render class for static page', function (done) {
var rendered = handlebars.helpers.body_class.call( helpers.body_class.call({
{post: {page: true}}, path: '/',
{path: '/'} post: {
); page: true
}
}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('home-template page');
should.exist(rendered); done();
rendered.string.should.equal('home-template page'); }, done);
}); });
}); });
@ -201,19 +214,23 @@ describe('Core Helpers', function () {
should.exist(handlebars.helpers.post_class); should.exist(handlebars.helpers.post_class);
}); });
it('can render class string', function () { it('can render class string', function (done) {
var rendered = handlebars.helpers.post_class.call({}); helpers.post_class.call({}).then(function (rendered) {
should.exist(rendered); should.exist(rendered);
rendered.string.should.equal('post');
rendered.string.should.equal('post'); done();
});
}); });
it('can render featured class', function () { it('can render featured class', function (done) {
var post = { featured: true }, var post = { featured: true };
rendered = handlebars.helpers.post_class.call(post);
should.exist(rendered); helpers.post_class.call(post).then(function (rendered) {
rendered.string.should.equal('post featured'); should.exist(rendered);
rendered.string.should.equal('post featured');
done();
}, done);
}); });
}); });
@ -222,16 +239,20 @@ describe('Core Helpers', function () {
should.exist(handlebars.helpers.ghost_head); should.exist(handlebars.helpers.ghost_head);
}); });
it('returns meta tag string', function () { it('returns meta tag string', function (done) {
var rendered = handlebars.helpers.ghost_head.call({version: "0.3.0"}); helpers.ghost_head.call({version: "0.3.0"}).then(function (rendered) {
should.exist(rendered); should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n<link rel="alternate" type="application/rss+xml" title="RSS" href="/rss/">'); rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n<link rel="alternate" type="application/rss+xml" title="RSS" href="/rss/">');
done();
});
}); });
it('returns meta tag string even if version is invalid', function () { it('returns meta tag string even if version is invalid', function () {
var rendered = handlebars.helpers.ghost_head.call({version: "0.9"}); var rendered = helpers.ghost_head.call({version: "0.9"}).then(function (rendered) {
should.exist(rendered); should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.9" />\n<link rel="alternate" type="application/rss+xml" title="RSS" href="/rss/">'); rendered.string.should.equal('<meta name="generator" content="Ghost 0.9" />\n<link rel="alternate" type="application/rss+xml" title="RSS" href="/rss/">');
});
}); });
}); });
@ -240,10 +261,14 @@ describe('Core Helpers', function () {
should.exist(handlebars.helpers.ghost_foot); should.exist(handlebars.helpers.ghost_foot);
}); });
it('returns meta tag string', function () { it('returns meta tag string', function (done) {
var rendered = handlebars.helpers.ghost_foot.call(); helpers.ghost_foot.call().then(function (rendered) {
should.exist(rendered); should.exist(rendered);
rendered.string.should.equal('<script src="/shared/vendor/jquery/jquery.js"></script>'); rendered.string.should.equal('<script src="/shared/vendor/jquery/jquery.js"></script>');
done();
});
}); });
}); });
@ -253,7 +278,7 @@ describe('Core Helpers', function () {
}); });
it('should return a the slug with a prefix slash if the context is a post', function () { it('should return a the slug with a prefix slash if the context is a post', function () {
var rendered = handlebars.helpers.url.call({html: 'content', markdown: "ff", title: "title", slug: "slug"}); var rendered = helpers.url.call({html: 'content', markdown: "ff", title: "title", slug: "slug"});
should.exist(rendered); should.exist(rendered);
rendered.should.equal('/slug/'); rendered.should.equal('/slug/');
}); });
@ -263,7 +288,7 @@ describe('Core Helpers', function () {
return { url: 'http://testurl.com' }; return { url: 'http://testurl.com' };
}), }),
rendered = handlebars.helpers.url.call( rendered = helpers.url.call(
{html: 'content', markdown: "ff", title: "title", slug: "slug"}, {html: 'content', markdown: "ff", title: "title", slug: "slug"},
{hash: { absolute: 'true'}} {hash: { absolute: 'true'}}
); );
@ -275,10 +300,10 @@ describe('Core Helpers', function () {
}); });
it('should return empty string if not a post', function () { it('should return empty string if not a post', function () {
handlebars.helpers.url.call({markdown: "ff", title: "title", slug: "slug"}).should.equal(''); helpers.url.call({markdown: "ff", title: "title", slug: "slug"}).should.equal('');
handlebars.helpers.url.call({html: 'content', title: "title", slug: "slug"}).should.equal(''); helpers.url.call({html: 'content', title: "title", slug: "slug"}).should.equal('');
handlebars.helpers.url.call({html: 'content', markdown: "ff", slug: "slug"}).should.equal(''); helpers.url.call({html: 'content', markdown: "ff", slug: "slug"}).should.equal('');
handlebars.helpers.url.call({html: 'content', markdown: "ff", title: "title"}).should.equal(''); helpers.url.call({html: 'content', markdown: "ff", title: "title"}).should.equal('');
}); });
}); });
@ -288,9 +313,9 @@ describe('Core Helpers', function () {
}); });
it('can return a valid url', function () { it('can return a valid url', function () {
handlebars.helpers.pageUrl(1).should.equal('/'); helpers.pageUrl(1).should.equal('/');
handlebars.helpers.pageUrl(2).should.equal('/page/2/'); helpers.pageUrl(2).should.equal('/page/2/');
handlebars.helpers.pageUrl(50).should.equal('/page/50/'); helpers.pageUrl(50).should.equal('/page/50/');
}); });
}); });
@ -307,7 +332,7 @@ describe('Core Helpers', function () {
it('can render single page with no pagination necessary', function (done) { it('can render single page with no pagination necessary', function (done) {
var rendered; var rendered;
helpers.loadCoreHelpers(ghost).then(function () { helpers.loadCoreHelpers(ghost).then(function () {
rendered = handlebars.helpers.pagination.call({pagination: {page: 1, prev: undefined, next: undefined, limit: 15, total: 8, pages: 1}}); rendered = helpers.pagination.call({pagination: {page: 1, prev: undefined, next: undefined, limit: 15, total: 8, pages: 1}});
should.exist(rendered); should.exist(rendered);
// strip out carriage returns and compare. // strip out carriage returns and compare.
rendered.string.should.match(paginationRegex); rendered.string.should.match(paginationRegex);
@ -322,7 +347,7 @@ describe('Core Helpers', function () {
it('can render first page of many with older posts link', function (done) { it('can render first page of many with older posts link', function (done) {
var rendered; var rendered;
helpers.loadCoreHelpers(ghost).then(function () { helpers.loadCoreHelpers(ghost).then(function () {
rendered = handlebars.helpers.pagination.call({pagination: {page: 1, prev: undefined, next: 2, limit: 15, total: 8, pages: 3}}); rendered = helpers.pagination.call({pagination: {page: 1, prev: undefined, next: 2, limit: 15, total: 8, pages: 3}});
should.exist(rendered); should.exist(rendered);
rendered.string.should.match(paginationRegex); rendered.string.should.match(paginationRegex);
@ -337,7 +362,7 @@ describe('Core Helpers', function () {
it('can render middle pages of many with older and newer posts link', function (done) { it('can render middle pages of many with older and newer posts link', function (done) {
var rendered; var rendered;
helpers.loadCoreHelpers(ghost).then(function () { helpers.loadCoreHelpers(ghost).then(function () {
rendered = handlebars.helpers.pagination.call({pagination: {page: 2, prev: 1, next: 3, limit: 15, total: 8, pages: 3}}); rendered = helpers.pagination.call({pagination: {page: 2, prev: 1, next: 3, limit: 15, total: 8, pages: 3}});
should.exist(rendered); should.exist(rendered);
rendered.string.should.match(paginationRegex); rendered.string.should.match(paginationRegex);
@ -353,7 +378,7 @@ describe('Core Helpers', function () {
it('can render last page of many with newer posts link', function (done) { it('can render last page of many with newer posts link', function (done) {
var rendered; var rendered;
helpers.loadCoreHelpers(ghost).then(function () { helpers.loadCoreHelpers(ghost).then(function () {
rendered = handlebars.helpers.pagination.call({pagination: {page: 3, prev: 2, next: undefined, limit: 15, total: 8, pages: 3}}); rendered = helpers.pagination.call({pagination: {page: 3, prev: 2, next: undefined, limit: 15, total: 8, pages: 3}});
should.exist(rendered); should.exist(rendered);
rendered.string.should.match(paginationRegex); rendered.string.should.match(paginationRegex);
@ -370,7 +395,7 @@ describe('Core Helpers', function () {
helpers.loadCoreHelpers(ghost).then(function () { helpers.loadCoreHelpers(ghost).then(function () {
var runErrorTest = function (data) { var runErrorTest = function (data) {
return function () { return function () {
handlebars.helpers.pagination.call(data); helpers.pagination.call(data);
}; };
}; };
@ -485,43 +510,49 @@ describe('Core Helpers', function () {
should.exist(handlebars.helpers.meta_title); should.exist(handlebars.helpers.meta_title);
}); });
it('can return blog title', function () { it('can return blog title', function (done) {
var rendered = handlebars.helpers.meta_title.call({path: '/'}); helpers.meta_title.call({path: '/'}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('Ghost');
should.exist(rendered); done();
String(rendered).should.equal('Ghost'); }, done);
}); });
it('can return title of a post', function () { it('can return title of a post', function (done) {
var rendered = handlebars.helpers.meta_title.call( var post = {path: '/nice-post', post: {title: 'Post Title'}};
{path: '/nice-post', post: {title: 'Post Title'}} helpers.meta_title.call(post).then(function (rendered) {
); should.exist(rendered);
rendered.string.should.equal('Post Title');
should.exist(rendered); done();
String(rendered).should.equal('Post Title'); }, done);
}); });
}); });
describe("meta_description helper", function () { describe("meta_description helper", function (done) {
it('has loaded meta_description helper', function () { it('has loaded meta_description helper', function () {
should.exist(handlebars.helpers.meta_description); should.exist(handlebars.helpers.meta_description);
}); });
it('can return blog description', function () { it('can return blog description', function () {
var rendered = handlebars.helpers.meta_description.call({path: '/'}); helpers.meta_description.call({path: '/'}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('Just a blogging platform.');
should.exist(rendered); done();
String(rendered).should.equal('Just a blogging platform.'); }, done);
}); });
it('can return empty description on post', function () { it('can return empty description on post', function (done) {
var rendered = handlebars.helpers.meta_description.call( var post = {path: '/nice-post', post: {title: 'Post Title'}};
{path: '/nice-post', post: {title: 'Post Title'}} helpers.meta_description.call(post).then(function (rendered) {
); should.exist(rendered);
rendered.string.should.equal('');
should.exist(rendered); done();
String(rendered).should.equal(''); }, done);
}); });
}); });