diff --git a/app.js b/app.js index f800a84777..624af48139 100755 --- a/app.js +++ b/app.js @@ -6,12 +6,15 @@ // Module dependencies. var express = require('express'), + when = require('when'), + 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 @@ -66,53 +69,54 @@ next(); }; - helpers.loadCoreHelpers(ghost); + console.log("Loading filters and helpers..."); + when.all([filters.loadCoreFilters(ghost), helpers.loadCoreHelpers(ghost)]).then(function () { + /** + * 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)); - /** - * 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 + */ - /** - * 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); - 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', frontend.single); + ghost.app().get('/', frontend.homepage); - /** - * Frontend routes.. - * @todo dynamic routing, homepage generator, filters ETC ETC - */ - ghost.app().get('/:slug', frontend.single); - ghost.app().get('/', frontend.homepage); + ghost.app().listen(3333, function () { + console.log("Express server listening on port " + 3333); + }); - - ghost.app().listen(3333, function () { - console.log("Express server listening on port " + 3333); - }); + }, errors.logAndThrowError); }()); \ No newline at end of file diff --git a/config.js b/config.js index 5e6813f00a..e67a032fd9 100644 --- a/config.js +++ b/config.js @@ -76,6 +76,17 @@ production: {} }; + /** + * @property {Array} nav + */ + config.nav = [{ + title: 'Home', + url: '/' + }, { + title: 'Admin', + url: '/ghost' + }]; + /** * @property {Object} exports */ diff --git a/core/frontend/controllers/index.js b/core/frontend/controllers/index.js index cf42783778..43485e42cb 100644 --- a/core/frontend/controllers/index.js +++ b/core/frontend/controllers/index.js @@ -15,15 +15,20 @@ frontendControllers = { '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()}); + // TODO: Make this more promisey or something + ghost.doFilter('ghostNavItems', {path: req.path, navItems: []}, function(navData) { + ghost.doFilter('prePostsRender', posts.toJSON(), function (posts) { + res.render('index', {posts: posts, ghostGlobals: ghost.globals(), navItems: navData.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()}); + ghost.doFilter('ghostNavItems', {path: req.path, navItems: []}, function(navData) { + ghost.doFilter('prePostsRender', post.toJSON(), function (post) { + res.render('single', {post: post, ghostGlobals: ghost.globals(), navItems: navData.navItems}); + }); }); }); } diff --git a/core/frontend/filters/index.js b/core/frontend/filters/index.js new file mode 100644 index 0000000000..e9da261371 --- /dev/null +++ b/core/frontend/filters/index.js @@ -0,0 +1,39 @@ +(function () { + "use strict"; + + var _ = require('underscore'), + when = require('when'), + coreFilters; + + coreFilters = function (ghost) { + var defer = when.defer(); + + 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; + }); + + setTimeout(function() { + defer.resolve(); + }, 30); + + return defer.promise; + }; + + module.exports.loadCoreFilters = coreFilters; + +}()); \ No newline at end of file diff --git a/core/frontend/helpers/ghostNav.js b/core/frontend/helpers/ghostNav.js new file mode 100644 index 0000000000..f1ae8bd9a7 --- /dev/null +++ b/core/frontend/helpers/ghostNav.js @@ -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; +}()); \ No newline at end of file diff --git a/core/frontend/helpers/index.js b/core/frontend/helpers/index.js index b0565110f9..51c2e5e1da 100644 --- a/core/frontend/helpers/index.js +++ b/core/frontend/helpers/index.js @@ -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) + ]); }; diff --git a/core/frontend/views/nav.hbs b/core/frontend/views/nav.hbs new file mode 100644 index 0000000000..72b01d6977 --- /dev/null +++ b/core/frontend/views/nav.hbs @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/core/ghost.js b/core/ghost.js index a38c99f061..f5c5dc6adf 100644 --- a/core/ghost.js +++ b/core/ghost.js @@ -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); }; diff --git a/core/shared/errorHandling.js b/core/shared/errorHandling.js index f61dcaafc0..af678453ae 100644 --- a/core/shared/errorHandling.js +++ b/core/shared/errorHandling.js @@ -10,7 +10,7 @@ errors = { throwError: function (err) { if (!err) { - return; + err = new Error("An error occurred"); } if (_.isString(err)) { diff --git a/core/test/ghost/api_posts_spec.js b/core/test/ghost/api_posts_spec.js index d19f4377d0..36f9084a30 100644 --- a/core/test/ghost/api_posts_spec.js +++ b/core/test/ghost/api_posts_spec.js @@ -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 () { diff --git a/core/test/ghost/errorHandling_spec.js b/core/test/ghost/errorHandling_spec.js index 816739e106..b4dc03b5f3 100644 --- a/core/test/ghost/errorHandling_spec.js +++ b/core/test/ghost/errorHandling_spec.js @@ -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"); diff --git a/core/test/ghost/frontend_helpers_ghostNav_spec.js b/core/test/ghost/frontend_helpers_ghostNav_spec.js new file mode 100644 index 0000000000..bbf7177ec2 --- /dev/null +++ b/core/test/ghost/frontend_helpers_ghostNav_spec.js @@ -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); + }); + }); +}()); \ No newline at end of file