diff --git a/ghost/core/core/frontend/services/proxy.js b/ghost/core/core/frontend/services/proxy.js index 831a4e0d16..8d6cbe0044 100644 --- a/ghost/core/core/frontend/services/proxy.js +++ b/ghost/core/core/frontend/services/proxy.js @@ -27,6 +27,9 @@ module.exports = { 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; }, /** diff --git a/ghost/core/core/frontend/services/rendering/format-response.js b/ghost/core/core/frontend/services/rendering/format-response.js index 98d02da0e9..135ff45e2c 100644 --- a/ghost/core/core/frontend/services/rendering/format-response.js +++ b/ghost/core/core/frontend/services/rendering/format-response.js @@ -1,4 +1,5 @@ const _ = require('lodash'); +const hbs = require('../theme-engine/engine'); const {prepareContextResource} = require('../proxy'); /** @@ -50,7 +51,17 @@ function formatPageResponse(result, pageAsPost = false) { * * @return {Object} containing page variables */ -function formatResponse(post, context) { +function formatResponse(post, context, locals = {}) { + // 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: true // default behaviour + }; + + // grab data off of the post that will be deleted in prepareContextResource + const showTitleAndFeatureImage = post.show_title_and_feature_image; + prepareContextResource(post); let entry = { @@ -60,8 +71,23 @@ function formatResponse(post, context) { // NOTE: preview context is a special case where the internal preview api returns the post model's type field if (context?.includes('page') || (context?.includes('preview') && post.type === 'page')) { entry.page = post; + + // move properties from the page context object onto @page + // - makes the value available outside of the page context + // - data is removed from the post object in prepareContextResource so use of @page is forced + if (showTitleAndFeatureImage !== undefined) { + pageData.show_title_and_feature_image = showTitleAndFeatureImage; + } } + // merge @page into local template options + const localTemplateOptions = hbs.getLocalTemplateOptions(locals); + hbs.updateLocalTemplateOptions(locals, _.merge({}, localTemplateOptions, { + data: { + page: pageData + } + })); + return entry; } diff --git a/ghost/core/core/frontend/services/rendering/render-entry.js b/ghost/core/core/frontend/services/rendering/render-entry.js index d18249827e..be1f5494a0 100644 --- a/ghost/core/core/frontend/services/rendering/render-entry.js +++ b/ghost/core/core/frontend/services/rendering/render-entry.js @@ -13,6 +13,6 @@ module.exports = function renderEntry(req, res) { return function renderEntryClosure(entry) { // Format data 2 - 1 is in preview/entry // Render - return renderer(req, res, formatResponse.entry(entry, res.routerOptions?.context)); + return renderer(req, res, formatResponse.entry(entry, res.routerOptions?.context, res.locals)); }; }; diff --git a/ghost/core/test/unit/frontend/services/rendering/format-response.test.js b/ghost/core/test/unit/frontend/services/rendering/format-response.test.js index 597a263d55..0f6a4ab85c 100644 --- a/ghost/core/test/unit/frontend/services/rendering/format-response.test.js +++ b/ghost/core/test/unit/frontend/services/rendering/format-response.test.js @@ -5,6 +5,7 @@ const {SafeString} = require('../../../../../core/frontend/services/handlebars') describe('Unit - services/routing/helpers/format-response', function () { let posts; + let pages; let tags; beforeEach(function () { @@ -12,6 +13,11 @@ describe('Unit - services/routing/helpers/format-response', function () { testUtils.DataGenerator.forKnex.createPost({slug: 'sluggy-thing'}) ]; + pages = [ + testUtils.DataGenerator.forKnex.createPost({slug: 'home', page: true}), + testUtils.DataGenerator.forKnex.createPost({slug: 'about', page: true, show_title_and_feature_image: false}) + ]; + tags = [ testUtils.DataGenerator.forKnex.createTag({name: 'video', slug: 'video'}) ]; @@ -34,6 +40,43 @@ describe('Unit - services/routing/helpers/format-response', function () { formatted.post.feature_image_caption.should.be.an.instanceof(SafeString); }); + + it('should set up @page local for posts', function () { + const postObject = posts[0]; + const locals = {}; + + helpers.formatResponse.entry(postObject, ['post'], locals); + + 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.true(); + }); + + it('should set up @page local for pages', function () { + const postObject = pages[0]; + const locals = {}; + + const formatted = helpers.formatResponse.entry(postObject, ['page'], 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.true(); + }); + + it('should assign properties on @page for pages', function () { + const postObject = pages[1]; + const locals = {}; + + const formatted = helpers.formatResponse.entry(postObject, ['page'], 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(); + }); }); describe('entries', function () {