0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-06 22:40:14 -05:00

Merge pull request #4919 from ErisDS/pr/4852

Update to PR 4852 ({{navigation}} helper)
This commit is contained in:
Sebastian Gierlinger 2015-02-18 20:00:34 +01:00
commit 8b4979c7d1
6 changed files with 226 additions and 40 deletions

View file

@ -37,7 +37,27 @@ function getPostPage(options) {
});
}
function formatPageResponse(posts, page) {
/**
* returns a promise with an array of values used in {{navigation}}
* TODO(nsfmc): should this be in the 'prePostsRender' pipeline?
* @return {Promise} containing an array of navigation items
*/
function getSiteNavigation() {
return Promise.resolve(api.settings.read('navigation')).then(function (result) {
if (result && result.settings && result.settings.length) {
return JSON.parse(result.settings[0].value) || [];
}
return [];
});
}
/**
* formats variables for handlebars in multi-post contexts.
* If extraValues are available, they are merged in the final value
* TODO(nsfmc): should this be in the 'prePostsRender' pipeline?
* @return {Promise} containing page variables
*/
function formatPageResponse(posts, page, extraValues) {
// Delete email from author for frontend output
// TODO: do this on API level if no context is available
posts = _.each(posts, function (post) {
@ -46,19 +66,36 @@ function formatPageResponse(posts, page) {
}
return post;
});
return {
posts: posts,
pagination: page.meta.pagination
};
extraValues = extraValues || {};
return getSiteNavigation().then(function (navigation) {
var resp = {
posts: posts,
pagination: page.meta.pagination,
navigation: navigation || {}
};
return _.extend(resp, extraValues);
});
}
/**
* similar to formatPageResponse, but for single post pages
* TODO(nsfmc): should this be in the 'prePostsRender' pipeline?
* @return {Promise} containing page variables
*/
function formatResponse(post) {
// Delete email from author for frontend output
// TODO: do this on API level if no context is available
if (post.author) {
delete post.author.email;
}
return {post: post};
return getSiteNavigation().then(function (navigation) {
return {
post: post,
navigation: navigation
};
});
}
function handleError(next) {
@ -155,7 +192,9 @@ frontendControllers = {
}
setResponseContext(req, res);
res.render(view, formatPageResponse(posts, page));
formatPageResponse(posts, page).then(function (result) {
res.render(view, result);
});
});
});
}).catch(handleError(next));
@ -198,19 +237,19 @@ frontendControllers = {
// Render the page of posts
filters.doFilter('prePostsRender', page.posts).then(function (posts) {
getActiveThemePaths().then(function (paths) {
var view = template.getThemeViewForTag(paths, options.tag),
var view = template.getThemeViewForTag(paths, options.tag);
// Format data for template
result = _.extend(formatPageResponse(posts, page), {
tag: page.meta.filters.tags ? page.meta.filters.tags[0] : ''
});
// If the resulting tag is '' then 404.
if (!result.tag) {
return next();
}
setResponseContext(req, res);
res.render(view, result);
formatPageResponse(posts, page, {
tag: page.meta.filters.tags ? page.meta.filters.tags[0] : ''
}).then(function (result) {
// If the resulting tag is '' then 404.
if (!result.tag) {
return next();
}
setResponseContext(req, res);
res.render(view, result);
});
});
});
}).catch(handleError(next));
@ -253,20 +292,20 @@ frontendControllers = {
// Render the page of posts
filters.doFilter('prePostsRender', page.posts).then(function (posts) {
getActiveThemePaths().then(function (paths) {
var view = paths.hasOwnProperty('author.hbs') ? 'author' : 'index',
var view = paths.hasOwnProperty('author.hbs') ? 'author' : 'index';
// Format data for template
result = _.extend(formatPageResponse(posts, page), {
author: page.meta.filters.author ? page.meta.filters.author : ''
});
formatPageResponse(posts, page, {
author: page.meta.filters.author ? page.meta.filters.author : ''
}).then(function (result) {
// If the resulting author is '' then 404.
if (!result.author) {
return next();
}
// If the resulting author is '' then 404.
if (!result.author) {
return next();
}
setResponseContext(req, res);
res.render(view, result);
setResponseContext(req, res);
res.render(view, result);
});
});
});
}).catch(handleError(next));
@ -339,12 +378,13 @@ frontendControllers = {
filters.doFilter('prePostsRender', post).then(function (post) {
getActiveThemePaths().then(function (paths) {
var view = template.getThemeViewForPost(paths, post),
response = formatResponse(post);
var view = template.getThemeViewForPost(paths, post);
setResponseContext(req, res, response);
return formatResponse(post).then(function (response) {
setResponseContext(req, res, response);
res.render(view, response);
res.render(view, response);
});
});
});
}

View file

