mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Merge pull request #6860 from kirrg001/timezones-support-permalinks
Timezones + support permalinks
This commit is contained in:
commit
4c44257115
12 changed files with 142 additions and 96 deletions
|
@ -60,7 +60,8 @@ updateConfigCache = function () {
|
|||
postsPerPage: (settingsCache.postsPerPage && settingsCache.postsPerPage.value) || 5,
|
||||
permalinks: (settingsCache.permalinks && settingsCache.permalinks.value) || '/:slug/',
|
||||
twitter: (settingsCache.twitter && settingsCache.twitter.value) || '',
|
||||
facebook: (settingsCache.facebook && settingsCache.facebook.value) || ''
|
||||
facebook: (settingsCache.facebook && settingsCache.facebook.value) || '',
|
||||
timezone: (settingsCache.activeTimezone && settingsCache.activeTimezone.value) || 'Europe/Dublin'
|
||||
},
|
||||
labs: labsValue
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Contains all path information to be used throughout
|
||||
// the codebase.
|
||||
|
||||
var moment = require('moment'),
|
||||
var moment = require('moment-timezone'),
|
||||
_ = require('lodash'),
|
||||
ghostConfig = '',
|
||||
// @TODO: unify this with routes.apiBaseUrl
|
||||
|
@ -95,18 +95,20 @@ function createUrl(urlPath, absolute, secure) {
|
|||
return urlJoin(base, urlPath);
|
||||
}
|
||||
|
||||
// ## urlPathForPost
|
||||
// Always sync
|
||||
// Creates the url path for a post, given a post and a permalink
|
||||
// Parameters:
|
||||
// - post - a json object representing a post
|
||||
/**
|
||||
* creates the url path for a post based on blog timezone and permalink pattern
|
||||
*
|
||||
* @param {JSON} post
|
||||
* @returns {string}
|
||||
*/
|
||||
function urlPathForPost(post) {
|
||||
var output = '',
|
||||
permalinks = ghostConfig.theme.permalinks,
|
||||
publishedAtMoment = moment.tz(post.published_at, ghostConfig.theme.timezone),
|
||||
tags = {
|
||||
year: function () { return moment(post.published_at).format('YYYY'); },
|
||||
month: function () { return moment(post.published_at).format('MM'); },
|
||||
day: function () { return moment(post.published_at).format('DD'); },
|
||||
year: function () { return publishedAtMoment.format('YYYY'); },
|
||||
month: function () { return publishedAtMoment.format('MM'); },
|
||||
day: function () { return publishedAtMoment.format('DD'); },
|
||||
author: function () { return post.author.slug; },
|
||||
slug: function () { return post.slug; },
|
||||
id: function () { return post.id; }
|
||||
|
|
|
@ -66,11 +66,21 @@ frontendControllers = {
|
|||
return next();
|
||||
}
|
||||
|
||||
// If we're ready to render the page but the last param is 'edit' then we'll send you to the edit page.
|
||||
// CASE: we only support /:slug format for pages
|
||||
if (post.page && post.url !== req.path) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// CASE: last param is of url is /edit, redirect to admin
|
||||
if (lookup.isEditURL) {
|
||||
return res.redirect(config.paths.subdir + '/ghost/editor/' + post.id + '/');
|
||||
}
|
||||
|
||||
// CASE: permalink is not valid anymore, we redirect him permanently to the correct one
|
||||
if (post.url !== req.path) {
|
||||
return res.redirect(301, post.url);
|
||||
}
|
||||
|
||||
setRequestIsSecure(req, post);
|
||||
|
||||
filters.doFilter('prePostsRender', post, res.locals)
|
||||
|
|
|
@ -16,28 +16,32 @@ function postLookup(postUrl) {
|
|||
postPermalink = config.theme.permalinks,
|
||||
pagePermalink = '/:slug/',
|
||||
isEditURL = false,
|
||||
matchFunc,
|
||||
matchFuncPost,
|
||||
matchFuncPage,
|
||||
postParams,
|
||||
pageParams,
|
||||
params;
|
||||
|
||||
// Convert saved permalink into a path-match function
|
||||
matchFunc = routeMatch(getEditFormat(postPermalink));
|
||||
params = matchFunc(postPath);
|
||||
matchFuncPost = routeMatch(getEditFormat(postPermalink));
|
||||
postParams = matchFuncPost(postPath);
|
||||
|
||||
// Check if the path matches the permalink structure.
|
||||
// If there are no matches found, test to see if this is a page instead
|
||||
if (params === false) {
|
||||
matchFunc = routeMatch(getEditFormat(pagePermalink));
|
||||
params = matchFunc(postPath);
|
||||
if (postParams === false) {
|
||||
matchFuncPage = routeMatch(getEditFormat(pagePermalink));
|
||||
pageParams = matchFuncPage(postPath);
|
||||
}
|
||||
|
||||
// If there are still no matches then return empty.
|
||||
if (params === false) {
|
||||
if (pageParams === false) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
params = postParams || pageParams;
|
||||
|
||||
// If params contains edit, and it is equal to 'edit' this is an edit URL
|
||||
if (params.edit && params.edit.toLowerCase() === 'edit') {
|
||||
postPath = postPath.replace(params.edit + '/', '');
|
||||
isEditURL = true;
|
||||
} else if (params.edit !== undefined) {
|
||||
// Unknown string in URL, return empty
|
||||
|
@ -53,9 +57,12 @@ function postLookup(postUrl) {
|
|||
return api.posts.read(params).then(function then(result) {
|
||||
var post = result.posts[0];
|
||||
|
||||
// If there is no post, or the post has no URL, or it isn't a match for our original lookup, return empty
|
||||
// This also catches the case where we use the pagePermalink but the post is not a page
|
||||
if (!post || !post.url || post.url !== postPath) {
|
||||
if (!post) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// CASE: we originally couldn't match the post based on date permalink and we tried to check if its a page
|
||||
if (!post.page && !postParams) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,12 @@
|
|||
"isLength": [1, 1000]
|
||||
}
|
||||
},
|
||||
"activeTimezone": {
|
||||
"defaultValue": "Europe/Dublin",
|
||||
"validations": {
|
||||
"isNull": false
|
||||
}
|
||||
},
|
||||
"forceI18n": {
|
||||
"defaultValue": "true",
|
||||
"validations": {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"timezones": [
|
||||
{
|
||||
"name": "Pacific/Samoa",
|
||||
"name": "Pacific/Pago_Pago",
|
||||
"label": "(GMT -11:00) Midway Island, Samoa",
|
||||
"offset": -660
|
||||
},
|
||||
|
@ -11,27 +11,27 @@
|
|||
"offset": -600
|
||||
},
|
||||
{
|
||||
"name": "US/Alaska",
|
||||
"name": "America/Anchorage",
|
||||
"label": "(GMT -9:00) Alaska",
|
||||
"offset": -540
|
||||
},
|
||||
{
|
||||
"name": "Mexico/BajaNorte",
|
||||
"name": "America/Tijuana",
|
||||
"label": "(GMT -8:00) Chihuahua, La Paz, Mazatlan",
|
||||
"offset": -480
|
||||
},
|
||||
{
|
||||
"name": "US/Pacific",
|
||||
"name": "America/Los_Angeles",
|
||||
"label": "(GMT -8:00) Pacific Time (US & Canada); Tijuana",
|
||||
"offset": -480
|
||||
},
|
||||
{
|
||||
"name": "US/Arizona",
|
||||
"name": "America/Phoenix",
|
||||
"label": "(GMT -7:00) Arizona",
|
||||
"offset": -420
|
||||
},
|
||||
{
|
||||
"name": "US/Mountain",
|
||||
"name": "America/Denver",
|
||||
"label": "(GMT -7:00) Mountain Time (US & Canada)",
|
||||
"offset": -420
|
||||
},
|
||||
|
@ -41,17 +41,17 @@
|
|||
"offset": -360
|
||||
},
|
||||
{
|
||||
"name": "US/Central",
|
||||
"name": "America/Chicago",
|
||||
"label": "(GMT -6:00) Central Time (US & Canada)",
|
||||
"offset": -360
|
||||
},
|
||||
{
|
||||
"name": "Mexico/General",
|
||||
"name": "America/Mexico_City",
|
||||
"label": "(GMT -6:00) Guadalajara, Mexico City, Monterrey",
|
||||
"offset": -360
|
||||
},
|
||||
{
|
||||
"name": "Canada/Saskatchewan",
|
||||
"name": "America/Regina",
|
||||
"label": "(GMT -6:00) Saskatchewan",
|
||||
"offset": -360
|
||||
},
|
||||
|
@ -61,12 +61,12 @@
|
|||
"offset": -300
|
||||
},
|
||||
{
|
||||
"name": "US/Eastern",
|
||||
"name": "America/New_York",
|
||||
"label": "(GMT -5:00) Eastern Time (US & Canada)",
|
||||
"offset": -300
|
||||
},
|
||||
{
|
||||
"name": "US/East-Indiana",
|
||||
"name": "America/Fort_Wayne",
|
||||
"label": "(GMT -5:00) Indiana (East)",
|
||||
"offset": -300
|
||||
},
|
||||
|
@ -76,17 +76,17 @@
|
|||
"offset": -270
|
||||
},
|
||||
{
|
||||
"name": "Canada/Atlantic",
|
||||
"name": "America/Halifax",
|
||||
"label": "(GMT -4:00) Atlantic Time (Canada); Brasilia, Greenland",
|
||||
"offset": -240
|
||||
},
|
||||
{
|
||||
"name": "Canada/Newfoundland",
|
||||
"name": "America/St_Johns",
|
||||
"label": "(GMT -3:30) Newfoundland",
|
||||
"offset": -210
|
||||
},
|
||||
{
|
||||
"name": "America/Buenos_Aires",
|
||||
"name": "America/Argentina/Buenos_Aires",
|
||||
"label": "(GMT -3:00) Buenos Aires, Georgetown",
|
||||
"offset": -180
|
||||
},
|
||||
|
@ -141,7 +141,7 @@
|
|||
"offset": 60
|
||||
},
|
||||
{
|
||||
"name": "Africa/Bangui",
|
||||
"name": "Africa/Lagos",
|
||||
"label": "(GMT +1:00) West Central Africa",
|
||||
"offset": 60
|
||||
},
|
||||
|
@ -156,7 +156,7 @@
|
|||
"offset": 120
|
||||
},
|
||||
{
|
||||
"name": "Africa/Harare",
|
||||
"name": "Africa/Maputo",
|
||||
"label": "(GMT +2:00) Harare",
|
||||
"offset": 120
|
||||
},
|
||||
|
@ -191,7 +191,7 @@
|
|||
"offset": 210
|
||||
},
|
||||
{
|
||||
"name": "Asia/Muscat",
|
||||
"name": "Asia/Dubai",
|
||||
"label": "(GMT +4:00) Abu Dhabi, Muscat",
|
||||
"offset": 240
|
||||
},
|
||||
|
@ -216,12 +216,12 @@
|
|||
"offset": 300
|
||||
},
|
||||
{
|
||||
"name": "Asia/Calcutta",
|
||||
"name": "Asia/Kolkata",
|
||||
"label": "(GMT +5:30) Chennai, Calcutta, Mumbai, New Delhi",
|
||||
"offset": 330
|
||||
},
|
||||
{
|
||||
"name": "Asia/Katmandu",
|
||||
"name": "Asia/Kathmandu",
|
||||
"label": "(GMT +5:45) Katmandu",
|
||||
"offset": 345
|
||||
},
|
||||
|
@ -301,7 +301,7 @@
|
|||
"offset": 630
|
||||
},
|
||||
{
|
||||
"name": "Australia/Canberra",
|
||||
"name": "Australia/Sydney",
|
||||
"label": "(GMT +11:00) Canberra, Hobart, Melbourne, Sydney, Vladivostok",
|
||||
"offset": 660
|
||||
},
|
||||
|
|
|
@ -1,34 +1,38 @@
|
|||
// # Date Helper
|
||||
// Usage: `{{date format="DD MM, YYYY"}}`, `{{date updated_at format="DD MM, YYYY"}}`
|
||||
//
|
||||
// Formats a date using moment.js. Formats published_at by default but will also take a date as a parameter
|
||||
// Formats a date using moment-timezone.js. Formats published_at by default but will also take a date as a parameter
|
||||
|
||||
var moment = require('moment'),
|
||||
date;
|
||||
var moment = require('moment-timezone'),
|
||||
date,
|
||||
timezone;
|
||||
|
||||
date = function (date, options) {
|
||||
if (!options && date.hasOwnProperty('hash')) {
|
||||
options = date;
|
||||
date = undefined;
|
||||
timezone = options.data.blog.timezone;
|
||||
|
||||
// set to published_at by default, if it's available
|
||||
// otherwise, this will print the current date
|
||||
if (this.published_at) {
|
||||
date = this.published_at;
|
||||
date = moment(this.published_at).tz(timezone).format();
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that context is undefined, not null, as that can cause errors
|
||||
date = date === null ? undefined : date;
|
||||
|
||||
var f = options.hash.format || 'MMM Do, YYYY',
|
||||
timeago = options.hash.timeago;
|
||||
var f = options.hash.format || 'MMM DD, YYYY',
|
||||
timeago = options.hash.timeago,
|
||||
timeNow = moment().tz(timezone);
|
||||
|
||||
if (timeago) {
|
||||
date = moment(date).fromNow();
|
||||
date = timezone ? moment(date).tz(timezone).from(timeNow) : moment(date).fromNow();
|
||||
} else {
|
||||
date = moment(date).format(f);
|
||||
date = timezone ? moment(date).tz(timezone).format(f) : moment(date).format(f);
|
||||
}
|
||||
|
||||
return date;
|
||||
};
|
||||
|
||||
|
|
|
@ -516,7 +516,6 @@ describe('Frontend Routing', function () {
|
|||
});
|
||||
|
||||
it('should load a post with date permalink', function (done) {
|
||||
// get today's date
|
||||
var date = moment().format('YYYY/MM/DD');
|
||||
|
||||
request.get('/' + date + '/welcome-to-ghost/')
|
||||
|
@ -525,6 +524,20 @@ describe('Frontend Routing', function () {
|
|||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('expect redirect because of wrong/old permalink prefix', function (done) {
|
||||
var date = moment().format('YYYY/MM/DD');
|
||||
|
||||
request.get('/2016/04/01/welcome-to-ghost/')
|
||||
.expect('Content-Type', /html/)
|
||||
.end(function (err, res) {
|
||||
res.status.should.eql(301);
|
||||
request.get('/' + date + '/welcome-to-ghost/')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /html/)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
});
|
||||
|
||||
it('should serve RSS with date permalink', function (done) {
|
||||
request.get('/rss/')
|
||||
.expect('Content-Type', 'text/xml; charset=utf-8')
|
||||
|
|
|
@ -384,8 +384,8 @@ describe('Config', function () {
|
|||
});
|
||||
|
||||
describe('urlPathForPost', function () {
|
||||
it('should output correct url for post', function () {
|
||||
configUtils.set({theme: {permalinks: '/:slug/'}});
|
||||
it('permalink is /:slug/', function () {
|
||||
configUtils.set({theme: {permalinks: '/:slug/', timezone: 'Europe/Dublin'}});
|
||||
|
||||
var testData = testUtils.DataGenerator.Content.posts[2],
|
||||
postLink = '/short-and-sweet/';
|
||||
|
@ -393,20 +393,17 @@ describe('Config', function () {
|
|||
config.urlPathForPost(testData).should.equal(postLink);
|
||||
});
|
||||
|
||||
it('should output correct url for post with date permalink', function () {
|
||||
configUtils.set({theme: {permalinks: '/:year/:month/:day/:slug/'}});
|
||||
it('permalink is /:year/:month/:day/:slug, blog timezone is Los Angeles', function () {
|
||||
configUtils.set({theme: {permalinks: '/:year/:month/:day/:slug/', timezone: 'America/Los_Angeles'}});
|
||||
var testData = testUtils.DataGenerator.Content.posts[2],
|
||||
today = testData.published_at,
|
||||
dd = ('0' + today.getDate()).slice(-2),
|
||||
mm = ('0' + (today.getMonth() + 1)).slice(-2),
|
||||
yyyy = today.getFullYear(),
|
||||
postLink = '/' + yyyy + '/' + mm + '/' + dd + '/short-and-sweet/';
|
||||
postLink = '/2016/05/17/short-and-sweet/';
|
||||
|
||||
testData.published_at = new Date('2016-05-18T06:30:00.000Z');
|
||||
config.urlPathForPost(testData).should.equal(postLink);
|
||||
});
|
||||
|
||||
it('should output correct url for page with date permalink', function () {
|
||||
configUtils.set({theme: {permalinks: '/:year/:month/:day/:slug/'}});
|
||||
it('post is page, no permalink usage allowed at all', function () {
|
||||
configUtils.set({theme: {permalinks: '/:year/:month/:day/:slug/', timezone: 'America/Los_Angeles'}});
|
||||
|
||||
var testData = testUtils.DataGenerator.Content.posts[5],
|
||||
postLink = '/static-page-test/';
|
||||
|
@ -414,16 +411,23 @@ describe('Config', function () {
|
|||
config.urlPathForPost(testData).should.equal(postLink);
|
||||
});
|
||||
|
||||
it('should output correct url for post with complex permalink', function () {
|
||||
configUtils.set({theme: {permalinks: '/:year/:id/:author/'}});
|
||||
it('permalink is /:year/:id:/:author', function () {
|
||||
configUtils.set({theme: {permalinks: '/:year/:id/:author/', timezone: 'America/Los_Angeles'}});
|
||||
|
||||
var testData = _.extend(
|
||||
{}, testUtils.DataGenerator.Content.posts[2], {id: 3}, {author: {slug: 'joe-bloggs'}}
|
||||
),
|
||||
today = testData.published_at,
|
||||
yyyy = today.getFullYear(),
|
||||
postLink = '/' + yyyy + '/3/joe-bloggs/';
|
||||
var testData = _.merge(testUtils.DataGenerator.Content.posts[2], {id: 3}, {author: {slug: 'joe-blog'}}),
|
||||
postLink = '/2015/3/joe-blog/';
|
||||
|
||||
testData.published_at = new Date('2016-01-01T00:00:00.000Z');
|
||||
config.urlPathForPost(testData).should.equal(postLink);
|
||||
});
|
||||
|
||||
it('permalink is /:year/:id:/:author', function () {
|
||||
configUtils.set({theme: {permalinks: '/:year/:id/:author/', timezone: 'Europe/Berlin'}});
|
||||
|
||||
var testData = _.merge(testUtils.DataGenerator.Content.posts[2], {id: 3}, {author: {slug: 'joe-blog'}}),
|
||||
postLink = '/2016/3/joe-blog/';
|
||||
|
||||
testData.published_at = new Date('2016-01-01T00:00:00.000Z');
|
||||
config.urlPathForPost(testData).should.equal(postLink);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -426,16 +426,6 @@ describe('Frontend Controller', function () {
|
|||
frontend.single(req, res, failTest(done));
|
||||
});
|
||||
|
||||
it('will NOT render post via /YYYY/MM/DD/:slug/ with non-matching date in url', function (done) {
|
||||
var date = moment(mockPosts[1].published_at).subtract(1, 'days').format('YYYY/MM/DD');
|
||||
req.path = '/' + [date, mockPosts[1].posts[0].slug].join('/') + '/';
|
||||
|
||||
frontend.single(req, res, function () {
|
||||
res.render.called.should.be.false();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('will NOT render post via /:slug/', function (done) {
|
||||
req.path = '/' + mockPosts[1].posts[0].slug + '/';
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ var should = require('should'),
|
|||
// Stuff we are testing
|
||||
handlebars = hbs.handlebars,
|
||||
helpers = require('../../../server/helpers'),
|
||||
moment = require('moment');
|
||||
moment = require('moment-timezone');
|
||||
|
||||
describe('{{date}} helper', function () {
|
||||
before(function () {
|
||||
|
@ -17,22 +17,23 @@ describe('{{date}} helper', function () {
|
|||
should.exist(handlebars.helpers.date);
|
||||
});
|
||||
|
||||
// TODO: When timezone support is added these tests should be updated
|
||||
// to test the output of the helper against static strings instead
|
||||
// of calling moment(). Without timezone support the output of this
|
||||
// helper may differ depending on what timezone the tests are run in.
|
||||
|
||||
it('creates properly formatted date strings', function () {
|
||||
var testDates = [
|
||||
'2013-12-31T11:28:58.593Z',
|
||||
'2014-01-01T01:28:58.593Z',
|
||||
'2014-02-20T01:28:58.593Z',
|
||||
'2014-03-01T01:28:58.593Z'
|
||||
'2013-12-31T11:28:58.593+02:00',
|
||||
'2014-01-01T01:28:58.593+11:00',
|
||||
'2014-02-20T01:28:58.593-04:00',
|
||||
'2014-03-01T01:28:58.593+00:00'
|
||||
],
|
||||
timezones = 'Europe/Dublin',
|
||||
format = 'MMM Do, YYYY',
|
||||
context = {
|
||||
hash: {
|
||||
format: format
|
||||
},
|
||||
data: {
|
||||
blog: {
|
||||
timezone: 'Europe/Dublin'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -40,20 +41,27 @@ describe('{{date}} helper', function () {
|
|||
var rendered = helpers.date.call({published_at: d}, context);
|
||||
|
||||
should.exist(rendered);
|
||||
rendered.should.equal(moment(d).format(format));
|
||||
rendered.should.equal(moment(d).tz(timezones).format(format));
|
||||
});
|
||||
});
|
||||
|
||||
it('creates properly formatted time ago date strings', function () {
|
||||
var testDates = [
|
||||
'2013-12-31T23:58:58.593Z',
|
||||
'2014-01-01T00:28:58.593Z',
|
||||
'2014-11-20T01:28:58.593Z',
|
||||
'2014-03-01T01:28:58.593Z'
|
||||
'2013-12-31T23:58:58.593+02:00',
|
||||
'2014-01-01T00:28:58.593+11:00',
|
||||
'2014-11-20T01:28:58.593-04:00',
|
||||
'2014-03-01T01:28:58.593+00:00'
|
||||
],
|
||||
timezones = 'Europe/Dublin',
|
||||
timeNow = moment().tz('Europe/Dublin'),
|
||||
context = {
|
||||
hash: {
|
||||
timeago: true
|
||||
},
|
||||
data: {
|
||||
blog: {
|
||||
timezone: 'Europe/Dublin'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -61,7 +69,7 @@ describe('{{date}} helper', function () {
|
|||
var rendered = helpers.date.call({published_at: d}, context);
|
||||
|
||||
should.exist(rendered);
|
||||
rendered.should.equal(moment(d).fromNow());
|
||||
rendered.should.equal(moment(d).tz(timezones).from(timeNow));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"morgan": "1.7.0",
|
||||
"multer": "1.1.0",
|
||||
"netjet": "1.1.0",
|
||||
"moment-timezone": "0.5.1",
|
||||
"node-uuid": "1.4.7",
|
||||
"nodemailer": "0.7.1",
|
||||
"oauth2orize": "1.3.0",
|
||||
|
|
Loading…
Add table
Reference in a new issue