mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Dynamic Routing Beta: collection name behaviour
refs #9601 Example: ``` collections: /podcast/: permalink: /{slug}/ ``` - the name of the collection is remembered as `routerName` (in the case above: "podcast") - the name of the collection is important for two things 1. context value 2. template name - the context value is available for specific theme helpers e.g. is helper, body_class helper - we auto-lookup the collection name in your theme e.g. podcast.hbs - this logic does not apply to static routes - if you define templates on your collection, they are stronger than the collection name
This commit is contained in:
parent
aad4f79410
commit
a1b55509df
11 changed files with 177 additions and 103 deletions
|
@ -1,21 +1,7 @@
|
|||
const routingService = require('../../services/routing');
|
||||
|
||||
/**
|
||||
* https://github.com/TryGhost/Team/issues/65#issuecomment-393622816
|
||||
*
|
||||
* For now we output only the default rss feed link. And this is the first collection.
|
||||
* If the first collection has rss disabled, we output nothing.
|
||||
*
|
||||
* @TODO: We are currently investigating this.
|
||||
*/
|
||||
function getRssUrl(data, absolute) {
|
||||
const firstCollection = routingService.registry.getFirstCollectionRouter();
|
||||
|
||||
if (!firstCollection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return firstCollection.getRssUrl({
|
||||
return routingService.registry.getRssUrl({
|
||||
secure: data.secure,
|
||||
absolute: absolute
|
||||
});
|
||||
|
|
|
@ -9,16 +9,14 @@ const middlewares = require('./middlewares');
|
|||
const RSSRouter = require('./RSSRouter');
|
||||
|
||||
class CollectionRouter extends ParentRouter {
|
||||
constructor(indexRoute, object, options) {
|
||||
options = options || {};
|
||||
|
||||
constructor(mainRoute, object) {
|
||||
super('CollectionRouter');
|
||||
|
||||
this.firstCollection = options.firstCollection;
|
||||
this.routerName = mainRoute === '/' ? 'index' : mainRoute.replace(/\//g, '');
|
||||
|
||||
// NOTE: index/parent route e.g. /, /podcast/, /magic/ ;)
|
||||
this.route = {
|
||||
value: indexRoute
|
||||
value: mainRoute
|
||||
};
|
||||
|
||||
this.permalinks = {
|
||||
|
@ -52,14 +50,9 @@ class CollectionRouter extends ParentRouter {
|
|||
return this.permalinks.value;
|
||||
};
|
||||
|
||||
// the main post listening collection get's the index context
|
||||
if (this.firstCollection) {
|
||||
this.context = ['index'];
|
||||
} else {
|
||||
this.context = [];
|
||||
}
|
||||
this.context = [this.routerName];
|
||||
|
||||
debug(this.route, this.permalinks);
|
||||
debug(this.name, this.route, this.permalinks);
|
||||
|
||||
this._registerRoutes();
|
||||
this._listeners();
|
||||
|
@ -67,7 +60,7 @@ class CollectionRouter extends ParentRouter {
|
|||
|
||||
_registerRoutes() {
|
||||
// REGISTER: context middleware for this collection
|
||||
this.router().use(this._prepareIndexContext.bind(this));
|
||||
this.router().use(this._prepareEntriesContext.bind(this));
|
||||
|
||||
// REGISTER: collection route e.g. /, /podcast/
|
||||
this.mountRoute(this.route.value, controllers.collection);
|
||||
|
@ -96,7 +89,7 @@ class CollectionRouter extends ParentRouter {
|
|||
*
|
||||
* @TODO: Why do we need two context objects? O_O - refactor this out
|
||||
*/
|
||||
_prepareIndexContext(req, res, next) {
|
||||
_prepareEntriesContext(req, res, next) {
|
||||
res.locals.routerOptions = {
|
||||
filter: this.filter,
|
||||
permalinks: this.permalinks.getValue({withUrlOptions: true}),
|
||||
|
@ -104,7 +97,8 @@ class CollectionRouter extends ParentRouter {
|
|||
context: this.context,
|
||||
frontPageTemplate: 'home',
|
||||
templates: this.templates,
|
||||
identifier: this.identifier
|
||||
identifier: this.identifier,
|
||||
name: this.routerName
|
||||
};
|
||||
|
||||
res._route = {
|
||||
|
|
|
@ -4,10 +4,10 @@ const helpers = require('./helpers');
|
|||
const ParentRouter = require('./ParentRouter');
|
||||
|
||||
class StaticRoutesRouter extends ParentRouter {
|
||||
constructor(key, object) {
|
||||
constructor(mainRoute, object) {
|
||||
super('StaticRoutesRouter');
|
||||
|
||||
this.route = {value: key};
|
||||
this.route = {value: mainRoute};
|
||||
this.templates = object.templates || [];
|
||||
|
||||
debug(this.route.value, this.templates);
|
||||
|
|
6
core/server/services/routing/bootstrap.js
vendored
6
core/server/services/routing/bootstrap.js
vendored
|
@ -47,12 +47,8 @@ module.exports = function bootstrap() {
|
|||
});
|
||||
|
||||
_.each(dynamicRoutes.collections, (value, key) => {
|
||||
const collectionRouter = new CollectionRouter(key, value, {
|
||||
firstCollection: Object.keys(dynamicRoutes.collections).indexOf(key) === 0
|
||||
});
|
||||
|
||||
const collectionRouter = new CollectionRouter(key, value);
|
||||
siteRouter.mountRouter(collectionRouter.router());
|
||||
|
||||
registry.setRouter(collectionRouter.identifier, collectionRouter);
|
||||
});
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ _private.getErrorTemplateHierarchy = function getErrorTemplateHierarchy(statusCo
|
|||
_private.getCollectionTemplateHierarchy = function getCollectionTemplateHierarchy(routerOptions, requestOptions) {
|
||||
const templateList = ['index'];
|
||||
|
||||
// CASE: author, tag
|
||||
// CASE: author, tag, custom collection name
|
||||
if (routerOptions.name && routerOptions.name !== 'index') {
|
||||
templateList.unshift(routerOptions.name);
|
||||
|
||||
|
|
|
@ -19,14 +19,40 @@ module.exports = {
|
|||
return routers[name];
|
||||
},
|
||||
|
||||
getFirstCollectionRouter() {
|
||||
return _.find(routers, (router) => {
|
||||
if (router.name === 'CollectionRouter' && router.firstCollection) {
|
||||
return router;
|
||||
}
|
||||
/**
|
||||
* https://github.com/TryGhost/Team/issues/65#issuecomment-393622816
|
||||
*
|
||||
* Hierarchy for primary rss url:
|
||||
*
|
||||
* - index collection (/)
|
||||
* - if you only have one collection, we take this rss url
|
||||
*/
|
||||
getRssUrl(options) {
|
||||
let rssUrl = null;
|
||||
|
||||
return false;
|
||||
});
|
||||
const collectionIndexRouter = _.find(routers, {name: 'CollectionRouter', routerName: 'index'});
|
||||
|
||||
if (collectionIndexRouter) {
|
||||
rssUrl = collectionIndexRouter.getRssUrl(options);
|
||||
|
||||
// CASE: is rss enabled?
|
||||
if (rssUrl) {
|
||||
return rssUrl;
|
||||
}
|
||||
}
|
||||
|
||||
const collectionRouters = _.filter(routers, {name: 'CollectionRouter'});
|
||||
|
||||
if (collectionRouters && collectionRouters.length === 1) {
|
||||
rssUrl = collectionRouters[0].getRssUrl(options);
|
||||
|
||||
// CASE: is rss enabled?
|
||||
if (rssUrl) {
|
||||
return rssUrl;
|
||||
}
|
||||
}
|
||||
|
||||
return rssUrl;
|
||||
},
|
||||
|
||||
resetAllRoutes() {
|
||||
|
|
|
@ -16,7 +16,7 @@ const should = require('should'),
|
|||
* either move to integration tests or rewrite!!!
|
||||
*/
|
||||
describe('{{ghost_head}} helper', function () {
|
||||
let posts = [], tags = [], users = [], firstCollection;
|
||||
let posts = [], tags = [], users = [];
|
||||
|
||||
before(function () {
|
||||
testUtils.integrationTesting.defaultMocks(sandbox);
|
||||
|
@ -28,9 +28,7 @@ describe('{{ghost_head}} helper', function () {
|
|||
before(function () {
|
||||
models.init();
|
||||
|
||||
firstCollection = sandbox.stub();
|
||||
firstCollection.getRssUrl = sandbox.stub().returns('http://localhost:65530/rss/');
|
||||
sandbox.stub(routing.registry, 'getFirstCollectionRouter').returns(firstCollection);
|
||||
sandbox.stub(routing.registry, 'getRssUrl').returns('http://localhost:65530/rss/');
|
||||
|
||||
settingsCache.get.withArgs('title').returns('Ghost');
|
||||
settingsCache.get.withArgs('description').returns('blog description');
|
||||
|
@ -1253,11 +1251,11 @@ describe('{{ghost_head}} helper', function () {
|
|||
url: 'http://localhost:65530/blog/'
|
||||
});
|
||||
|
||||
firstCollection.getRssUrl.returns('http://localhost:65530/blog/rss/');
|
||||
routing.registry.getRssUrl.returns('http://localhost:65530/blog/rss/');
|
||||
});
|
||||
|
||||
after(function () {
|
||||
firstCollection.getRssUrl.returns('http://localhost:65530/rss/');
|
||||
routing.registry.getRssUrl.returns('http://localhost:65530/rss/');
|
||||
});
|
||||
|
||||
it('returns correct rss url with subdirectory', function (done) {
|
||||
|
|
|
@ -5,15 +5,9 @@ const should = require('should'),
|
|||
getRssUrl = require('../../../../server/data/meta/rss_url');
|
||||
|
||||
describe('getRssUrl', function () {
|
||||
let firstCollection;
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox.restore();
|
||||
|
||||
firstCollection = sandbox.stub();
|
||||
firstCollection.getRssUrl = sandbox.stub().returns('/rss/');
|
||||
|
||||
sandbox.stub(routing.registry, 'getFirstCollectionRouter').returns(firstCollection);
|
||||
sandbox.stub(routing.registry, 'getRssUrl').returns('/rss/');
|
||||
});
|
||||
|
||||
it('should return rss url', function () {
|
||||
|
@ -29,6 +23,6 @@ describe('getRssUrl', function () {
|
|||
secure: false
|
||||
}, true);
|
||||
|
||||
firstCollection.getRssUrl.calledWith({secure: false, absolute: true}).should.be.true();
|
||||
routing.registry.getRssUrl.calledWith({secure: false, absolute: true}).should.be.true();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,27 +9,28 @@ const should = require('should'),
|
|||
describe('UNIT - services/routing/CollectionRouter', function () {
|
||||
let req, res, next;
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox.stub(settingsCache, 'get').withArgs('permalinks').returns('/:slug/');
|
||||
|
||||
sandbox.stub(common.events, 'emit');
|
||||
sandbox.stub(common.events, 'on');
|
||||
|
||||
sandbox.spy(CollectionRouter.prototype, 'mountRoute');
|
||||
sandbox.spy(CollectionRouter.prototype, 'mountRouter');
|
||||
sandbox.spy(CollectionRouter.prototype, 'unmountRoute');
|
||||
|
||||
req = sandbox.stub();
|
||||
res = sandbox.stub();
|
||||
next = sandbox.stub();
|
||||
|
||||
res.locals = {};
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('instantiate', function () {
|
||||
beforeEach(function () {
|
||||
sandbox.stub(settingsCache, 'get').withArgs('permalinks').returns('/:slug/');
|
||||
|
||||
sandbox.stub(common.events, 'emit');
|
||||
sandbox.stub(common.events, 'on');
|
||||
|
||||
sandbox.spy(CollectionRouter.prototype, 'mountRoute');
|
||||
sandbox.spy(CollectionRouter.prototype, 'mountRouter');
|
||||
|
||||
req = sandbox.stub();
|
||||
res = sandbox.stub();
|
||||
next = sandbox.stub();
|
||||
|
||||
res.locals = {};
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('default', function () {
|
||||
const collectionRouter = new CollectionRouter('/', {permalink: '/:slug/'});
|
||||
|
||||
|
@ -64,15 +65,18 @@ describe('UNIT - services/routing/CollectionRouter', function () {
|
|||
collectionRouter.mountRouter.args[0][1].should.eql(collectionRouter.rssRouter.router());
|
||||
});
|
||||
|
||||
it('first collection option', function () {
|
||||
const collectionRouter1 = new CollectionRouter('/', {permalink: '/:slug/'}, {firstCollection: true});
|
||||
const collectionRouter2 = new CollectionRouter('/', {permalink: '/:slug/'}, {firstCollection: false});
|
||||
it('router name', function () {
|
||||
const collectionRouter1 = new CollectionRouter('/', {permalink: '/:slug/'});
|
||||
const collectionRouter2 = new CollectionRouter('/podcast/', {permalink: '/:slug/'});
|
||||
const collectionRouter3 = new CollectionRouter('/hello/world/', {permalink: '/:slug/'});
|
||||
|
||||
collectionRouter1.firstCollection.should.be.true();
|
||||
collectionRouter2.firstCollection.should.be.false();
|
||||
collectionRouter1.routerName.should.eql('index');
|
||||
collectionRouter2.routerName.should.eql('podcast');
|
||||
collectionRouter3.routerName.should.eql('helloworld');
|
||||
|
||||
collectionRouter1.context.should.eql(['index']);
|
||||
collectionRouter2.context.should.eql([]);
|
||||
collectionRouter2.context.should.eql(['podcast']);
|
||||
collectionRouter3.context.should.eql(['helloworld']);
|
||||
});
|
||||
|
||||
it('collection lives under /blog/', function () {
|
||||
|
@ -136,11 +140,29 @@ describe('UNIT - services/routing/CollectionRouter', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('fn: _prepareIndexContext', function () {
|
||||
it('default', function () {
|
||||
describe('fn: _prepareEntriesContext', function () {
|
||||
it('index collection', function () {
|
||||
const collectionRouter = new CollectionRouter('/', {permalink: '/:slug/'});
|
||||
|
||||
collectionRouter._prepareEntriesContext(req, res, next);
|
||||
|
||||
next.calledOnce.should.be.true();
|
||||
res.locals.routerOptions.should.eql({
|
||||
filter: 'page:false',
|
||||
permalinks: '/:slug/:options(edit)?/',
|
||||
frontPageTemplate: 'home',
|
||||
templates: [],
|
||||
identifier: collectionRouter.identifier,
|
||||
context: ['index'],
|
||||
name: 'index',
|
||||
type: 'posts'
|
||||
});
|
||||
});
|
||||
|
||||
it('with templates, no index collection', function () {
|
||||
const collectionRouter = new CollectionRouter('/magic/', {permalink: '/:slug/', templates: ['home', 'index']});
|
||||
|
||||
collectionRouter._prepareIndexContext(req, res, next);
|
||||
collectionRouter._prepareEntriesContext(req, res, next);
|
||||
|
||||
next.calledOnce.should.be.true();
|
||||
res.locals.routerOptions.should.eql({
|
||||
|
@ -149,27 +171,14 @@ describe('UNIT - services/routing/CollectionRouter', function () {
|
|||
frontPageTemplate: 'home',
|
||||
templates: ['index', 'home'],
|
||||
identifier: collectionRouter.identifier,
|
||||
context: [],
|
||||
context: ['magic'],
|
||||
name: 'magic',
|
||||
type: 'posts'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('permalink in database changes', function () {
|
||||
beforeEach(function () {
|
||||
sandbox.stub(settingsCache, 'get').withArgs('permalinks').returns('/:slug/');
|
||||
|
||||
sandbox.stub(common.events, 'emit');
|
||||
sandbox.stub(common.events, 'on');
|
||||
|
||||
sandbox.spy(CollectionRouter.prototype, 'mountRoute');
|
||||
sandbox.spy(CollectionRouter.prototype, 'unmountRoute');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('permalink placeholder: flat', function () {
|
||||
const collectionRouter = new CollectionRouter('/magic/', {permalink: '{globals.permalinks}'});
|
||||
|
||||
|
|
|
@ -22,6 +22,18 @@ describe('templates', function () {
|
|||
_private.getCollectionTemplateHierarchy({name: 'index'}).should.eql(['index']);
|
||||
});
|
||||
|
||||
it('should return custom templates even if the collection is index', function () {
|
||||
_private.getCollectionTemplateHierarchy({name: 'index', templates: ['something']}).should.eql(['something', 'index']);
|
||||
});
|
||||
|
||||
it('should return collection name', function () {
|
||||
_private.getCollectionTemplateHierarchy({name: 'podcast'}).should.eql(['podcast', 'index']);
|
||||
});
|
||||
|
||||
it('should return custom templates', function () {
|
||||
_private.getCollectionTemplateHierarchy({name: 'podcast', templates: ['mozart']}).should.eql(['mozart', 'podcast', 'index']);
|
||||
});
|
||||
|
||||
it('should return just index if collection name is index even if slug is set', function () {
|
||||
_private.getCollectionTemplateHierarchy({name: 'index', slugTemplate: true}, {slugParam: 'test'}).should.eql(['index']);
|
||||
});
|
||||
|
|
59
core/test/unit/services/routing/registry_spec.js
Normal file
59
core/test/unit/services/routing/registry_spec.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
const should = require('should'),
|
||||
sinon = require('sinon'),
|
||||
rewire = require('rewire'),
|
||||
registry = rewire('../../../../server/services/routing/registry'),
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('UNIT: services/routing/registry', function () {
|
||||
let getRssUrlStub;
|
||||
|
||||
beforeEach(function () {
|
||||
getRssUrlStub = sandbox.stub();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('fn: getRssUrl', function () {
|
||||
it('no url available', function () {
|
||||
should.not.exist(registry.getRssUrl());
|
||||
});
|
||||
|
||||
it('single collection, no index collection', function () {
|
||||
registry.setRouter('CollectionRouter', {
|
||||
name: 'CollectionRouter',
|
||||
routerName: 'podcast',
|
||||
getRssUrl: sandbox.stub().returns('/podcast/rss/')
|
||||
});
|
||||
|
||||
registry.getRssUrl().should.eql('/podcast/rss/');
|
||||
});
|
||||
|
||||
it('single collection, no index collection, rss disabled', function () {
|
||||
registry.setRouter('CollectionRouter', {
|
||||
name: 'CollectionRouter',
|
||||
routerName: 'podcast',
|
||||
getRssUrl: sandbox.stub().returns(null)
|
||||
});
|
||||
|
||||
should.not.exist(registry.getRssUrl());
|
||||
});
|
||||
|
||||
it('index collection', function () {
|
||||
registry.setRouter('CollectionRouter', {
|
||||
name: 'CollectionRouter',
|
||||
routerName: 'podcast',
|
||||
getRssUrl: sandbox.stub().returns('/podcast/rss/')
|
||||
});
|
||||
|
||||
registry.setRouter('CollectionRouter', {
|
||||
name: 'CollectionRouter',
|
||||
routerName: 'index',
|
||||
getRssUrl: sandbox.stub().returns('/rss/')
|
||||
});
|
||||
|
||||
registry.getRssUrl().should.eql('/rss/');
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue