2
1
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2023-12-13 21:00:40 +01:00

Merge pull request #1577 from halfdan/527-subdir-admin

Fix Admin interface with sub directories
This commit is contained in:
Hannah Wolfe 2013-11-28 05:14:23 -08:00
commit 7b2bf5b98c
23 changed files with 151 additions and 99 deletions

View file

@ -47,7 +47,7 @@
.attr({'src': '', "width": 'auto', "height": 'auto'});
$progress.animate({"opacity": 0}, 250, function () {
$dropzone.find('span.media').after('<img class="fileupload-loading" src="/ghost/img/loadingcat.gif" />');
$dropzone.find('span.media').after('<img class="fileupload-loading" src="' + Ghost.paths.ghostRoot + '/ghost/img/loadingcat.gif" />');
if (!settings.editor) {$progress.find('.fileupload-loading').css({"top": "56px"}); }
});
$dropzone.trigger("uploadsuccess", [result]);
@ -62,7 +62,7 @@
var self = this;
$dropzone.find('.js-fileupload').fileupload().fileupload("option", {
url: '/ghost/upload/',
url: Ghost.paths.ghostRoot + '/ghost/upload/',
headers: {
'X-CSRF-Token': $("meta[name='csrf-param']").attr('content')
},

View file

@ -2,6 +2,16 @@
(function () {
'use strict';
function ghostPaths() {
var path = window.location.pathname,
root = path.substr(0, path.search('/ghost/'));
return {
ghostRoot: root,
apiRoot: root + '/ghost/api/v0.1'
};
}
var Ghost = {
Layout : {},
Views : {},
@ -9,9 +19,7 @@
Models : {},
Validate : new Validator(),
settings: {
apiRoot: '/ghost/api/v0.1'
},
paths: ghostPaths(),
// This is a helper object to denote legacy things in the
// middle of being transitioned.
@ -46,7 +54,7 @@
Backbone.history.start({
pushState: true,
hashChange: false,
root: '/ghost'
root: Ghost.paths.ghostRoot + '/ghost'
});
};

View file

@ -50,7 +50,7 @@
nextPage: 0,
prevPage: 0,
url: Ghost.settings.apiRoot + '/posts/',
url: Ghost.paths.apiRoot + '/posts/',
model: Ghost.Models.Post,
parse: function (resp) {

View file

@ -3,7 +3,7 @@
'use strict';
//id:0 is used to issue PUT requests
Ghost.Models.Settings = Ghost.ProgressModel.extend({
url: Ghost.settings.apiRoot + '/settings/?type=blog,theme',
url: Ghost.paths.apiRoot + '/settings/?type=blog,theme',
id: '0',
parse: function (resp) {
resp.permalinks = resp.permalinks === "/:slug/" ? "" : "1";

View file

@ -3,6 +3,6 @@
'use strict';
Ghost.Collections.Tags = Ghost.ProgressCollection.extend({
url: Ghost.settings.apiRoot + '/tags/'
url: Ghost.paths.apiRoot + '/tags/'
});
}());

View file

@ -3,7 +3,7 @@
'use strict';
Ghost.Models.Themes = Backbone.Model.extend({
url: Ghost.settings.apiRoot + '/themes'
url: Ghost.paths.apiRoot + '/themes'
});
}());
}());

View file

@ -3,11 +3,11 @@
'use strict';
Ghost.Models.User = Ghost.ProgressModel.extend({
url: Ghost.settings.apiRoot + '/users/me/'
url: Ghost.paths.apiRoot + '/users/me/'
});
// Ghost.Collections.Users = Backbone.Collection.extend({
// url: Ghost.settings.apiRoot + '/users/'
// url: Ghost.paths.apiRoot + '/users/'
// });
}());

View file

@ -36,8 +36,8 @@
});
Ghost.Collections.Widgets = Ghost.ProgressCollection.extend({
// url: Ghost.settings.apiRoot + '/widgets/', // What will this be?
// url: Ghost.paths.apiRoot + '/widgets/', // What will this be?
model: Ghost.Models.Widget
});
}());
}());

View file

