Add ghostNav helper and filter for themes; closes #47

This commit is contained in:
Jacob Gable 2013-05-27 14:27:41 -05:00
parent f4368a2744
commit c699121049
12 changed files with 265 additions and 51 deletions

113
app.js
View File

@ -6,17 +6,23 @@
// Module dependencies.
var express = require('express'),
when = require('when'),
_ = require('underscore'),
errors = require('./core/shared/errorHandling'),
admin = require('./core/admin/controllers'),
frontend = require('./core/frontend/controllers'),
api = require('./core/shared/api'),
flash = require('connect-flash'),
Ghost = require('./core/ghost'),
I18n = require('./core/lang/i18n'),
filters = require('./core/frontend/filters'),
helpers = require('./core/frontend/helpers'),
// ## Variables
auth,
authAPI,
ghostLocals,
loading = when.defer(),
/**
* Create new Ghost object
@ -66,53 +72,76 @@
next();
};
helpers.loadCoreHelpers(ghost);
/**
* API routes..
* @todo auth should be public auth not user auth
* Expose the standard locals that every external page should have available;
* path, navItems and ghostGlobals
*/
ghost.app().get('/api/v0.1/posts', authAPI, api.requestHandler(api.posts.browse));
ghost.app().post('/api/v0.1/posts', authAPI, api.requestHandler(api.posts.add));
ghost.app().get('/api/v0.1/posts/:id', authAPI, api.requestHandler(api.posts.read));
ghost.app().put('/api/v0.1/posts/:id', authAPI, api.requestHandler(api.posts.edit));
ghost.app().del('/api/v0.1/posts/:id', authAPI, api.requestHandler(api.posts.destroy));
ghost.app().get('/api/v0.1/settings', authAPI, api.requestHandler(api.settings.browse));
ghost.app().get('/api/v0.1/settings/:key', authAPI, api.requestHandler(api.settings.read));
ghost.app().put('/api/v0.1/settings', authAPI, api.requestHandler(api.settings.edit));
ghostLocals = function(req, res, next) {
ghost.doFilter('ghostNavItems', {path: req.path, navItems: []}, function(navData) {
// Make sure we have a locals value.
res.locals = res.locals || {};
/**
* Admin routes..
* @todo put these somewhere in admin
*/
// Extend it with nav data and ghostGlobals
_.extend(res.locals, navData, {
ghostGlobals: ghost.globals()
});
ghost.app().get(/^\/logout\/?$/, admin.logout);
ghost.app().get('/ghost/login/', admin.login);
ghost.app().get('/ghost/register/', admin.register);
ghost.app().post('/ghost/login/', admin.auth);
ghost.app().post('/ghost/register/', admin.doRegister);
ghost.app().get('/ghost/editor/:id', auth, admin.editor);
ghost.app().get('/ghost/editor', auth, admin.editor);
ghost.app().get('/ghost/blog', auth, admin.blog);
ghost.app().get('/ghost/settings', auth, admin.settings);
ghost.app().get('/ghost/debug', auth, admin.debug.index);
ghost.app().get('/ghost/debug/db/delete/', auth, admin.debug.dbdelete);
ghost.app().get('/ghost/debug/db/populate/', auth, admin.debug.dbpopulate);
ghost.app().get(/^\/(ghost$|(ghost-admin|admin|wp-admin|dashboard|login)\/?)/, auth, function (req, res) {
res.redirect('/ghost/');
});
ghost.app().get('/ghost/', auth, admin.index);
next();
});
};
/**
* Frontend routes..
* @todo dynamic routing, homepage generator, filters ETC ETC
*/
ghost.app().get('/:slug', frontend.single);
ghost.app().get('/', frontend.homepage);
// Expose the promise we will resolve after our pre-loading
ghost.loaded = loading.promise;
when.all([filters.loadCoreFilters(ghost), helpers.loadCoreHelpers(ghost)]).then(function () {
ghost.app().listen(3333, function () {
console.log("Express server listening on port " + 3333);
});
/**
* API routes..
* @todo auth should be public auth not user auth
*/
ghost.app().get('/api/v0.1/posts', authAPI, api.requestHandler(api.posts.browse));
ghost.app().post('/api/v0.1/posts', authAPI, api.requestHandler(api.posts.add));
ghost.app().get('/api/v0.1/posts/:id', authAPI, api.requestHandler(api.posts.read));
ghost.app().put('/api/v0.1/posts/:id', authAPI, api.requestHandler(api.posts.edit));
ghost.app().del('/api/v0.1/posts/:id', authAPI, api.requestHandler(api.posts.destroy));
ghost.app().get('/api/v0.1/settings', authAPI, api.requestHandler(api.settings.browse));
ghost.app().get('/api/v0.1/settings/:key', authAPI, api.requestHandler(api.settings.read));
ghost.app().put('/api/v0.1/settings', authAPI, api.requestHandler(api.settings.edit));
/**
* Admin routes..
* @todo put these somewhere in admin
*/
ghost.app().get(/^\/logout\/?$/, admin.logout);
ghost.app().get('/ghost/login/', admin.login);
ghost.app().get('/ghost/register/', admin.register);
ghost.app().post('/ghost/login/', admin.auth);
ghost.app().post('/ghost/register/', admin.doRegister);
ghost.app().get('/ghost/editor/:id', auth, admin.editor);
ghost.app().get('/ghost/editor', auth, admin.editor);
ghost.app().get('/ghost/blog', auth, admin.blog);
ghost.app().get('/ghost/settings', auth, admin.settings);
ghost.app().get('/ghost/debug', auth, admin.debug.index);
ghost.app().get('/ghost/debug/db/delete/', auth, admin.debug.dbdelete);
ghost.app().get('/ghost/debug/db/populate/', auth, admin.debug.dbpopulate);
ghost.app().get(/^\/(ghost$|(ghost-admin|admin|wp-admin|dashboard|login)\/?)/, auth, function (req, res) {
res.redirect('/ghost/');
});
ghost.app().get('/ghost/', auth, admin.index);
/**
* Frontend routes..
* @todo dynamic routing, homepage generator, filters ETC ETC
*/
ghost.app().get('/:slug', ghostLocals, frontend.single);
ghost.app().get('/', ghostLocals, frontend.homepage);
ghost.app().listen(3333, function () {
console.log("Express server listening on port " + 3333);
// Let everyone know we have finished loading
loading.resolve();
});
}, errors.logAndThrowError);
}());