@ -27,6 +27,7 @@ coreHelpers.is = require('./is');
coreHelpers.has = require('./has');
coreHelpers.meta_description = require('./meta_description');
coreHelpers.meta_title = require('./meta_title');
coreHelpers.navigation = require('./navigation');
coreHelpers.page_url = require('./page_url');
coreHelpers.pageUrl = require('./page_url').deprecated;
coreHelpers.pagination = require('./pagination');
@ -89,6 +90,7 @@ registerHelpers = function (adminHbs) {
registerThemeHelper('foreach', coreHelpers.foreach);
registerThemeHelper('is', coreHelpers.is);
registerThemeHelper('has', coreHelpers.has);
registerThemeHelper('navigation', coreHelpers.navigation);
registerThemeHelper('page_url', coreHelpers.page_url);
registerThemeHelper('pageUrl', coreHelpers.pageUrl);
registerThemeHelper('pagination', coreHelpers.pagination);

View file

@ -0,0 +1,58 @@
// ### Navigation Helper
// `{{navigation}}`
// Outputs navigation menu of static urls
var _ = require('lodash'),
hbs = require('express-hbs'),
errors = require('../errors'),
template = require('./template'),
navigation;
navigation = function (options) {
/*jshint unused:false*/
var navigation,
context,
currentUrl = this.relativeUrl;
if (!_.isObject(this.navigation) || _.isFunction(this.navigation)) {
return errors.logAndThrowError('navigation data is not an object or is a function');
}
if (this.navigation.filter(function (e) {
return (_.isUndefined(e.label) || _.isUndefined(e.url));
}).length > 0) {
return errors.logAndThrowError('All values must be defined for label, url and current');
}
// check for non-null string values
if (this.navigation.filter(function (e) {
return ((!_.isNull(e.label) && !_.isString(e.label)) ||
(!_.isNull(e.url) && !_.isString(e.url)));
}).length > 0) {
return errors.logAndThrowError('Invalid value, Url and Label must be strings');
}
function _slugify(label) {
return label.toLowerCase().replace(/[^\w ]+/g, '').replace(/ +/g, '-');
}
// {{navigation}} should no-op if no data passed in
if (this.navigation.length === 0) {
return new hbs.SafeString('');
}
navigation = this.navigation.map(function (e) {
var out = {};
out.current = e.url === currentUrl;
out.label = e.label;
out.slug = _slugify(e.label);
out.url = hbs.handlebars.Utils.escapeExpression(e.url);
return out;
});
context = _.merge({}, {navigation: navigation});
return template.execute('navigation', context);
};
module.exports = navigation;

View file

@ -1,7 +0,0 @@
<nav id="site-navigation" role="navigation">
<ul>
{{#links}}
<li class="{{#active}}current-menu-item{{/active}}"><a title="{{{title}}}" href="{{url}}">{{{title}}}</a></li>
{{/links}}
</ul>
</nav>

View file

@ -0,0 +1,5 @@
<ul class="nav">
{{#foreach navigation}}
<li class="nav-{{slug}}{{#if current}} nav-current{{/if}}" role="presentation"><a href="{{url absolute="true"}}">{{label}}</a></li>
{{/foreach}}
</ul>

View file

@ -0,0 +1,88 @@
/*globals describe, before, it*/
/*jshint expr:true*/
var should = require('should'),
hbs = require('express-hbs'),
utils = require('./utils'),
// Stuff we are testing
handlebars = hbs.handlebars,
helpers = require('../../../server/helpers');
describe('{{navigation}} helper', function () {
before(function (done) {
utils.loadHelpers();
hbs.express3({partialsDir: [utils.config.paths.helperTemplates]});
hbs.cachePartials(function () {
done();
});
});
it('has loaded navigation helper', function () {
should.exist(handlebars.helpers.navigation);
});
it('should throw errors on invalid data', function () {
var runHelper = function (data) {
return function () {
helpers.navigation.call(data);
};
};
runHelper('not an object').should.throwError('navigation data is not an object or is a function');
runHelper(function () {}).should.throwError('navigation data is not an object or is a function');
runHelper({navigation: [{label: 1, url: 'bar'}]}).should.throwError('Invalid value, Url and Label must be strings');
runHelper({navigation: [{label: 'foo', url: 1}]}).should.throwError('Invalid value, Url and Label must be strings');
});
it('can render empty nav', function () {
var navigation = {navigation:[]},
rendered = helpers.navigation.call(navigation);
should.exist(rendered);
rendered.string.should.be.equal('');
});
it('can render one item', function () {
var singleItem = {label: 'Foo', url: '/foo'},
navigation = {navigation: [singleItem]},
rendered = helpers.navigation.call(navigation),
testUrl = 'href="' + utils.config.url + '/foo"';
should.exist(rendered);
rendered.string.should.containEql('li');
rendered.string.should.containEql('nav-foo');
rendered.string.should.containEql(testUrl);
});
it('can render multiple items', function () {
var firstItem = {label: 'Foo', url: '/foo'},
secondItem = {label: 'Bar Baz Qux', url: '/qux'},
navigation = {navigation: [firstItem, secondItem]},
rendered = helpers.navigation.call(navigation),
testUrl = 'href="' + utils.config.url + '/foo"',
testUrl2 = 'href="' + utils.config.url + '/qux"';
should.exist(rendered);
rendered.string.should.containEql('nav-foo');
rendered.string.should.containEql('nav-bar-baz-qux');
rendered.string.should.containEql(testUrl);
rendered.string.should.containEql(testUrl2);
});
it('can annotate the current url', function () {
var firstItem = {label: 'Foo', url: '/foo'},
secondItem = {label: 'Bar', url: '/qux'},
navigation = {
relativeUrl: '/foo',
navigation: [firstItem, secondItem]
},
rendered = helpers.navigation.call(navigation);
should.exist(rendered);
rendered.string.should.containEql('nav-foo');
rendered.string.should.containEql('nav-current');
rendered.string.should.containEql('nav-foo nav-current');
rendered.string.should.containEql('nav-bar"');
});
});