0
Fork 0
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:
Sebastian Gierlinger 2014-07-31 18:52:16 +02:00
commit c5fbe2def3
4 changed files with 280 additions and 18 deletions

View file

@ -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);

View file

@ -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);
};

View file

@ -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 () {

View file

@ -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 () {