View File

@ -76,6 +76,17 @@
production: {}
};
/**
* @property {Array} nav
*/
config.nav = [{
title: 'Home',
url: '/'
}, {
title: 'Admin',
url: '/ghost'
}];
/**
* @property {Object} exports
*/

View File

@ -16,14 +16,15 @@
'homepage': function (req, res) {
api.posts.browse().then(function (posts) {
ghost.doFilter('prePostsRender', posts.toJSON(), function (posts) {
res.render('index', {posts: posts, ghostGlobals: ghost.globals()});
res.render('index', {posts: posts, ghostGlobals: res.locals.ghostGlobals, navItems: res.locals.navItems});
});
});
},
'single': function (req, res) {
api.posts.read({'slug': req.params.slug}).then(function (post) {
ghost.doFilter('prePostsRender', post.toJSON(), function (post) {
res.render('single', {post: post, ghostGlobals: ghost.globals()});
res.render('single', {post: post, ghostGlobals: res.locals.ghostGlobals, navItems: res.locals.navItems});
});
});
}

View File

@ -0,0 +1,30 @@
(function () {
"use strict";
var _ = require('underscore'),
coreFilters;
coreFilters = function (ghost) {
ghost.registerFilter('ghostNavItems', function (args) {
var selectedItem;
// Set the nav items based on the config
args.navItems = ghost.config().nav;
// Mark the current selected Item
selectedItem = _.find(args.navItems, function (item) {
// TODO: Better selection determination?
return item.url === args.path;
});
if (selectedItem) {
selectedItem.active = true;
}
return args;
});
};
module.exports.loadCoreFilters = coreFilters;
}());

View File

@ -0,0 +1,53 @@
(function () {
"use strict";
var fs = require('fs'),
path = require('path'),
_ = require('underscore'),
handlebars = require('express-hbs').handlebars,
nodefn = require('when/node/function'),
GhostNavHelper;
GhostNavHelper = function (navTemplate) {
// Bind the context for our methods.
_.bindAll(this, 'compileTemplate', 'renderNavItems');
if (_.isFunction(navTemplate)) {
this.navTemplateFunc = navTemplate;
} else {
this.navTemplatePath = navTemplate;
}
};
GhostNavHelper.prototype.compileTemplate = function (templatePath) {
var self = this;
// Allow people to overwrite the navTemplatePath
templatePath = templatePath || this.navTemplatePath;
return nodefn.call(fs.readFile, templatePath).then(function(navTemplateContents) {
// TODO: Can handlebars compile async?
self.navTemplateFunc = handlebars.compile(navTemplateContents.toString());
});
};
GhostNavHelper.prototype.renderNavItems = function (navItems) {
var output;
output = this.navTemplateFunc({links: navItems});
return output;
};
// A static helper method for registering with ghost
GhostNavHelper.registerWithGhost = function(ghost) {
var templatePath = path.join(ghost.paths().frontendViews, 'nav.hbs'),
ghostNavHelper = new GhostNavHelper(templatePath);
return ghostNavHelper.compileTemplate().then(function() {
ghost.registerThemeHelper("ghostNav", ghostNavHelper.renderNavItems);
});
};
module.exports = GhostNavHelper;
}());

