mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Split theme engine middleware into separate files
- one big file full of stuff is never good for clarity - separating it out helps us see what requires what - it also highlights the awful naming and opaque behaviour we have in themes - much to do, but this helps us start
This commit is contained in:
parent
e6503e5148
commit
9f11140ec4
6 changed files with 222 additions and 209 deletions
|
@ -1,209 +0,0 @@
|
|||
const _ = require('lodash');
|
||||
const hbs = require('./engine');
|
||||
const urlUtils = require('../../../shared/url-utils');
|
||||
const {api} = require('../proxy');
|
||||
const errors = require('@tryghost/errors');
|
||||
const tpl = require('@tryghost/tpl');
|
||||
const settingsCache = require('../../../shared/settings-cache');
|
||||
const customThemeSettingsCache = require('../../../shared/custom-theme-settings-cache');
|
||||
const labs = require('../../../shared/labs');
|
||||
const activeTheme = require('./active');
|
||||
const preview = require('./preview');
|
||||
|
||||
const messages = {
|
||||
missingTheme: 'The currently active theme "{theme}" is missing.'
|
||||
};
|
||||
|
||||
// ### Ensure Active Theme
|
||||
// Ensure there's a properly set & mounted active theme before attempting to serve a site request
|
||||
// If there is no active theme, throw an error
|
||||
// Else, ensure the active theme is mounted
|
||||
function ensureActiveTheme(req, res, next) {
|
||||
// CASE: this means that the theme hasn't been loaded yet i.e. there is no active theme
|
||||
if (!activeTheme.get()) {
|
||||
// This is the one place we ACTUALLY throw an error for a missing theme as it's a request we cannot serve
|
||||
return next(new errors.InternalServerError({
|
||||
// We use the settingsCache here, because the setting will be set,
|
||||
// even if the theme itself is not usable because it is invalid or missing.
|
||||
message: tpl(messages.missingTheme, {theme: settingsCache.get('active_theme')})
|
||||
}));
|
||||
}
|
||||
|
||||
// If the active theme has not yet been mounted, mount it into express
|
||||
if (!activeTheme.get().mounted) {
|
||||
activeTheme.get().mount(req.app);
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
function calculateLegacyPriceData(products) {
|
||||
const defaultPrice = {
|
||||
amount: 0,
|
||||
currency: 'usd',
|
||||
interval: 'year',
|
||||
nickname: ''
|
||||
};
|
||||
|
||||
function makePriceObject(price) {
|
||||
const numberAmount = 0 + price.amount;
|
||||
const dollarAmount = numberAmount ? Math.round(numberAmount / 100) : 0;
|
||||
return {
|
||||
valueOf() {
|
||||
return dollarAmount;
|
||||
},
|
||||
amount: numberAmount,
|
||||
currency: price.currency,
|
||||
nickname: price.name,
|
||||
interval: price.interval
|
||||
};
|
||||
}
|
||||
|
||||
const defaultProduct = products[0] || {};
|
||||
|
||||
const monthlyPrice = makePriceObject(defaultProduct.monthly_price || defaultPrice);
|
||||
|
||||
const yearlyPrice = makePriceObject(defaultProduct.yearly_price || defaultPrice);
|
||||
|
||||
const priceData = {
|
||||
monthly: monthlyPrice,
|
||||
yearly: yearlyPrice,
|
||||
currency: monthlyPrice ? monthlyPrice.currency : defaultPrice.currency
|
||||
};
|
||||
|
||||
return priceData;
|
||||
}
|
||||
|
||||
async function getProductAndPricesData() {
|
||||
try {
|
||||
const page = await api.canary.productsPublic.browse({
|
||||
include: ['monthly_price', 'yearly_price'],
|
||||
limit: 'all'
|
||||
});
|
||||
|
||||
return page.products;
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function getSiteData() {
|
||||
let siteData = settingsCache.getPublic();
|
||||
|
||||
// theme-only computed property added to @site
|
||||
if (settingsCache.get('members_signup_access') === 'none') {
|
||||
const escapedUrl = encodeURIComponent(urlUtils.urlFor({relativeUrl: '/rss/'}, true));
|
||||
siteData.signup_url = `https://feedly.com/i/subscription/feed/${escapedUrl}`;
|
||||
} else {
|
||||
siteData.signup_url = '#/portal';
|
||||
}
|
||||
|
||||
return siteData;
|
||||
}
|
||||
|
||||
async function updateGlobalTemplateOptions(req, res, next) {
|
||||
// Static information, same for every request unless the settings change
|
||||
// @TODO: bind this once and then update based on events?
|
||||
// @TODO: decouple theme layer from settings cache using the Content API
|
||||
const siteData = getSiteData();
|
||||
const labsData = labs.getAll();
|
||||
|
||||
const themeData = {
|
||||
posts_per_page: activeTheme.get().config('posts_per_page'),
|
||||
image_sizes: activeTheme.get().config('image_sizes')
|
||||
};
|
||||
const themeSettingsData = customThemeSettingsCache.getAll();
|
||||
const productData = await getProductAndPricesData();
|
||||
const priceData = calculateLegacyPriceData(productData);
|
||||
|
||||
let products = null;
|
||||
let product = null;
|
||||
if (productData.length === 1) {
|
||||
product = productData[0];
|
||||
} else {
|
||||
products = productData;
|
||||
}
|
||||
|
||||
// @TODO: only do this if something changed?
|
||||
// @TODO: remove blog in a major where we are happy to break more themes
|
||||
{
|
||||
hbs.updateTemplateOptions({
|
||||
data: {
|
||||
blog: siteData,
|
||||
site: siteData,
|
||||
labs: labsData,
|
||||
config: themeData,
|
||||
price: priceData,
|
||||
product,
|
||||
products,
|
||||
custom: themeSettingsData
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
function updateLocalTemplateData(req, res, next) {
|
||||
// Pass 'secure' flag to the view engine
|
||||
// so that templates can choose to render https or http 'url', see url utility
|
||||
res.locals.secure = req.secure;
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
function updateLocalTemplateOptions(req, res, next) {
|
||||
const localTemplateOptions = hbs.getLocalTemplateOptions(res.locals);
|
||||
|
||||
// adjust @site.url for http/https based on the incoming request
|
||||
const siteData = {
|
||||
url: urlUtils.urlFor('home', {secure: req.secure, trailingSlash: false}, true)
|
||||
};
|
||||
|
||||
// @TODO: it would be nicer if this was proper middleware somehow...
|
||||
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);
|
||||
|
||||
const member = req.member ? {
|
||||
uuid: req.member.uuid,
|
||||
email: req.member.email,
|
||||
name: req.member.name,
|
||||
firstname: req.member.name && req.member.name.split(' ')[0],
|
||||
avatar_image: req.member.avatar_image,
|
||||
subscriptions: req.member.subscriptions && req.member.subscriptions.map((sub) => {
|
||||
return Object.assign({}, sub, {
|
||||
default_payment_card_last4: sub.default_payment_card_last4 || '****'
|
||||
});
|
||||
}),
|
||||
paid: req.member.status !== 'free'
|
||||
} : null;
|
||||
|
||||
hbs.updateLocalTemplateOptions(res.locals, _.merge({}, localTemplateOptions, {
|
||||
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
|
||||
}
|
||||
}));
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
ensureActiveTheme,
|
||||
updateGlobalTemplateOptions,
|
||||
updateLocalTemplateData,
|
||||
updateLocalTemplateOptions
|
||||
];
|
|
@ -0,0 +1,34 @@
|
|||
const errors = require('@tryghost/errors');
|
||||
const tpl = require('@tryghost/tpl');
|
||||
|
||||
const activeTheme = require('../active');
|
||||
const settingsCache = require('../../../../shared/settings-cache');
|
||||
|
||||
const messages = {
|
||||
missingTheme: 'The currently active theme "{theme}" is missing.'
|
||||
};
|
||||
|
||||
// ### Ensure Active Theme
|
||||
// Ensure there's a properly set & mounted active theme before attempting to serve a site request
|
||||
// If there is no active theme, throw an error
|
||||
// Else, ensure the active theme is mounted
|
||||
function ensureActiveTheme(req, res, next) {
|
||||
// CASE: this means that the theme hasn't been loaded yet i.e. there is no active theme
|
||||
if (!activeTheme.get()) {
|
||||
// This is the one place we ACTUALLY throw an error for a missing theme as it's a request we cannot serve
|
||||
return next(new errors.InternalServerError({
|
||||
// We use the settingsCache here, because the setting will be set,
|
||||
// even if the theme itself is not usable because it is invalid or missing.
|
||||
message: tpl(messages.missingTheme, {theme: settingsCache.get('active_theme')})
|
||||
}));
|
||||
}
|
||||
|
||||
// If the active theme has not yet been mounted, mount it into express
|
||||
if (!activeTheme.get().mounted) {
|
||||
activeTheme.get().mount(req.app);
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = ensureActiveTheme;
|
6
core/frontend/services/theme-engine/middleware/index.js
Normal file
6
core/frontend/services/theme-engine/middleware/index.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
module.exports = [
|
||||
require('./ensure-active-theme'),
|
||||
require('./update-global-template-options'),
|
||||
require('./update-local-template-data'),
|
||||
require('./update-local-template-options')
|
||||
];
|
|
@ -0,0 +1,116 @@
|
|||
const hbs = require('../engine');
|
||||
const urlUtils = require('../../../../shared/url-utils');
|
||||
const {api} = require('../../proxy');
|
||||
const settingsCache = require('../../../../shared/settings-cache');
|
||||
const customThemeSettingsCache = require('../../../../shared/custom-theme-settings-cache');
|
||||
const labs = require('../../../../shared/labs');
|
||||
const activeTheme = require('../active');
|
||||
|
||||
function calculateLegacyPriceData(products) {
|
||||
const defaultPrice = {
|
||||
amount: 0,
|
||||
currency: 'usd',
|
||||
interval: 'year',
|
||||
nickname: ''
|
||||
};
|
||||
|
||||
function makePriceObject(price) {
|
||||
const numberAmount = 0 + price.amount;
|
||||
const dollarAmount = numberAmount ? Math.round(numberAmount / 100) : 0;
|
||||
return {
|
||||
valueOf() {
|
||||
return dollarAmount;
|
||||
},
|
||||
amount: numberAmount,
|
||||
currency: price.currency,
|
||||
nickname: price.name,
|
||||
interval: price.interval
|
||||
};
|
||||
}
|
||||
|
||||
const defaultProduct = products[0] || {};
|
||||
|
||||
const monthlyPrice = makePriceObject(defaultProduct.monthly_price || defaultPrice);
|
||||
|
||||
const yearlyPrice = makePriceObject(defaultProduct.yearly_price || defaultPrice);
|
||||
|
||||
const priceData = {
|
||||
monthly: monthlyPrice,
|
||||
yearly: yearlyPrice,
|
||||
currency: monthlyPrice ? monthlyPrice.currency : defaultPrice.currency
|
||||
};
|
||||
|
||||
return priceData;
|
||||
}
|
||||
|
||||
async function getProductAndPricesData() {
|
||||
try {
|
||||
const page = await api.canary.productsPublic.browse({
|
||||
include: ['monthly_price', 'yearly_price'],
|
||||
limit: 'all'
|
||||
});
|
||||
|
||||
return page.products;
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function getSiteData() {
|
||||
let siteData = settingsCache.getPublic();
|
||||
|
||||
// theme-only computed property added to @site
|
||||
if (settingsCache.get('members_signup_access') === 'none') {
|
||||
const escapedUrl = encodeURIComponent(urlUtils.urlFor({relativeUrl: '/rss/'}, true));
|
||||
siteData.signup_url = `https://feedly.com/i/subscription/feed/${escapedUrl}`;
|
||||
} else {
|
||||
siteData.signup_url = '#/portal';
|
||||
}
|
||||
|
||||
return siteData;
|
||||
}
|
||||
|
||||
async function updateGlobalTemplateOptions(req, res, next) {
|
||||
// Static information, same for every request unless the settings change
|
||||
// @TODO: bind this once and then update based on events?
|
||||
// @TODO: decouple theme layer from settings cache using the Content API
|
||||
const siteData = getSiteData();
|
||||
const labsData = labs.getAll();
|
||||
|
||||
const themeData = {
|
||||
posts_per_page: activeTheme.get().config('posts_per_page'),
|
||||
image_sizes: activeTheme.get().config('image_sizes')
|
||||
};
|
||||
const themeSettingsData = customThemeSettingsCache.getAll();
|
||||
const productData = await getProductAndPricesData();
|
||||
const priceData = calculateLegacyPriceData(productData);
|
||||
|
||||
let products = null;
|
||||
let product = null;
|
||||
if (productData.length === 1) {
|
||||
product = productData[0];
|
||||
} else {
|
||||
products = productData;
|
||||
}
|
||||
|
||||
// @TODO: only do this if something changed?
|
||||
// @TODO: remove blog in a major where we are happy to break more themes
|
||||
{
|
||||
hbs.updateTemplateOptions({
|
||||
data: {
|
||||
blog: siteData,
|
||||
site: siteData,
|
||||
labs: labsData,
|
||||
config: themeData,
|
||||
price: priceData,
|
||||
product,
|
||||
products,
|
||||
custom: themeSettingsData
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = updateGlobalTemplateOptions;
|
|
@ -0,0 +1,9 @@
|
|||
function updateLocalTemplateData(req, res, next) {
|
||||
// Pass 'secure' flag to the view engine
|
||||
// so that templates can choose to render https or http 'url', see url utility
|
||||
res.locals.secure = req.secure;
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = updateLocalTemplateData;
|
|
@ -0,0 +1,57 @@
|
|||
const _ = require('lodash');
|
||||
const hbs = require('../engine');
|
||||
const urlUtils = require('../../../../shared/url-utils');
|
||||
const customThemeSettingsCache = require('../../../../shared/custom-theme-settings-cache');
|
||||
const labs = require('../../../../shared/labs');
|
||||
const preview = require('../preview');
|
||||
|
||||
function updateLocalTemplateOptions(req, res, next) {
|
||||
const localTemplateOptions = hbs.getLocalTemplateOptions(res.locals);
|
||||
|
||||
// adjust @site.url for http/https based on the incoming request
|
||||
const siteData = {
|
||||
url: urlUtils.urlFor('home', {secure: req.secure, trailingSlash: false}, true)
|
||||
};
|
||||
|
||||
// @TODO: it would be nicer if this was proper middleware somehow...
|
||||
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);
|
||||
|
||||
const member = req.member ? {
|
||||
uuid: req.member.uuid,
|
||||
email: req.member.email,
|
||||
name: req.member.name,
|
||||
firstname: req.member.name && req.member.name.split(' ')[0],
|
||||
avatar_image: req.member.avatar_image,
|
||||
subscriptions: req.member.subscriptions && req.member.subscriptions.map((sub) => {
|
||||
return Object.assign({}, sub, {
|
||||
default_payment_card_last4: sub.default_payment_card_last4 || '****'
|
||||
});
|
||||
}),
|
||||
paid: req.member.status !== 'free'
|
||||
} : null;
|
||||
|
||||
hbs.updateLocalTemplateOptions(res.locals, _.merge({}, localTemplateOptions, {
|
||||
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
|
||||
}
|
||||
}));
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = updateLocalTemplateOptions;
|
Loading…
Add table
Reference in a new issue