Merge pull request #1523 from halfdan/527-subdir-support

This commit is contained in:
Hannah Wolfe 2013-11-24 21:20:24 +00:00
commit 727b845254
9 changed files with 95 additions and 60 deletions

View File

@ -11,6 +11,7 @@ var config = require('../config'),
hbs = require('express-hbs'),
nodefn = require('when/node/function'),
_ = require('underscore'),
url = require('url'),
Polyglot = require('node-polyglot'),
Mailer = require('./server/mail'),
models = require('./server/models'),
@ -88,10 +89,18 @@ Ghost = function () {
},
dataProvider: models,
blogGlobals: function () {
var localPath = url.parse(instance.config().url).path;
// Remove trailing slash
if (localPath !== '/') {
localPath = localPath.replace(/\/$/, '');
}
/* this is a bit of a hack until we have a better way to combine settings and config
* this data is what becomes globally available to themes */
return {
url: instance.config().url,
url: instance.config().url.replace(/\/$/, ''),
path: localPath,
title: instance.settings('title'),
description: instance.settings('description'),
logo: instance.settings('logo'),

View File

@ -41,6 +41,9 @@ function setup(server) {
// set the view engine
server.set('view engine', 'hbs');
// set the configured URL
server.set('ghost root', ghost.blogGlobals().path);
// return the correct mime type for woff filess
express['static'].mime.define({'application/font-woff': ['woff']});
@ -163,4 +166,4 @@ function init(app) {
setup(app);
}
module.exports = init;
module.exports = init;

View File

@ -1,13 +1,14 @@
var _ = require('underscore'),
moment = require('moment'),
downsize = require('downsize'),
path = require('path'),
when = require('when'),
hbs = require('express-hbs'),
errors = require('../errorHandling'),
models = require('../models'),
packageInfo = require('../../../package.json'),
version = packageInfo.version,
scriptTemplate = _.template("<script src='/built/scripts/<%= name %>?v=<%= version %>'></script>"),
scriptTemplate = _.template("<script src='<%= source %>?v=<%= version %>'></script>"),
isProduction = process.env.NODE_ENV === 'production',
coreHelpers = {},
registerHelpers;
@ -88,10 +89,16 @@ coreHelpers.url = function (options) {
day: function () { return self.created_at.getDate(); },
slug: function () { return self.slug; },
id: function () { return self.id; }
};
},
blog = coreHelpers.ghost.blogGlobals(),
isAbsolute = options && options.hash.absolute;
if (options && options.hash.absolute) {
output += coreHelpers.ghost.config().url;
if (isAbsolute) {
output += blog.url;
}
if (blog.path !== '/') {
output += blog.path;
}
if (models.isPost(this)) {
@ -219,7 +226,8 @@ coreHelpers.fileStorage = function (context, options) {
};
coreHelpers.ghostScriptTags = function () {
var scriptFiles = [];
var scriptFiles = [],
blog = coreHelpers.ghost.blogGlobals();
if (isProduction) {
scriptFiles.push("ghost.min.js");
@ -235,7 +243,7 @@ coreHelpers.ghostScriptTags = function () {
scriptFiles = _.map(scriptFiles, function (fileName) {
return scriptTemplate({
name: fileName,
source: path.join(blog.path, '/built/scripts/', fileName),
version: version
});
});
@ -253,9 +261,9 @@ coreHelpers.body_class = function (options) {
tags = this.post && this.post.tags ? this.post.tags : this.tags || [],
page = this.post && this.post.page ? this.post.page : this.page || false;
if (_.isString(this.path) && this.path.match(/\/page/)) {
if (_.isString(this.ghostRoot) && this.ghostRoot.match(/\/page/)) {
classes.push('archive-template');
} else if (!this.path || this.path === '/' || this.path === '') {
} else if (!this.ghostRoot || this.ghostRoot === '/' || this.ghostRoot === '') {
classes.push('home-template');
} else {
classes.push('post-template');
@ -302,17 +310,18 @@ coreHelpers.post_class = function (options) {
coreHelpers.ghost_head = function (options) {
/*jslint unparam:true*/
var head = [],
var blog = coreHelpers.ghost.blogGlobals(),
head = [],
majorMinor = /^(\d+\.)?(\d+)/,
trimmedVersion = this.version,
blog = coreHelpers.ghost.blogGlobals();
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="' + _.escape(blog.title) + '" href="/rss/">');
if (this.path) {
head.push('<link rel="canonical" href="' + coreHelpers.ghost.config().url + this.path + '" />');
head.push('<link rel="alternate" type="application/rss+xml" title="' + _.escape(blog.title) + '" href="' + path.join(blog.path, '/rss/') + '">');
if (this.ghostRoot) {
head.push('<link rel="canonical" href="' + coreHelpers.ghost.blogGlobals().url + this.ghostRoot + '" />');
}
return coreHelpers.ghost.doFilter('ghost_head', head).then(function (head) {
@ -324,7 +333,7 @@ coreHelpers.ghost_head = function (options) {
coreHelpers.ghost_foot = function (options) {
/*jslint unparam:true*/
var foot = [];
foot.push('<script src="/shared/vendor/jquery/jquery.js"></script>');
foot.push('<script src="' + coreHelpers.ghost.blogGlobals().url + '/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; }, '');
@ -336,8 +345,8 @@ coreHelpers.meta_title = function (options) {
/*jslint unparam:true*/
var title,
blog;
if (_.isString(this.path)) {
if (!this.path || this.path === '/' || this.path === '' || this.path.match(/\/page/)) {
if (_.isString(this.ghostRoot)) {
if (!this.ghostRoot || this.ghostRoot === '/' || this.ghostRoot === '' || this.ghostRoot.match(/\/page/)) {
blog = coreHelpers.ghost.blogGlobals();
title = blog.title;
} else {
@ -356,8 +365,8 @@ coreHelpers.meta_description = function (options) {
var description,
blog;
if (_.isString(this.path)) {
if (!this.path || this.path === '/' || this.path === '' || this.path.match(/\/page/)) {
if (_.isString(this.ghostRoot)) {
if (!this.ghostRoot || this.ghostRoot === '/' || this.ghostRoot === '' || this.ghostRoot.match(/\/page/)) {
blog = coreHelpers.ghost.blogGlobals();
description = blog.description;
} else {

View File

@ -24,6 +24,7 @@ function ghostLocals(req, res, next) {
res.locals.version = packageInfo.version;
res.locals.path = req.path;
res.locals.csrfToken = req.csrfToken();
res.locals.ghostRoot = req.path.replace(ghost.blogGlobals().path, '');
if (res.isAdmin) {
api.users.read({id: req.session.user}).then(function (currentUser) {
@ -100,7 +101,12 @@ function activateTheme() {
// This is used to ensure the right content is served, and is not for security purposes
function manageAdminAndTheme(req, res, next) {
// TODO improve this regex
res.isAdmin = /(^\/ghost\/)/.test(req.url);
if (ghost.blogGlobals().path === '/') {
res.isAdmin = /(^\/ghost\/)/.test(req.url);
} else {
res.isAdmin = new RegExp("^\\" + ghost.blogGlobals().path + "\\/ghost\\/").test(req.url);
}
if (res.isAdmin) {
ghost.server.enable('admin');
ghost.server.disable(ghost.server.get('activeTheme'));
@ -127,6 +133,7 @@ function manageAdminAndTheme(req, res, next) {
module.exports = function (server) {
var oneYear = 31536000000,
root = ghost.blogGlobals().path === '/' ? '' : ghost.blogGlobals().path,
corePath = path.join(ghost.paths().appRoot, 'core');
// Logging configuration
@ -137,15 +144,15 @@ module.exports = function (server) {
}
// Favicon
server.use(express.favicon(corePath + '/shared/favicon.ico'));
server.use(root, express.favicon(corePath + '/shared/favicon.ico'));
// Shared static config
server.use('/shared', express['static'](path.join(corePath, '/shared')));
server.use(root + '/shared', express['static'](path.join(corePath, '/shared')));
server.use('/content/images', storage.get_storage().serve());
server.use(root + '/content/images', storage.get_storage().serve());
// Serve our built scripts; can't use /scripts here because themes already are
server.use('/built/scripts', express['static'](path.join(corePath, '/built/scripts'), {
server.use(root + '/built/scripts', express['static'](path.join(corePath, '/built/scripts'), {
// Put a maxAge of one year on built scripts
maxAge: oneYear
}));
@ -154,7 +161,7 @@ module.exports = function (server) {
server.use(manageAdminAndTheme);
// Admin only config
server.use('/ghost', middleware.whenEnabled('admin', express['static'](path.join(corePath, '/client/assets'))));
server.use(root + '/ghost', middleware.whenEnabled('admin', express['static'](path.join(corePath, '/client/assets'))));
// Theme only config
server.use(middleware.whenEnabled(server.get('activeTheme'), middleware.staticTheme(ghost)));
@ -164,9 +171,10 @@ module.exports = function (server) {
server.use(express.json());
server.use(express.urlencoded());
server.use('/ghost/upload/', express.multipart());
server.use('/ghost/upload/', express.multipart({uploadDir: corePath + '/content/images'}));
server.use('/ghost/api/v0.1/db/', express.multipart());
server.use(root + '/ghost/upload/', express.multipart());
server.use(root + '/ghost/upload/', express.multipart({uploadDir: __dirname + '/content/images'}));
server.use(root + '/ghost/api/v0.1/db/', express.multipart());
// Session handling
server.use(express.cookieParser());
@ -187,7 +195,7 @@ module.exports = function (server) {
server.use(initViews);
// process the application routes
server.use(server.router);
server.use(root, server.router);
// ### Error handling
// 404 Handler
@ -198,4 +206,4 @@ module.exports = function (server) {
};
// Export middleware functions directly
module.exports.middleware = middleware;
module.exports.middleware = middleware;

View File

@ -35,7 +35,7 @@ var middleware = {
}
redirect = '?r=' + encodeURIComponent(path);
}
return res.redirect('/ghost/signin/' + redirect);
return res.redirect(ghost.blogGlobals().url + '/ghost/signin/' + redirect);
}
next();
@ -57,7 +57,7 @@ var middleware = {
// Login and signup forms in particular
redirectToDashboard: function (req, res, next) {
if (req.session.user) {
return res.redirect('/ghost/');
return res.redirect(ghost.blogGlobals().url + '/ghost/');
}
next();

View File

@ -1,13 +1,18 @@
var admin = require('../controllers/admin'),
api = require('../api'),
middleware = require('../middleware').middleware;
middleware = require('../middleware').middleware,
Ghost = require('../../ghost'),
url = require('url'),
ghost = new Ghost();
// Redirect to signup if no users are currently created
function redirectToSignup(req, res, next) {
var root = ghost.server.get('ghost root').replace(/\/$/, '');
/*jslint unparam:true*/
api.users.browse().then(function (users) {
if (users.length === 0) {
return res.redirect('/ghost/signup/');
return res.redirect(root + '/ghost/signup/');
}
next();
}).otherwise(function (err) {
@ -16,16 +21,17 @@ function redirectToSignup(req, res, next) {
}
module.exports = function (server) {
var root = server.get('ghost root').replace(/\/$/, '');
// ### Admin routes
/* TODO: put these somewhere in admin */
server.get(/^\/logout\/?$/, function redirect(req, res) {
server.get(/logout/, function redirect(req, res) {
/*jslint unparam:true*/
res.redirect(301, '/signout/');
res.redirect(301, root + '/signout/');
});
server.get(/^\/signout\/?$/, admin.logout);
server.get(/signout/, admin.logout);
server.get('/ghost/login/', function redirect(req, res) {
/*jslint unparam:true*/
res.redirect(301, '/ghost/signin/');
res.redirect(301, root + '/ghost/signin/');
});
server.get('/ghost/signin/', redirectToSignup, middleware.redirectToDashboard, admin.login);
server.get('/ghost/signup/', middleware.redirectToDashboard, admin.signup);
@ -46,13 +52,13 @@ module.exports = function (server) {
server.post('/ghost/upload/', middleware.auth, admin.uploader);
// redirect to /ghost and let that do the authentication to prevent redirects to /ghost//admin etc.
server.get(/^\/((ghost-admin|admin|wp-admin|dashboard|signin)\/?)/, function (req, res) {
server.get(/\/((ghost-admin|admin|wp-admin|dashboard|signin)\/?)$/, function (req, res) {
/*jslint unparam:true*/
res.redirect('/ghost/');
res.redirect(root + '/ghost/');
});
server.get(/^\/(ghost$\/?)/, middleware.auth, function (req, res) {
server.get(/\/(ghost$\/?)/, middleware.auth, function (req, res) {
/*jslint unparam:true*/
res.redirect('/ghost/');
res.redirect(root + '/ghost/');
});
server.get('/ghost/', redirectToSignup, middleware.auth, admin.index);
};

File diff suppressed because one or more lines are too long

View File

@ -30,7 +30,7 @@ describe('Middleware', function () {
req.path = '';
middleware.auth(req, res, null);
assert(res.redirect.calledWith('/ghost/signin/'));
assert(res.redirect.calledWithMatch('/ghost/signin/'));
return done();
});
@ -40,7 +40,7 @@ describe('Middleware', function () {
req.path = '/ghost/' + path;
middleware.auth(req, res, null);
assert(res.redirect.calledWith('/ghost/signin/?r=' + encodeURIComponent(path)));
assert(res.redirect.calledWithMatch('/ghost/signin/?r=' + encodeURIComponent(path)));
return done();
});
@ -50,11 +50,11 @@ describe('Middleware', function () {
req.path = '/ghost/' + path;
middleware.auth(req, res, null);
assert(res.redirect.calledWith('/ghost/signin/?r=' + encodeURIComponent(path)));
assert(res.redirect.calledWithMatch('/ghost/signin/?r=' + encodeURIComponent(path)));
assert.equal(ghost.notifications.length, 1);
middleware.auth(req, res, null);
assert(res.redirect.calledWith('/ghost/signin/?r=' + encodeURIComponent(path)));
assert(res.redirect.calledWithMatch('/ghost/signin/?r=' + encodeURIComponent(path)));
assert.equal(ghost.notifications.length, 1);
return done();
@ -119,7 +119,7 @@ describe('Middleware', function () {
req.session.user = {};
middleware.redirectToDashboard(req, res, null);
assert(res.redirect.calledWith('/ghost/'));
assert(res.redirect.calledWithMatch('/ghost/'));
return done();
});

View File

@ -176,9 +176,9 @@ describe('Core Helpers', function () {
it('can render class string for context', function (done) {
when.all([
helpers.body_class.call({path: '/'}),
helpers.body_class.call({path: '/a-post-title'}),
helpers.body_class.call({path: '/page/4'})
helpers.body_class.call({ghostRoot: '/'}),
helpers.body_class.call({ghostRoot: '/a-post-title'}),
helpers.body_class.call({ghostRoot: '/page/4'})
]).then(function (rendered) {
rendered.length.should.equal(3);
@ -196,7 +196,7 @@ describe('Core Helpers', function () {
it('can render class for static page', function (done) {
helpers.body_class.call({
path: '/',
ghostRoot: '/',
post: {
page: true
}
@ -264,7 +264,7 @@ describe('Core Helpers', function () {
it('returns meta tag string', function (done) {
helpers.ghost_foot.call().then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<script src="/shared/vendor/jquery/jquery.js"></script>');
rendered.string.should.match(/<script src=".*\/shared\/vendor\/jquery\/jquery.js"><\/script>/);
done();
});
@ -510,7 +510,7 @@ describe('Core Helpers', function () {
});
it('can return blog title', function (done) {
helpers.meta_title.call({path: '/'}).then(function (rendered) {
helpers.meta_title.call({ghostRoot: '/'}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('Ghost');
@ -519,7 +519,7 @@ describe('Core Helpers', function () {
});
it('can return title of a post', function (done) {
var post = {path: '/nice-post', post: {title: 'Post Title'}};
var post = {ghostRoot: '/nice-post', post: {title: 'Post Title'}};
helpers.meta_title.call(post).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('Post Title');
@ -536,7 +536,7 @@ describe('Core Helpers', function () {
});
it('can return blog description', function () {
helpers.meta_description.call({path: '/'}).then(function (rendered) {
helpers.meta_description.call({ghostRoot: '/'}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('Just a blogging platform.');
@ -545,7 +545,7 @@ describe('Core Helpers', function () {
});
it('can return empty description on post', function (done) {
var post = {path: '/nice-post', post: {title: 'Post Title'}};
var post = {ghostRoot: '/nice-post', post: {title: 'Post Title'}};
helpers.meta_description.call(post).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('');