View File

@ -3,10 +3,11 @@
var _ = require('underscore'),
moment = require('moment'),
when = require('when'),
navHelper = require('./ghostNav'),
coreHelpers;
coreHelpers = function (ghost) {
/**
* [ description]
* @todo ghost core helpers + a way for themes to register them
@ -39,6 +40,10 @@
return output;
});
return when.all([
// Just one async helper for now, but could be more in the future
navHelper.registerWithGhost(ghost)
]);
};

View File

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

View File

@ -72,9 +72,10 @@
polyglot: function () { return polyglot; },
paths: function () {
return {
'activeTheme': __dirname + '/../content/' + config.themeDir + '/' + config.activeTheme + '/',
'adminViews': __dirname + '/admin/views/',
'lang': __dirname + '/lang/'
'activeTheme': __dirname + '/../content/' + config.themeDir + '/' + config.activeTheme + '/',
'adminViews': __dirname + '/admin/views/',
'frontendViews': __dirname + '/frontend/views/',
'lang': __dirname + '/lang/'
};
}
});
@ -139,6 +140,7 @@
}
}
}
callback(args);
};

View File

@ -10,7 +10,7 @@
errors = {
throwError: function (err) {
if (!err) {
return;
err = new Error("An error occurred");
}
if (_.isString(err)) {

View File

@ -6,8 +6,7 @@
var _ = require("underscore"),
should = require('should'),
helpers = require('./helpers'),
PostProvider = require('../../shared/models/dataProvider.bookshelf.posts'),
Bookshelf = require('bookshelf');
PostProvider = require('../../shared/models/dataProvider.bookshelf.posts');
describe('Bookshelf PostsProvider', function () {

View File

@ -31,6 +31,14 @@
runThrowError.should['throw']("test2");
});
it("throws error even if nothing passed", function () {
var runThrowError = function () {
errors.throwError();
};
runThrowError.should['throw']("An error occurred");
});
it("logs errors", function () {
var err = new Error("test1"),
logStub = sinon.stub(console, "log");

View File

@ -0,0 +1,69 @@
/*globals describe, beforeEach, it*/
(function () {
"use strict";
var should = require('should'),
sinon = require('sinon'),
_ = require('underscore'),
path = require('path'),
GhostNavHelper = require('../../frontend/helpers/ghostNav');
describe('ghostNav Helper', function () {
var navTemplatePath = path.join(process.cwd(), 'core/frontend/views/nav.hbs');
should.exist(GhostNavHelper, "GhostNavHelper exists");
it('can compile the nav template', function (done) {
var helper = new GhostNavHelper(navTemplatePath);
helper.compileTemplate().then(function () {
should.exist(helper.navTemplateFunc);
_.isFunction(helper.navTemplateFunc).should.equal(true);
done();
}, done);
});
it('can render nav items', function () {
var helper = new GhostNavHelper(function (data) { return "rendered " + data.links.length; }),
templateSpy = sinon.spy(helper, 'navTemplateFunc'),
fakeNavItems = [{
title: 'test1',
url: '/test1'
}, {
title: 'test2',
url: '/test2'
}],
rendered;
rendered = helper.renderNavItems(fakeNavItems);
// Returns a string returned from navTemplateFunc
should.exist(rendered);
rendered.should.equal("rendered 2");
templateSpy.calledWith({ links: fakeNavItems }).should.equal(true);
});
it('can register with ghost', function (done) {
var fakeGhost = {
paths: function () {
return {
frontendViews: path.join(process.cwd(), 'core/frontend/views/')
};
},
registerThemeHelper: function () {
return;
}
},
registerStub = sinon.stub(fakeGhost, 'registerThemeHelper');
GhostNavHelper.registerWithGhost(fakeGhost).then(function () {
registerStub.called.should.equal(true);
done();
}, done);
});
});
}());