mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
Merge pull request #3479 from ErisDS/issue-3079
Complete frontend multi-user features
This commit is contained in:
commit
c5fbe2def3
4 changed files with 280 additions and 18 deletions
|
@ -389,18 +389,34 @@ frontendControllers = {
|
|||
});
|
||||
},
|
||||
'rss': function (req, res, next) {
|
||||
function isPaginated() {
|
||||
return req.route.path.indexOf(':page') !== -1;
|
||||
}
|
||||
|
||||
function isTag() {
|
||||
return req.route.path.indexOf('/tag/') !== -1;
|
||||
}
|
||||
|
||||
function isAuthor() {
|
||||
return req.route.path.indexOf('/author/') !== -1;
|
||||
}
|
||||
|
||||
// Initialize RSS
|
||||
var pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
|
||||
tagParam = req.params.slug;
|
||||
slugParam = req.params.slug,
|
||||
baseUrl = config.paths.subdir;
|
||||
|
||||
if (isTag()) {
|
||||
baseUrl += '/tag/' + slugParam + '/rss/';
|
||||
} else if (isAuthor()) {
|
||||
baseUrl += '/author/' + slugParam + '/rss/';
|
||||
} else {
|
||||
baseUrl += '/rss/';
|
||||
}
|
||||
|
||||
// No negative pages, or page 1
|
||||
if (isNaN(pageParam) || pageParam < 1 ||
|
||||
(pageParam === 1 && (req.route.path === '/rss/:page/' || req.route.path === '/tag/:slug/rss/:page/'))) {
|
||||
if (tagParam !== undefined) {
|
||||
return res.redirect(config.paths.subdir + '/tag/' + tagParam + '/rss/');
|
||||
} else {
|
||||
return res.redirect(config.paths.subdir + '/rss/');
|
||||
}
|
||||
if (isNaN(pageParam) || pageParam < 1 || (pageParam === 1 && isPaginated())) {
|
||||
return res.redirect(baseUrl);
|
||||
}
|
||||
|
||||
return when.settle([
|
||||
|
@ -411,7 +427,8 @@ frontendControllers = {
|
|||
|
||||
var options = {};
|
||||
if (pageParam) { options.page = pageParam; }
|
||||
if (tagParam) { options.tag = tagParam; }
|
||||
if (isTag()) { options.tag = slugParam; }
|
||||
if (isAuthor()) { options.author = slugParam; }
|
||||
|
||||
options.include = 'author,tags,fields';
|
||||
|
||||
|
@ -430,13 +447,20 @@ frontendControllers = {
|
|||
|
||||
trimmedVersion = trimmedVersion ? trimmedVersion.match(majorMinor)[0] : '?';
|
||||
|
||||
if (tagParam) {
|
||||
if (isTag()) {
|
||||
if (page.meta.filters.tags) {
|
||||
title = page.meta.filters.tags[0].name + ' - ' + title;
|
||||
feedUrl = feedUrl + 'tag/' + page.meta.filters.tags[0].slug + '/';
|
||||
}
|
||||
}
|
||||
|
||||
if (isAuthor()) {
|
||||
if (page.meta.filters.author) {
|
||||
title = page.meta.filters.author.name + ' - ' + title;
|
||||
feedUrl = feedUrl + 'author/' + page.meta.filters.author.slug + '/';
|
||||
}
|
||||
}
|
||||
|
||||
feed = new RSS({
|
||||
title: title,
|
||||
description: description,
|
||||
|
@ -448,11 +472,7 @@ frontendControllers = {
|
|||
|
||||
// If page is greater than number of pages we have, redirect to last page
|
||||
if (pageParam > maxPage) {
|
||||
if (tagParam) {
|
||||
return res.redirect(config.paths.subdir + '/tag/' + tagParam + '/rss/' + maxPage + '/');
|
||||
} else {
|
||||
return res.redirect(config.paths.subdir + '/rss/' + maxPage + '/');
|
||||
}
|
||||
return res.redirect(baseUrl + maxPage + '/');
|
||||
}
|
||||
|
||||
setReqCtx(req, page.posts);
|
||||
|
|
|
@ -99,6 +99,10 @@ coreHelpers.page_url = function (context, block) {
|
|||
url += '/tag/' + this.tagSlug;
|
||||
}
|
||||
|
||||
if (this.authorSlug !== undefined) {
|
||||
url += '/author/' + this.authorSlug;
|
||||
}
|
||||
|
||||
if (context > 1) {
|
||||
url += '/page/' + context;
|
||||
}
|
||||
|
@ -644,10 +648,18 @@ coreHelpers.foreach = function (context, options) {
|
|||
|
||||
// ### Has Helper
|
||||
// `{{#has tag="video, music"}}`
|
||||
// `{{#has author="sam, pat"}}`
|
||||
// Checks whether a post has at least one of the tags
|
||||
coreHelpers.has = function (options) {
|
||||
options = options || {};
|
||||
options.hash = options.hash || {};
|
||||
|
||||
var tags = _.pluck(this.tags, 'name'),
|
||||
tagList = options && options.hash ? options.hash.tag : false;
|
||||
author = this.author ? this.author.name : null,
|
||||
tagList = options.hash.tag || false,
|
||||
authorList = options.hash.author || false,
|
||||
tagsOk,
|
||||
authorOk;
|
||||
|
||||
function evaluateTagList(expr, tags) {
|
||||
return expr.split(',').map(function (v) {
|
||||
|
@ -662,12 +674,23 @@ coreHelpers.has = function (options) {
|
|||
}, false);
|
||||
}
|
||||
|
||||
if (!tagList) {
|
||||
function evaluateAuthorList(expr, author) {
|
||||
var authorList = expr.split(',').map(function (v) {
|
||||
return v.trim().toLocaleLowerCase();
|
||||
});
|
||||
|
||||
return _.contains(authorList, author.toLocaleLowerCase());
|
||||
}
|
||||
|
||||
if (!tagList && !authorList) {
|
||||
errors.logWarn('Invalid or no attribute given to has helper');
|
||||
return;
|
||||
}
|
||||
|
||||
if (tagList && evaluateTagList(tagList, tags)) {
|
||||
tagsOk = tagList && evaluateTagList(tagList, tags) || false;
|
||||
authorOk = authorList && evaluateAuthorList(authorList, author) || false;
|
||||
|
||||
if (tagsOk || authorOk) {
|
||||
return options.fn(this);
|
||||
}
|
||||
return options.inverse(this);
|
||||
|
@ -703,6 +726,10 @@ coreHelpers.pagination = function (options) {
|
|||
context.tagSlug = this.tag.slug;
|
||||
}
|
||||
|
||||
if (this.author !== undefined) {
|
||||
context.authorSlug = this.author.slug;
|
||||
}
|
||||
|
||||
return template.execute('pagination', context);
|
||||
};
|
||||
|
||||
|
|
|
@ -365,6 +365,49 @@ describe('Frontend Routing', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Author based RSS pages', function () {
|
||||
it('should redirect without slash', function (done) {
|
||||
request.get('/author/ghost-owner/rss')
|
||||
.expect('Location', '/author/ghost-owner/rss/')
|
||||
.expect('Cache-Control', cacheRules.year)
|
||||
.expect(301)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should respond with xml', function (done) {
|
||||
request.get('/author/ghost-owner/rss/')
|
||||
.expect('Content-Type', /xml/)
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
.expect(200)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect page 1', function (done) {
|
||||
request.get('/author/ghost-owner/rss/1/')
|
||||
.expect('Location', '/author/ghost-owner/rss/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
// TODO: This should probably be a 301?
|
||||
.expect(302)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect to last page if page too high', function (done) {
|
||||
request.get('/author/ghost-owner/rss/3/')
|
||||
.expect('Location', '/author/ghost-owner/rss/2/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
.expect(302)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect to first page if page too low', function (done) {
|
||||
request.get('/author/ghost-owner/rss/0/')
|
||||
.expect('Location', '/author/ghost-owner/rss/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
.expect(302)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Static page', function () {
|
||||
it('should redirect without slash', function (done) {
|
||||
request.get('/static-page-test')
|
||||
|
@ -493,6 +536,66 @@ describe('Frontend Routing', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Author pages', function () {
|
||||
|
||||
// Add enough posts to trigger tag pages
|
||||
before(function (done) {
|
||||
testUtils.clearData().then(function () {
|
||||
// we initialise data, but not a user. No user should be required for navigating the frontend
|
||||
return testUtils.initData();
|
||||
}).then(function () {
|
||||
|
||||
return testUtils.fixtures.insertPosts();
|
||||
}).then(function () {
|
||||
return testUtils.fixtures.insertMorePosts(10);
|
||||
}).then(function () {
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('should redirect without slash', function (done) {
|
||||
request.get('/author/ghost-owner/page/2')
|
||||
.expect('Location', '/author/ghost-owner/page/2/')
|
||||
.expect('Cache-Control', cacheRules.year)
|
||||
.expect(301)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should respond with html', function (done) {
|
||||
request.get('/author/ghost-owner/page/2/')
|
||||
.expect('Content-Type', /html/)
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
.expect(200)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect page 1', function (done) {
|
||||
request.get('/author/ghost-owner/page/1/')
|
||||
.expect('Location', '/author/ghost-owner/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
// TODO: This should probably be a 301?
|
||||
.expect(302)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect to last page if page too high', function (done) {
|
||||
request.get('/author/ghost-owner/page/4/')
|
||||
.expect('Location', '/author/ghost-owner/page/3/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
.expect(302)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect to first page if page too low', function (done) {
|
||||
request.get('/author/ghost-owner/page/0/')
|
||||
.expect('Location', '/author/ghost-owner/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
.expect(302)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// ### The rest of the tests switch to date permalinks
|
||||
|
||||
// describe('Date permalinks', function () {
|
||||
|
|
|
@ -613,6 +613,19 @@ describe('Core Helpers', function () {
|
|||
inverse.called.should.be.true;
|
||||
});
|
||||
|
||||
it('should not do anything if there are no attributes', function () {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.has.call(
|
||||
{tags: [{ name: 'foo'}, { name: 'bar'}, { name: 'baz'}]},
|
||||
{fn: fn, inverse: inverse}
|
||||
);
|
||||
|
||||
fn.called.should.be.false;
|
||||
inverse.called.should.be.false;
|
||||
});
|
||||
|
||||
it('should not do anything when an invalid attribute is given', function () {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
@ -625,6 +638,84 @@ describe('Core Helpers', function () {
|
|||
fn.called.should.be.false;
|
||||
inverse.called.should.be.false;
|
||||
});
|
||||
|
||||
it('should handle author list that evaluates to true', function () {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.has.call(
|
||||
{author: { name: 'sam'}},
|
||||
{hash: { author: 'joe, sam, pat'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
|
||||
fn.called.should.be.true;
|
||||
inverse.called.should.be.false;
|
||||
});
|
||||
|
||||
it('should handle author list that evaluates to false', function () {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.has.call(
|
||||
{author: { name: 'jamie'}},
|
||||
{hash: { author: 'joe, sam, pat'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
|
||||
fn.called.should.be.false;
|
||||
inverse.called.should.be.true;
|
||||
});
|
||||
|
||||
it('should handle authors with case-insensitivity', function () {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.has.call(
|
||||
{author: { name: 'Sam'}},
|
||||
{hash: { author: 'joe, sAm, pat'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
|
||||
fn.called.should.be.true;
|
||||
inverse.called.should.be.false;
|
||||
});
|
||||
|
||||
it('should handle tags and authors like an OR query (pass)', function () {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.has.call(
|
||||
{author: {name: 'sam'}, tags: [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}]},
|
||||
{hash: {author: 'joe, sam, pat', tag: 'much, such, wow'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
|
||||
fn.called.should.be.true;
|
||||
inverse.called.should.be.false;
|
||||
});
|
||||
|
||||
it('should handle tags and authors like an OR query (pass)', function () {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.has.call(
|
||||
{author: { name: 'sam'}, tags: [{ name: 'much'}, { name: 'bar'}, { name: 'baz'}]},
|
||||
{hash: { author: 'joe, sam, pat', tag: 'much, such, wow'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
|
||||
fn.called.should.be.true;
|
||||
inverse.called.should.be.false;
|
||||
});
|
||||
|
||||
it('should handle tags and authors like an OR query (fail)', function () {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.has.call(
|
||||
{author: { name: 'fred'}, tags: [{ name: 'foo'}, { name: 'bar'}, { name: 'baz'}]},
|
||||
{hash: { author: 'joe, sam, pat', tag: 'much, such, wow'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
|
||||
fn.called.should.be.false;
|
||||
inverse.called.should.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('url Helper', function () {
|
||||
|
@ -734,6 +825,27 @@ describe('Core Helpers', function () {
|
|||
helpers.page_url.call(tagContext, 2).should.equal('/blog/tag/pumpkin/page/2/');
|
||||
helpers.page_url.call(tagContext, 50).should.equal('/blog/tag/pumpkin/page/50/');
|
||||
});
|
||||
|
||||
it('can return a valid url for tag pages', function () {
|
||||
var authorContext = {
|
||||
authorSlug: 'pumpkin'
|
||||
};
|
||||
helpers.page_url.call(authorContext, 1).should.equal('/author/pumpkin/');
|
||||
helpers.page_url.call(authorContext, 2).should.equal('/author/pumpkin/page/2/');
|
||||
helpers.page_url.call(authorContext, 50).should.equal('/author/pumpkin/page/50/');
|
||||
});
|
||||
|
||||
it('can return a valid url for tag pages with subdirectory', function () {
|
||||
_.extend(helpers.__get__('config'), {
|
||||
paths: {'subdir': '/blog'}
|
||||
});
|
||||
var authorContext = {
|
||||
authorSlug: 'pumpkin'
|
||||
};
|
||||
helpers.page_url.call(authorContext, 1).should.equal('/blog/author/pumpkin/');
|
||||
helpers.page_url.call(authorContext, 2).should.equal('/blog/author/pumpkin/page/2/');
|
||||
helpers.page_url.call(authorContext, 50).should.equal('/blog/author/pumpkin/page/50/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Page Url Helper: DEPRECATED', function () {
|
||||
|
|
Loading…
Reference in a new issue