0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Allowed custom theme settings to be passed through preview data

refs https://github.com/TryGhost/Team/issues/1097

- added `customThemeSettingKeys` as an argument to `preview.handle()` because we can't know which keys should be allowed through up-front
- added `custom` as a supported setting in the preview header data
  - `custom` should be a JSON object containing any custom theme settings
  - we parse the object but only set properties on `@custom` that are known custom theme setting keys
  - if parsing fails or it's not an object then no custom data is set
- updated `updateLocalTemplateOptions()` to pull `.custom` off of the preview data and pass it through so it's accessible on `@custom` as an override to the saved custom data
This commit is contained in:
Kevin Ansfield 2021-09-30 13:23:39 +01:00
parent 8a17e723a1
commit b8e2bb7b6d
3 changed files with 154 additions and 7 deletions

View file

@ -161,7 +161,15 @@ function updateLocalTemplateOptions(req, res, next) {
};
// @TODO: it would be nicer if this was proper middleware somehow...
const previewData = preview.handle(req);
const previewData = preview.handle(req, Object.keys(customThemeSettingsCache.getAll()));
// strip custom off of preview data so it doesn't get merged into @site
const customThemeSettingsPreviewData = previewData.custom;
delete previewData.custom;
let customData = {};
if (labs.isSet('customThemeSettings')) {
customData = customThemeSettingsPreviewData;
}
// update site data with any preview values from the request
Object.assign(siteData, previewData);
@ -184,6 +192,7 @@ function updateLocalTemplateOptions(req, res, next) {
data: {
member: member,
site: siteData,
custom: customData,
// @deprecated: a gscan warning for @blog was added before 3.0 which replaced it with @site
blog: siteData
}

View file

@ -14,13 +14,14 @@ function decodeValue(value) {
return value;
}
function getPreviewData(previewHeader) {
function getPreviewData(previewHeader, customThemeSettingKeys = []) {
// Keep the string shorter with short codes for certain parameters
const supportedSettings = {
c: 'accent_color',
icon: 'icon',
logo: 'logo',
cover: 'cover_image'
cover: 'cover_image',
custom: 'custom'
};
let opts = new URLSearchParams(previewHeader);
@ -34,17 +35,34 @@ function getPreviewData(previewHeader) {
}
});
if (previewData.custom) {
try {
const custom = {};
const previewCustom = JSON.parse(previewData.custom);
if (typeof previewCustom === 'object') {
customThemeSettingKeys.forEach((key) => {
custom[key] = previewCustom[key];
});
}
previewData.custom = custom;
} catch (e) {
previewData.custom = {};
}
}
previewData._preview = previewHeader;
return previewData;
}
module.exports._PREVIEW_HEADER_NAME = PREVIEW_HEADER_NAME;
module.exports.handle = (req) => {
module.exports.handle = (req, customThemeSettingKeys) => {
let previewData = {};
if (req && req.header(PREVIEW_HEADER_NAME)) {
previewData = getPreviewData(req.header(PREVIEW_HEADER_NAME));
previewData = getPreviewData(req.header(PREVIEW_HEADER_NAME), customThemeSettingKeys);
}
return previewData;

View file

@ -5,6 +5,7 @@ const middleware = require('../../../../core/frontend/services/theme-engine').mi
// is only exposed via themeEngine.getActive()
const activeTheme = require('../../../../core/frontend/services/theme-engine/active');
const settingsCache = require('../../../../core/shared/settings-cache');
const customThemeSettingsCache = require('../../../../core/shared/custom-theme-settings-cache');
const sandbox = sinon.createSandbox();
@ -34,6 +35,7 @@ describe('Themes middleware', function () {
let fakeActiveThemeName;
let fakeSiteData;
let fakeLabsData;
let fakeCustomThemeSettingsData;
beforeEach(function () {
req = {app: {}, header: () => {}};
@ -52,7 +54,12 @@ describe('Themes middleware', function () {
// labs data is deep cloned,
// if we want to compare it
// we will need some unique content
members: true
members: true,
customThemeSettings: true
};
fakeCustomThemeSettingsData = {
header_typography: 'Sans-Serif'
};
sandbox.stub(activeTheme, 'get')
@ -65,6 +72,9 @@ describe('Themes middleware', function () {
sandbox.stub(settingsCache, 'getPublic')
.returns(fakeSiteData);
sandbox.stub(customThemeSettingsCache, 'getAll')
.returns(fakeCustomThemeSettingsData);
sandbox.stub(hbs, 'updateTemplateOptions');
sandbox.stub(hbs, 'updateLocalTemplateOptions');
});
@ -151,7 +161,7 @@ describe('Themes middleware', function () {
const templateOptions = hbs.updateTemplateOptions.firstCall.args[0];
const data = templateOptions.data;
data.should.be.an.Object().with.properties('site', 'labs', 'config');
data.should.be.an.Object().with.properties('site', 'labs', 'config', 'custom');
// Check Theme Config
data.config.should.be.an.Object()
@ -167,6 +177,8 @@ describe('Themes middleware', function () {
should.exist(data.site.signup_url);
data.site.signup_url.should.equal('#/portal');
should.deepEqual(data.custom, fakeCustomThemeSettingsData);
done();
} catch (error) {
done(error);
@ -249,5 +261,113 @@ describe('Themes middleware', function () {
}
});
});
it('calls updateLocalTemplateOptions with correct custom theme settings data', function (done) {
const customPreviewObject = {header_typography: 'Serif'};
const previewString = `custom=${encodeURIComponent(JSON.stringify(customPreviewObject))}`;
req.header = () => {
return previewString;
};
executeMiddleware(middleware, req, res, function next(err) {
try {
should.not.exist(err);
hbs.updateLocalTemplateOptions.calledOnce.should.be.true();
const templateOptions = hbs.updateLocalTemplateOptions.firstCall.args[1];
const data = templateOptions.data;
data.should.be.an.Object().with.properties('site', 'custom');
data.custom.should.be.an.Object().with.properties('header_typography');
data.custom.header_typography.should.eql('Serif');
done();
} catch (error) {
done(error);
}
});
});
it('calls updateLocalTemplateOptions with correct without unknown custom theme settings', function (done) {
const customPreviewObject = {header_typography: 'Serif', unknown_setting: true};
const previewString = `custom=${encodeURIComponent(JSON.stringify(customPreviewObject))}`;
req.header = () => {
return previewString;
};
executeMiddleware(middleware, req, res, function next(err) {
try {
should.not.exist(err);
hbs.updateLocalTemplateOptions.calledOnce.should.be.true();
const templateOptions = hbs.updateLocalTemplateOptions.firstCall.args[1];
const data = templateOptions.data;
data.should.be.an.Object().with.properties('site', 'custom');
data.custom.should.be.an.Object().with.properties('header_typography');
data.custom.header_typography.should.eql('Serif');
data.custom.should.not.have.property('unknown_setting');
done();
} catch (error) {
done(error);
}
});
});
it('calls updateLocalTemplateOptions with no custom data when custom param is not parseable JSON', function (done) {
const previewString = `custom=${encodeURIComponent('<html>')}`;
req.header = () => {
return previewString;
};
executeMiddleware(middleware, req, res, function next(err) {
try {
should.not.exist(err);
hbs.updateLocalTemplateOptions.calledOnce.should.be.true();
const templateOptions = hbs.updateLocalTemplateOptions.firstCall.args[1];
const data = templateOptions.data;
data.should.be.an.Object().with.properties('site', 'custom');
data.custom.should.be.an.Object();
data.custom.should.be.empty();
done();
} catch (error) {
done(error);
}
});
});
it('calls updateLocalTemplateOptions with no custom data when custom param is not an object', function (done) {
const previewString = `custom=${encodeURIComponent('"header_typography"')}`;
req.header = () => {
return previewString;
};
executeMiddleware(middleware, req, res, function next(err) {
try {
should.not.exist(err);
hbs.updateLocalTemplateOptions.calledOnce.should.be.true();
const templateOptions = hbs.updateLocalTemplateOptions.firstCall.args[1];
const data = templateOptions.data;
data.should.be.an.Object().with.properties('site', 'custom');
data.custom.should.be.an.Object();
data.custom.should.be.empty();
done();
} catch (error) {
done(error);
}
});
});
});
});