mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 06:35:49 +03:00
Merge pull request #84 from jgable/frontEndMenu
Add ghostNav helper and filter for themes; closes #47
This commit is contained in:
commit
4acf541fa6
113
app.js
113
app.js
@ -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);
|
||||
}());
|
11
config.js
11
config.js
@ -77,6 +77,17 @@
|
||||
production: {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @property {Array} nav
|
||||
*/
|
||||
config.nav = [{
|
||||
title: 'Home',
|
||||
url: '/'
|
||||
}, {
|
||||
title: 'Admin',
|
||||
url: '/ghost'
|
||||
}];
|
||||
|
||||
/**
|
||||
* @property {Object} exports
|
||||
*/
|
||||
|
@ -16,14 +16,14 @@
|
||||
'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});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
30
core/frontend/filters/index.js
Normal file
30
core/frontend/filters/index.js
Normal 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;
|
||||
|
||||
}());
|
53
core/frontend/helpers/ghostNav.js
Normal file
53
core/frontend/helpers/ghostNav.js
Normal 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;
|
||||
}());
|
@ -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)
|
||||
]);
|
||||
};
|
||||
|
||||
|
||||
|
7
core/frontend/views/nav.hbs
Normal file
7
core/frontend/views/nav.hbs
Normal 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>
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
errors = {
|
||||
throwError: function (err) {
|
||||
if (!err) {
|
||||
return;
|
||||
err = new Error("An error occurred");
|
||||
}
|
||||
|
||||
if (_.isString(err)) {
|
||||
|
@ -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");
|
||||
|
69
core/test/ghost/frontend_helpers_ghostNav_spec.js
Normal file
69
core/test/ghost/frontend_helpers_ghostNav_spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
}());
|
Loading…
Reference in New Issue
Block a user