@ -60,7 +60,7 @@
editor: function (id) {
var post = new Ghost.Models.Post();
post.urlRoot = Ghost.settings.apiRoot + '/posts';
post.urlRoot = Ghost.paths.apiRoot + '/posts';
if (id) {
post.id = id;
post.fetch({ data: {status: 'all'}}).then(function () {

View file

@ -205,7 +205,7 @@
headers: {
'X-CSRF-Token': $("meta[name='csrf-param']").attr('content')
},
url: Ghost.settings.apiRoot + '/notifications/' + $(self).find('.close').data('id')
url: Ghost.paths.apiRoot + '/notifications/' + $(self).find('.close').data('id')
}).done(function (result) {
/*jslint unparam:true*/
bbSelf.$el.slideUp(250, function () {
@ -239,7 +239,7 @@
headers: {
'X-CSRF-Token': $("meta[name='csrf-param']").attr('content')
},
url: Ghost.settings.apiRoot + '/notifications/' + $(self).data('id')
url: Ghost.paths.apiRoot + '/notifications/' + $(self).data('id')
}).done(function (result) {
/*jslint unparam:true*/
var height = bbSelf.$('.js-notification').outerHeight(true),

View file

@ -214,7 +214,7 @@
e.preventDefault();
// for now this will disable "open in new tab", but when we have a Router implemented
// it can go back to being a normal link to '#/ghost/editor/X'
window.location = '/ghost/editor/' + this.model.get('id') + '/';
window.location = Ghost.paths.ghostRoot + '/ghost/editor/' + this.model.get('id') + '/';
},
toggleFeatured: function (e) {

View file

@ -35,7 +35,7 @@
Ghost.Validate.handleErrors();
} else {
$.ajax({
url: '/ghost/signin/',
url: Ghost.paths.ghostRoot + '/ghost/signin/',
type: 'POST',
headers: {
'X-CSRF-Token': $("meta[name='csrf-param']").attr('content')
@ -100,7 +100,7 @@
Ghost.Validate.handleErrors();
} else {
$.ajax({
url: '/ghost/signup/',
url: Ghost.paths.ghostRoot + '/ghost/signup/',
type: 'POST',
headers: {
'X-CSRF-Token': $("meta[name='csrf-param']").attr('content')
@ -157,7 +157,7 @@
Ghost.Validate.handleErrors();
} else {
$.ajax({
url: '/ghost/forgotten/',
url: Ghost.paths.ghostRoot + '/ghost/forgotten/',
type: 'POST',
headers: {
'X-CSRF-Token': $("meta[name='csrf-param']").attr('content')
@ -224,7 +224,7 @@
this.$('input, button').prop('disabled', true);
$.ajax({
url: '/ghost/reset/' + this.token + '/',
url: Ghost.paths.ghostRoot + '/ghost/reset/' + this.token + '/',
type: 'POST',
headers: {
'X-CSRF-Token': $("meta[name='csrf-param']").attr('content')

View file

@ -221,7 +221,7 @@
}).then(function () {
// Redirect to content screen if deleting post from editor.
if (window.location.pathname.indexOf('editor') > -1) {
window.location = '/ghost/content/';
window.location = Ghost.paths.ghostRoot + '/ghost/content/';
}
Ghost.notifications.addItem({
type: 'success',

View file

@ -73,7 +73,8 @@ adminControllers = {
});
},
'auth': function (req, res) {
var currentTime = process.hrtime()[0],
var root = ghost.blogGlobals().path === '/' ? '' : ghost.blogGlobals().path,
currentTime = process.hrtime()[0],
denied = '';
loginSecurity = _.filter(loginSecurity, function (ipTime) {
return (ipTime.time + 2 > currentTime);
@ -88,8 +89,12 @@ adminControllers = {
req.session.regenerate(function (err) {
if (!err) {
req.session.user = user.id;
res.json(200, {redirect: req.body.redirect ? '/ghost/'
+ decodeURIComponent(req.body.redirect) : '/ghost/'});
var redirect = root + '/ghost/';
if (req.body.redirect) {
redirect += decodeURIComponent(req.body.redirect);
}
res.json(200, {redirect: redirect});
}
});
}, function (error) {
@ -120,7 +125,8 @@ adminControllers = {
});
},
'doRegister': function (req, res) {
var name = req.body.name,
var root = ghost.blogGlobals().path === '/' ? '' : ghost.blogGlobals().path,
name = req.body.name,
email = req.body.email,
password = req.body.password;
@ -135,7 +141,7 @@ adminControllers = {
if (req.session.user === undefined) {
req.session.user = user.id;
}
res.json(200, {redirect: '/ghost/'});
res.json(200, {redirect: root + '/ghost/'});
}
});
});
@ -152,7 +158,8 @@ adminControllers = {
});
},
'generateResetToken': function (req, res) {
var email = req.body.email;
var root = ghost.blogGlobals().path === '/' ? '' : ghost.blogGlobals().path,
email = req.body.email;
api.users.generateResetToken(email).then(function (token) {
var siteLink = '<a href="' + config().url + '">' + config().url + '</a>',
@ -177,7 +184,7 @@ adminControllers = {
};
return api.notifications.add(notification).then(function () {
res.json(200, {redirect: '/ghost/signin/'});
res.json(200, {redirect: root + '/ghost/signin/'});
});
}, function failure(error) {
@ -191,8 +198,9 @@ adminControllers = {
});
},
'reset': function (req, res) {
// Validate the request token
var token = req.params.token;
var root = ghost.blogGlobals().path === '/' ? '' : ghost.blogGlobals().path,
// Validate the request token
token = req.params.token;
api.users.validateToken(token).then(function () {
// Render the reset form
@ -213,12 +221,13 @@ adminControllers = {
errors.logError(err, 'admin.js', "Please check the provided token for validity and expiration.");
return api.notifications.add(notification).then(function () {
res.redirect('/ghost/forgotten');
res.redirect(root + '/ghost/forgotten');
});
});
},
'resetPassword': function (req, res) {
var token = req.params.token,
var root = ghost.blogGlobals().path === '/' ? '' : ghost.blogGlobals().path,
token = req.params.token,
newPassword = req.param('newpassword'),
ne2Password = req.param('ne2password');
@ -231,7 +240,7 @@ adminControllers = {
};
return api.notifications.add(notification).then(function () {
res.json(200, {redirect: '/ghost/signin/'});
res.json(200, {redirect: root + '/ghost/signin/'});
});
}).otherwise(function (err) {
// TODO: Better error message if we can tell whether the passwords didn't match or something
@ -240,15 +249,17 @@ adminControllers = {
},
'logout': function (req, res) {
req.session.destroy();
var notification = {
type: 'success',
message: 'You were successfully signed out',
status: 'passive',
id: 'successlogout'
};
var root = ghost.blogGlobals().path === '/' ? '' : ghost.blogGlobals().path,
notification = {
type: 'success',
message: 'You were successfully signed out',
status: 'passive',
id: 'successlogout'
};
return api.notifications.add(notification).then(function () {
res.redirect('/ghost/signin/');
res.redirect(root + '/ghost/signin/');
});
},
'index': function (req, res) {

View file

@ -19,21 +19,22 @@ var Ghost = require('../../ghost'),
frontendControllers = {
'homepage': function (req, res, next) {
// Parse the page number
var pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
var root = ghost.blogGlobals().path === '/' ? '' : ghost.blogGlobals().path,
// Parse the page number
pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
postsPerPage = parseInt(ghost.settings('postsPerPage'), 10),
options = {};
// No negative pages
if (isNaN(pageParam) || pageParam < 1) {
//redirect to 404 page?
return res.redirect('/');
return res.redirect(root + '/');
}
options.page = pageParam;
// Redirect '/page/1/' to '/' for all teh good SEO
if (pageParam === 1 && req.route.path === '/page/:page/') {
return res.redirect('/');
return res.redirect(root + '/');
}
// No negative posts per page, must be number
@ -52,7 +53,7 @@ frontendControllers = {
// If page is greater than number of pages we have, redirect to last page
if (pageParam > maxPage) {
return res.redirect(maxPage === 1 ? '/' : ('/page/' + maxPage + '/'));
return res.redirect(maxPage === 1 ? root + '/' : (root + '/page/' + maxPage + '/'));
}
// Render the page of posts
@ -89,6 +90,7 @@ frontendControllers = {
'rss': function (req, res, next) {
// Initialize RSS
var siteUrl = config().url,
root = ghost.blogGlobals().path === '/' ? '' : ghost.blogGlobals().path,
pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
feed;
//needs refact for multi user to not use first user as default
@ -105,11 +107,11 @@ frontendControllers = {
// No negative pages
if (isNaN(pageParam) || pageParam < 1) {
return res.redirect('/rss/');
return res.redirect(root + '/rss/');
}
if (pageParam === 1 && req.route.path === '/rss/:page/') {
return res.redirect('/rss/');
if (pageParam === 1 && req.route.path === root + '/rss/:page/') {
return res.redirect(root + '/rss/');
}
api.posts.browse({page: pageParam}).then(function (page) {
@ -123,7 +125,7 @@ frontendControllers = {
// If page is greater than number of pages we have, redirect to last page
if (pageParam > maxPage) {
return res.redirect('/rss/' + maxPage + '/');
return res.redirect(root + '/rss/' + maxPage + '/');
}
ghost.doFilter('prePostsRender', page.posts).then(function (posts) {

View file

@ -113,6 +113,35 @@ coreHelpers.url = function (options) {
return output;
};
// ### Asset helper
//
// *Usage example:*
// `{{asset src="css/screen.css"}}`
// `{{asset src="css/screen.css" ghost="true"}}`
//
// Returns the path to the specified asset. The ghost
// flag outputs the asset path for the Ghost admin
coreHelpers.asset = function (context, options) {
var output = '',
subDir = coreHelpers.ghost.blogGlobals().path,
isAdmin = options && options.hash && options.hash.ghost;
if (subDir === '/') {
output += '/';
} else {
output += subDir + '/';
}
if (isAdmin) {
output += 'ghost/';
} else {
output += 'assets/';
}
output += context;
return new hbs.handlebars.SafeString(output);
};
// ### Author Helper
//
// *Usage example:*
@ -541,47 +570,49 @@ registerHelpers = function (ghost, config) {
// And expose config
coreHelpers.config = config;
ghost.registerThemeHelper('date', coreHelpers.date);
ghost.registerThemeHelper('encode', coreHelpers.encode);
ghost.registerThemeHelper('pageUrl', coreHelpers.pageUrl);
ghost.registerThemeHelper('url', coreHelpers.url);
ghost.registerThemeHelper('asset', coreHelpers.asset);
ghost.registerThemeHelper('author', coreHelpers.author);
ghost.registerThemeHelper('tags', coreHelpers.tags);
ghost.registerThemeHelper('content', coreHelpers.content);
ghost.registerThemeHelper('date', coreHelpers.date);
ghost.registerThemeHelper('e', coreHelpers.e);
ghost.registerThemeHelper('encode', coreHelpers.encode);
ghost.registerThemeHelper('excerpt', coreHelpers.excerpt);
ghost.registerThemeHelper('fileStorage', coreHelpers.fileStorage);
ghost.registerThemeHelper('ghostScriptTags', coreHelpers.ghostScriptTags);
ghost.registerThemeHelper('e', coreHelpers.e);
ghost.registerThemeHelper('json', coreHelpers.json);
ghost.registerThemeHelper('foreach', coreHelpers.foreach);
ghost.registerThemeHelper('helperMissing', coreHelpers.helperMissing);
ghost.registerThemeHelper('ghostScriptTags', coreHelpers.ghostScriptTags);
ghost.registerThemeHelper('has_tag', coreHelpers.has_tag);
ghost.registerThemeHelper('helperMissing', coreHelpers.helperMissing);
ghost.registerThemeHelper('json', coreHelpers.json);
ghost.registerThemeHelper('pageUrl', coreHelpers.pageUrl);
ghost.registerThemeHelper('tags', coreHelpers.tags);
ghost.registerThemeHelper('url', coreHelpers.url);
ghost.registerAsyncThemeHelper('body_class', coreHelpers.body_class);
ghost.registerAsyncThemeHelper('post_class', coreHelpers.post_class);
ghost.registerAsyncThemeHelper('meta_title', coreHelpers.meta_title);
ghost.registerAsyncThemeHelper('meta_description', coreHelpers.meta_description);
ghost.registerAsyncThemeHelper('ghost_foot', coreHelpers.ghost_foot);
ghost.registerAsyncThemeHelper('ghost_head', coreHelpers.ghost_head);
ghost.registerAsyncThemeHelper('ghost_foot', coreHelpers.ghost_foot);
ghost.registerAsyncThemeHelper('meta_description', coreHelpers.meta_description);
ghost.registerAsyncThemeHelper('meta_title', coreHelpers.meta_title);
ghost.registerAsyncThemeHelper('post_class', coreHelpers.post_class);
paginationHelper = ghost.loadTemplate('pagination').then(function (templateFn) {
coreHelpers.paginationTemplate = templateFn;

View file

@ -187,7 +187,7 @@ module.exports = function (server) {
server.use(express.session({
store: new BSStore(ghost.dataProvider),
secret: ghost.dbHash,
cookie: { path: '/ghost', maxAge: 12 * 60 * 60 * 1000 }
cookie: { path: root + '/ghost', maxAge: 12 * 60 * 60 * 1000 }
}));
//enable express csrf protection

View file

@ -5,7 +5,7 @@
<section class="content-filter">
<small>All Posts</small>
</section>
<a href="/ghost/editor/" class="button button-add" title="New Post"><span class="hidden">New Post</span></a>
<a href="{{url}}/ghost/editor/" class="button button-add" title="New Post"><span class="hidden">New Post</span></a>
</header>
<section class="content-list-content">
<ol></ol>
@ -14,4 +14,4 @@
<section class="content-preview js-content-preview">
</section>
</section>
</section>

View file

@ -20,12 +20,12 @@
<fieldset>
<div class="form-group">
<label>Export</label>
<a href="/ghost/api/v0.1/db/" class="button-save">Export</a>
<a href="{{url}}/ghost/api/v0.1/db/" class="button-save">Export</a>
<p>Export the blog settings and data.</p>
</div>
</fieldset>
</form>
<form id="settings-import" method="post" action="/ghost/api/v0.1/db/" enctype="multipart/form-data">
<form id="settings-import" method="post" action="{{url}}/ghost/api/v0.1/db/" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="{{csrfToken}}" />
<fieldset>
<div class="form-group">
@ -38,4 +38,4 @@
</form>
</section>
</section>
</div>
</div>

View file

@ -15,20 +15,20 @@
<meta http-equiv="cleartype" content="on">
<meta name="apple-mobile-web-app-capable" content="yes" />
<link rel="shortcut icon" href="/favicon.ico">
<link rel="apple-touch-icon-precomposed" href="/ghost/img/touch-icon-iphone.png" />
<link rel="apple-touch-icon-precomposed" sizes="76x76" href="/ghost/img/touch-icon-ipad.png" />
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="/ghost/img/small.png" />
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="/ghost/img/medium.png" />
<link rel="shortcut icon" href="{{asset "favicon.ico"}}">
<link rel="apple-touch-icon-precomposed" href="{{asset "img/touch-icon-iphone.png" ghost="true"}}" />
<link rel="apple-touch-icon-precomposed" sizes="76x76" href="{{asset "img/touch-icon-ipad.png" ghost="true"}}" />
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="{{asset "img/small.png" ghost="true"}}" />
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="{{asset "img/medium.png" ghost="true"}}" />
<meta name="application-name" content="Ghost"/>
<meta name="msapplication-TileColor" content="#ffffff"/>
<meta name="msapplication-square70x70logo" content="/ghost/img/small.png"/>
<meta name="msapplication-square150x150logo" content="/ghost/img/medium.png"/>
<meta name="msapplication-square310x310logo" content="/ghost/img/large.png"/>
<meta name="msapplication-square70x70logo" content="{{asset "img/small.png" ghost="true"}}" />
<meta name="msapplication-square150x150logo" content="{{asset "img/medium.png" ghost="true"}}" />
<meta name="msapplication-square310x310logo" content="{{asset "img/large.png" ghost="true"}}" />
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans:400,300,700">
<link rel="stylesheet" href="/ghost/css/screen.css">
<link rel="stylesheet" href="{{asset "css/screen.css" ghost="true"}}">
{{{block "pageStyles"}}}
</head>
<body class="{{bodyClass}}">

View file

@ -2,7 +2,7 @@
<section class="error-content error-404 js-error-container">
<section class="error-details">
<figure class="error-image">
<img class="error-ghost" src="/ghost/img/404-ghost@2x.png" srcset="/ghost/img/404-ghost.png 1x, /ghost/img/404-ghost@2x.png 2x"/>
<img class="error-ghost" src="{{asset "img/404-ghost@2x.png" ghost="true"}} srcset="{{asset "img/404-ghost.png" ghost="true"}} 1x, {{asset "img/404-ghost@2x.png" ghost="true"}} 2x"/>
</figure>
<section class="error-message">
<h1 class="error-code">{{code}}</h1>
@ -24,4 +24,4 @@
{{/foreach}}
</ul>
</section>
{{/if}}
{{/if}}

View file

@ -3,20 +3,20 @@
<nav id="global-nav" role="navigation">
<ul id="main-menu" >
{{#each adminNav}}
<li class="{{navClass}}{{#if selected}} active{{/if}}"><a href="/ghost{{path}}">{{name}}</a></li>
<li class="{{navClass}}{{#if selected}} active{{/if}}"><a href="{{url}}/ghost{{path}}">{{name}}</a></li>
{{/each}}
<li id="usermenu" class="subnav">
<a href="#" data-toggle="ul" class="dropdown">
<img class="avatar" src="{{#if currentUser.image}}{{currentUser.image}}{{else}}/shared/img/user-image.png{{/if}}" alt="Avatar" />
<img class="avatar" src="{{#if currentUser.image}}{{currentUser.image}}{{else}}{{asset "shared/img/user-image.png"}}{{/if}}" alt="Avatar" />
<span class="name">{{#if currentUser.name}}{{currentUser.name}}{{else}}{{currentUser.email}}{{/if}}</span>
</a>
<ul class="overlay">
<li class="usermenu-profile"><a href="/ghost/settings/user/">Your Profile</a></li>
<li class="usermenu-profile"><a href="{{url}}/ghost/settings/user/">Your Profile</a></li>
<li class="divider"></li>
<li class="usermenu-help"><a href="http://ghost.org/forum/">Help / Support</a></li>
<li class="divider"></li>
<li class="usermenu-signout"><a href="/ghost/signout/">Sign Out</a></li>
<li class="usermenu-signout"><a href="{{url}}/ghost/signout/">Sign Out</a></li>
</ul>
</li>
</ul>

View file

@ -17,7 +17,7 @@
<meta http-equiv="cleartype" content="on">
<link rel="stylesheet" type='text/css' href='//fonts.googleapis.com/css?family=Open+Sans:400,300,700'>
<link rel="stylesheet" href="/ghost/css/screen.css">
<link rel="stylesheet" href="{{asset "css/screen.css" ghost="true"}}">
{{{block "pageStyles"}}}
</head>
<body class="{{bodyClass}}">
@ -27,8 +27,8 @@
<figure class="error-image">
<img
class="error-ghost"
src="/ghost/img/404-ghost@2x.png"
srcset="/ghost/img/404-ghost.png 1x, /ghost/img/404-ghost@2x.png 2x"/>
src="{{asset "img/404-ghost@2x.png" ghost="true"}}"
srcset="{{asset "img/404-ghost.png" ghost="true"}} 1x, {{asset "img/404-ghost@2x.png" ghost="true"}} 2x"/>
</figure>
<section class="error-message">
<h1 class="error-code">{{code}}</h1>
@ -53,4 +53,4 @@
{{/if}}
</main>
</body>
</html>
</html>