0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-04-08 02:52:39 -05:00

🐛 Fixed missing @page object in themes when rendering custom routed page (#17693)

closes https://github.com/TryGhost/Ghost/issues/17681

- updated `prepareContextResource()` to make sure `show_title_and_feature_image` is always removed from pages
- updated `formatResponse.entries()` to apply the same `@page` local behaviour when it's passed a `data.page` object to account for custom routed pages
This commit is contained in:
Kevin Ansfield 2023-08-11 14:50:01 +01:00 committed by GitHub
parent 6c481b74a9
commit 1ebdac3997
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 60 additions and 6 deletions

View file

@ -3,6 +3,10 @@ function isPost(jsonData) {
Object.prototype.hasOwnProperty.call(jsonData, 'title') && Object.prototype.hasOwnProperty.call(jsonData, 'slug');
}
function isPage(jsonData = {}) {
return Object.prototype.hasOwnProperty.call(jsonData, 'show_title_and_feature_image');
}
function isTag(jsonData) {
return Object.prototype.hasOwnProperty.call(jsonData, 'name') && Object.prototype.hasOwnProperty.call(jsonData, 'slug') &&
Object.prototype.hasOwnProperty.call(jsonData, 'description') && Object.prototype.hasOwnProperty.call(jsonData, 'feature_image');
@ -20,6 +24,7 @@ function isNav(jsonData) {
module.exports = {
isPost,
isPage,
isTag,
isUser,
isNav

View file

@ -26,10 +26,10 @@ module.exports = {
if (resource.feature_image_caption) {
resource.feature_image_caption = new SafeString(resource.feature_image_caption);
}
});
// some properties are extracted to local template data to force one way of using it
delete data.show_title_and_feature_image;
// some properties are extracted to local template data to force one way of using it
delete resource.show_title_and_feature_image;
});
},
/**

View file

@ -1,13 +1,14 @@
const _ = require('lodash');
const hbs = require('../theme-engine/engine');
const {prepareContextResource} = require('../proxy');
const {isPage} = require('../data/checks');
/**
* @description Formats API response into handlebars/theme format.
*
* @return {Object} containing page variables
*/
function formatPageResponse(result, pageAsPost = false) {
function formatPageResponse(result, pageAsPost = false, locals = {}) {
const response = {};
if (result.posts) {
@ -19,6 +20,28 @@ function formatPageResponse(result, pageAsPost = false) {
response.pagination = result.meta.pagination;
}
// when a custom routed page is loaded it can have an associated page object,
// in that case we want to make sure @page is still available and matches the
// selected page properties
if (isPage(result.data?.page?.[0])) {
const page = result.data?.page?.[0];
// build up @page data for use in templates
// - done here rather than `update-local-template-options` middleware because
// we need access to the rendered entry's data which isn't available in middleware
const pageData = {
show_title_and_feature_image: page.show_title_and_feature_image
};
// merge @page into local template options
const localTemplateOptions = hbs.getLocalTemplateOptions(locals);
hbs.updateLocalTemplateOptions(locals, _.merge({}, localTemplateOptions, {
data: {
page: pageData
}
}));
}
_.each(result.data, function (data, name) {
prepareContextResource(data);

View file

@ -14,6 +14,6 @@ module.exports = function renderEntries(req, res) {
return function renderEntriesClosure(result) {
// Format data 2
// Render
return renderer(req, res, formatResponse.entries(result));
return renderer(req, res, formatResponse.entries(result, false, res.locals));
};
};

View file

@ -65,7 +65,7 @@ module.exports = function staticController(req, res, next) {
// This flag solves the confusion about whether the output contains a post or page object by duplicating the objects
// This is not ideal, but will solve some long standing pain points with dynamic routing until we can overhaul it
const duplicatePagesAsPosts = true;
renderer.renderer(req, res, renderer.formatResponse.entries(response, duplicatePagesAsPosts));
renderer.renderer(req, res, renderer.formatResponse.entries(response, duplicatePagesAsPosts, res.locals));
})
.catch(renderer.handleError(next));
};

View file

@ -5,6 +5,7 @@ describe('Checks', function () {
it('methods', function () {
Object.keys(checks).should.eql([
'isPost',
'isPage',
'isTag',
'isUser',
'isNav'
@ -17,6 +18,14 @@ describe('Checks', function () {
checks.isPost({title: 'Test', slug: 'test', html: ''}).should.eql(true);
});
it('isPage', function () {
checks.isPage(undefined).should.eql(false);
checks.isPage({}).should.eql(false);
checks.isPage({title: 'Test'}).should.eql(false);
checks.isPage({title: 'Test', show_title_and_feature_image: false}).should.eql(true);
checks.isPage({title: 'Test', show_title_and_feature_image: true}).should.eql(true);
});
it('isTag', function () {
checks.isTag({}).should.eql(false);
checks.isTag({name: 'Test'}).should.eql(false);

View file

@ -151,5 +151,22 @@ describe('Unit - services/routing/helpers/format-response', function () {
formatted.featured_multiple[0].feature_image_caption.should.be.an.instanceof(SafeString);
formatted.featured_multiple[1].feature_image_caption.should.be.an.instanceof(SafeString);
});
it('should set @page when data.page is present (e.g. custom routing)', function () {
const data = {
posts,
data: {
page: [pages[1]]
}
};
const locals = {};
const formatted = helpers.formatResponse.entries(data, true, locals);
formatted.page.should.not.have.property('show_title_and_feature_image');
locals.should.be.an.Object().with.properties('_templateOptions');
locals._templateOptions.data.should.be.an.Object().with.properties('page');
locals._templateOptions.data.page.show_title_and_feature_image.should.be.false();
});
});
});