mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-22 18:31:57 +03:00
623c65c509
fixes #10990 - Changed the static router to throw a 400 error for a missing template file, rather than falling back to using the default.hbs file - Falling back is weird and hard to understand, but throwing an error makes it clear that the user has to provide the matching template - The new error reads 'Missing template [filename].hbs for route "[route]".' Assume you have a route.yaml file something like: ``` routes: /: home ``` - In Ghost v2, if you don't have a home.hbs template, Ghost falls back to using the default.hbs file if it's available - Most themes have a default.hbs, however this file is a layout file, depended on by other templates, not a template file itself - In production mode, using the default.hbs as a template causes weird, intermittent layout issues depending on which order pages are loaded - This is due to this issue: https://github.com/barc/express-hbs/issues/161 - In Ghost v3, we will throw a 400 error for missing template files instead of having a fallback - In the example above, navigating to '/' would throw the error 'Missing template home.hbs for route "/".'
142 lines
4.6 KiB
JavaScript
142 lines
4.6 KiB
JavaScript
const debug = require('ghost-ignition').debug('services:routing:static-routes-router');
|
|
const common = require('../../../server/lib/common');
|
|
const urlUtils = require('../../../server/lib/url-utils');
|
|
const RSSRouter = require('./RSSRouter');
|
|
const controllers = require('./controllers');
|
|
const middlewares = require('./middlewares');
|
|
const ParentRouter = require('./ParentRouter');
|
|
|
|
/**
|
|
* @description Template routes allow you to map individual URLs to specific template files within a Ghost theme
|
|
*/
|
|
class StaticRoutesRouter extends ParentRouter {
|
|
constructor(mainRoute, object) {
|
|
super('StaticRoutesRouter');
|
|
|
|
this.route = {value: mainRoute};
|
|
this.templates = object.templates || [];
|
|
this.data = object.data || {query: {}, router: {}};
|
|
this.routerName = mainRoute === '/' ? 'index' : mainRoute.replace(/\//g, '');
|
|
|
|
debug(this.route.value, this.templates);
|
|
|
|
// CASE 1: Route is channel (controller: channel) - a stream of posts
|
|
// CASE 2: Route is just a static page e.g. landing page
|
|
if (this.isChannel(object)) {
|
|
this.templates = this.templates.reverse();
|
|
this.rss = object.rss !== false;
|
|
this.filter = object.filter;
|
|
this.limit = object.limit;
|
|
this.order = object.order;
|
|
|
|
this.controller = object.controller;
|
|
|
|
debug(this.route.value, this.templates, this.filter, this.data);
|
|
this._registerChannelRoutes();
|
|
} else {
|
|
this.contentType = object.content_type;
|
|
debug(this.route.value, this.templates);
|
|
this._registerStaticRoute();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @description Register all channel routes of this router (...if the router is a channel)
|
|
* @private
|
|
*/
|
|
_registerChannelRoutes() {
|
|
// REGISTER: prepare context object
|
|
this.router().use(this._prepareChannelContext.bind(this));
|
|
|
|
// REGISTER: is rss enabled?
|
|
if (this.rss) {
|
|
this.rssRouter = new RSSRouter();
|
|
this.mountRouter(this.route.value, this.rssRouter.router());
|
|
}
|
|
|
|
// REGISTER: channel route
|
|
this.mountRoute(this.route.value, controllers[this.controller]);
|
|
|
|
// REGISTER: pagination
|
|
this.router().param('page', middlewares.pageParam);
|
|
this.mountRoute(urlUtils.urlJoin(this.route.value, 'page', ':page(\\d+)'), controllers[this.controller]);
|
|
|
|
common.events.emit('router.created', this);
|
|
}
|
|
|
|
/**
|
|
* @description Prepare channel context for further middlewares/controllers.
|
|
* @param {Object} req
|
|
* @param {Object} res
|
|
* @param {Function} next
|
|
* @private
|
|
*/
|
|
_prepareChannelContext(req, res, next) {
|
|
res.routerOptions = {
|
|
type: this.controller,
|
|
name: this.routerName,
|
|
context: [this.routerName],
|
|
filter: this.filter,
|
|
limit: this.limit,
|
|
order: this.order,
|
|
data: this.data.query,
|
|
templates: this.templates
|
|
};
|
|
|
|
next();
|
|
}
|
|
|
|
/**
|
|
* @description Register all static routes of this router (...if the router is just a static route)
|
|
* @private
|
|
*/
|
|
_registerStaticRoute() {
|
|
// REGISTER: prepare context object
|
|
this.router().use(this._prepareStaticRouteContext.bind(this));
|
|
|
|
// REGISTER: static route
|
|
this.mountRoute(this.route.value, controllers.static);
|
|
|
|
common.events.emit('router.created', this);
|
|
}
|
|
|
|
/**
|
|
* @description Prepare static route context for further middlewares/controllers.
|
|
* @param {Object} req
|
|
* @param {Object} res
|
|
* @param {Function} next
|
|
* @private
|
|
*/
|
|
_prepareStaticRouteContext(req, res, next) {
|
|
res.routerOptions = {
|
|
type: 'custom',
|
|
templates: this.templates,
|
|
defaultTemplate: () => {
|
|
throw new common.errors.IncorrectUsageError({
|
|
message: `Missing template ${res.routerOptions.templates.map(x => `${x}.hbs`).join(', ')} for route "${req.originalUrl}".`
|
|
});
|
|
},
|
|
data: this.data.query,
|
|
context: [this.routerName],
|
|
contentType: this.contentType
|
|
};
|
|
|
|
next();
|
|
}
|
|
|
|
/**
|
|
* @description Helper function to figure out if this router is a channel.
|
|
* @param {Object} object
|
|
* @returns {boolean}
|
|
*/
|
|
isChannel(object) {
|
|
if (object && object.controller && object.controller === 'channel') {
|
|
return true;
|
|
}
|
|
|
|
return this.controller === 'channel';
|
|
}
|
|
}
|
|
|
|
module.exports = StaticRoutesRouter;
|