ghost 4.24.0 → 4.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +39 -0
- package/content/themes/casper/assets/built/casper.js +1 -1
- package/content/themes/casper/assets/built/casper.js.map +1 -1
- package/content/themes/casper/assets/built/global.css +1 -1
- package/content/themes/casper/assets/built/global.css.map +1 -1
- package/content/themes/casper/assets/built/screen.css +1 -1
- package/content/themes/casper/assets/built/screen.css.map +1 -1
- package/content/themes/casper/assets/css/global.css +6 -1
- package/content/themes/casper/assets/css/screen.css +32 -216
- package/content/themes/casper/default.hbs +2 -2
- package/content/themes/casper/package.json +3 -2
- package/content/themes/casper/post.hbs +1 -1
- package/content/themes/casper/yarn.lock +173 -123
- package/core/boot.js +2 -2
- package/core/built/assets/ghost-dark-d690e732e17ffc794e2e59c1467ca282.css +1 -0
- package/core/built/assets/ghost.min-043bb7480a0810109b130f13b2a4235e.css +1 -0
- package/core/built/assets/{ghost.min-d5595f9c71ebc534ccf9ac78483d357c.js → ghost.min-bc72f685c1c9adc9885925c1412435a5.js} +502 -577
- package/core/built/assets/icons/audio-upload.svg +8 -0
- package/core/built/assets/{vendor.min-1a84ac3ef74edf31c6e86810b45221cc.js → vendor.min-d1234c632a54502777c34e50752fa3fc.js} +2598 -2137
- package/core/frontend/apps/amp/lib/helpers/amp_content.js +2 -2
- package/core/frontend/apps/amp/lib/views/amp.hbs +8 -0
- package/core/frontend/apps/private-blogging/index.js +1 -1
- package/core/frontend/services/apps/index.js +1 -1
- package/core/frontend/services/apps/loader.js +3 -3
- package/core/frontend/services/helpers/handlebars.js +1 -1
- package/core/frontend/services/theme-engine/middleware/ensure-active-theme.js +34 -0
- package/core/frontend/services/theme-engine/middleware/index.js +6 -0
- package/core/frontend/services/theme-engine/middleware/update-global-template-options.js +116 -0
- package/core/frontend/services/theme-engine/middleware/update-local-template-data.js +9 -0
- package/core/frontend/services/theme-engine/middleware/update-local-template-options.js +57 -0
- package/core/frontend/src/cards/css/button.css +4 -0
- package/core/frontend/src/cards/css/callout.css +10 -10
- package/core/frontend/web/middleware/error-handler.js +93 -0
- package/core/frontend/web/middleware/handle-image-sizes.js +3 -6
- package/core/frontend/web/middleware/index.js +1 -0
- package/core/frontend/web/site.js +1 -1
- package/core/server/adapters/scheduling/SchedulingDefault.js +2 -2
- package/core/server/adapters/storage/LocalStorageBase.js +2 -2
- package/core/server/api/canary/db.js +2 -2
- package/core/server/api/canary/media.js +3 -2
- package/core/server/api/canary/oembed.js +16 -1
- package/core/server/api/canary/session.js +1 -1
- package/core/server/api/canary/slugs.js +1 -1
- package/core/server/api/canary/utils/permissions.js +2 -2
- package/core/server/api/canary/utils/serializers/output/config.js +2 -6
- package/core/server/api/v2/db.js +2 -2
- package/core/server/api/v2/session.js +1 -1
- package/core/server/api/v2/slugs.js +1 -1
- package/core/server/api/v2/utils/permissions.js +2 -2
- package/core/server/api/v3/db.js +2 -2
- package/core/server/api/v3/session.js +1 -1
- package/core/server/api/v3/slugs.js +1 -1
- package/core/server/api/v3/utils/permissions.js +2 -2
- package/core/server/data/db/state-manager.js +4 -4
- package/core/server/data/exporter/export-filename.js +1 -1
- package/core/server/data/importer/handlers/json.js +1 -1
- package/core/server/data/importer/import-manager.js +1 -1
- package/core/server/data/importer/importers/data/base.js +1 -1
- package/core/server/data/migrations/utils.js +2 -2
- package/core/server/data/migrations/versions/1.25/1-update-koenig-beta-html.js +1 -0
- package/core/server/data/migrations/versions/3.1/08-add-uuid-values-to-members.js +1 -0
- package/core/server/data/migrations/versions/3.22/02-settings-key-renames.js +2 -0
- package/core/server/data/migrations/versions/3.22/05-migrate-members-subscription-settings.js +3 -0
- package/core/server/data/migrations/versions/3.22/06-migrate-stripe-connect-settings.js +2 -0
- package/core/server/data/migrations/versions/3.23/01-migrate-bulk-email-settings.js +1 -0
- package/core/server/data/migrations/versions/3.29/01-remove-duplicate-subscriptions.js +2 -0
- package/core/server/data/migrations/versions/3.29/02-remove-duplicate-customers.js +2 -0
- package/core/server/data/migrations/versions/3.38/04-populate-recipient-filter-column.js +2 -0
- package/core/server/data/migrations/versions/4.0/01-update-mobiledoc.js +2 -0
- package/core/server/data/migrations/versions/4.0/03-populate-status-column-for-members.js +4 -0
- package/core/server/data/migrations/versions/4.0/06-populate-members-subscribe-events-table.js +1 -0
- package/core/server/data/migrations/versions/4.0/17-populate-members-status-events-table.js +1 -0
- package/core/server/data/migrations/versions/4.0/18-transform-urls-absolute-to-transform-ready.js +5 -0
- package/core/server/data/migrations/versions/4.0/22-solve-orphaned-webhooks.js +1 -0
- package/core/server/data/migrations/versions/4.0/23-regenerate-posts-html.js +1 -0
- package/core/server/data/migrations/versions/4.0/25-populate-members-paid-subscription-events-table.js +2 -1
- package/core/server/data/migrations/versions/4.12/02-fix-member-statuses.js +1 -0
- package/core/server/data/migrations/versions/4.14/01-fix-comped-member-statuses.js +3 -0
- package/core/server/data/migrations/versions/4.14/02-fix-free-members-status-events.js +1 -0
- package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +2 -0
- package/core/server/data/migrations/versions/4.23/01-truncate-offer-names.js +1 -0
- package/core/server/data/migrations/versions/4.3/04-attach-members-to-product.js +1 -0
- package/core/server/data/migrations/versions/4.4/01-restore-free-members-signup-setting-from-backup.js +1 -0
- package/core/server/data/migrations/versions/4.6/01-remove-comped-status.js +1 -0
- package/core/server/data/migrations/versions/4.8/04-migrate-show-newsletter-header-setting.js +1 -0
- package/core/server/data/migrations/versions/4.9/05-fix-missed-mobiledoc-url-transforms.js +1 -0
- package/core/server/data/migrations/versions/4.9/06-add-comped-status.js +1 -0
- package/core/server/data/migrations/versions/4.9/07-update-comped-members-status-events.js +1 -0
- package/core/server/data/schema/commands.js +2 -2
- package/core/server/ghost-server.js +2 -2
- package/core/server/lib/image/image-size.js +2 -2
- package/core/server/models/base/listeners.js +2 -2
- package/core/server/models/member-email-change-event.js +2 -2
- package/core/server/models/member-login-event.js +2 -2
- package/core/server/models/member-paid-subscription-event.js +3 -3
- package/core/server/models/member-payment-event.js +3 -3
- package/core/server/models/member-product-event.js +6 -6
- package/core/server/models/member-status-event.js +5 -3
- package/core/server/models/member-subscribe-event.js +9 -3
- package/core/server/models/relations/authors.js +1 -1
- package/core/server/models/settings.js +1 -1
- package/core/server/services/auth/passwordreset.js +1 -1
- package/core/server/services/auth/setup.js +1 -1
- package/core/server/services/mega/mega.js +6 -4
- package/core/server/services/mega/template.js +12 -12
- package/core/server/services/members/api.js +22 -0
- package/core/server/services/members/config.js +1 -1
- package/core/server/services/members/emails/signup-paid.js +168 -0
- package/core/server/services/members/service.js +6 -2
- package/core/server/services/members/stripe-connect.js +4 -2
- package/core/server/services/nft-oembed.js +6 -1
- package/core/server/services/oembed.js +6 -2
- package/core/server/services/permissions/can-this.js +1 -1
- package/core/server/services/redirects/api.js +2 -2
- package/core/server/services/route-settings/default-settings-manager.js +1 -1
- package/core/server/services/route-settings/route-settings.js +4 -12
- package/core/server/services/route-settings/settings-loader.js +4 -4
- package/core/server/services/route-settings/yaml-parser.js +1 -1
- package/core/server/services/slack.js +1 -1
- package/core/server/services/themes/storage.js +2 -2
- package/core/server/services/twitter-embed.js +80 -0
- package/core/server/services/xmlrpc.js +2 -2
- package/core/server/web/admin/views/default-prod.html +4 -4
- package/core/server/web/admin/views/default.html +4 -4
- package/core/server/web/api/canary/admin/middleware.js +1 -1
- package/core/server/web/api/v2/admin/middleware.js +1 -1
- package/core/server/web/api/v3/admin/middleware.js +1 -1
- package/core/server/web/shared/middleware/error-handler.js +28 -150
- package/core/shared/config/defaults.json +7 -1
- package/core/shared/labs.js +3 -4
- package/core/shared/sentry.js +1 -1
- package/package.json +11 -11
- package/yarn.lock +426 -731
- package/content/themes/casper/assets/js/gallery-card.js +0 -24
- package/core/built/assets/ghost-dark-e7b57ab951512c5719aee89b16b9a448.css +0 -1
- package/core/built/assets/ghost.min-7f3603dbeb5ebf0ec09e207ae82fb4e3.css +0 -1
- package/core/frontend/services/theme-engine/middleware.js +0 -209
|
@@ -143,7 +143,7 @@ function getAmperizeHTML(html, post) {
|
|
|
143
143
|
if (err) {
|
|
144
144
|
if (err.src) {
|
|
145
145
|
// This is a valid 500 GhostError because it means the amperize parser is unable to handle some Ghost HTML.
|
|
146
|
-
logging.error(new errors.
|
|
146
|
+
logging.error(new errors.InternalServerError({
|
|
147
147
|
message: `AMP HTML couldn't be parsed: ${err.src}`,
|
|
148
148
|
code: 'AMP_PARSER_ERROR',
|
|
149
149
|
err: err,
|
|
@@ -151,7 +151,7 @@ function getAmperizeHTML(html, post) {
|
|
|
151
151
|
help: 'Please share this error on GitHub or https://forum.ghost.org'
|
|
152
152
|
}));
|
|
153
153
|
} else {
|
|
154
|
-
logging.error(new errors.
|
|
154
|
+
logging.error(new errors.InternalServerError({err, code: 'AMP_PARSER_ERROR'}));
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
// save it in cache to prevent multiple calls to Amperize until
|
|
@@ -24,7 +24,7 @@ let checkSubdir = function checkSubdir() {
|
|
|
24
24
|
paths = urlUtils.getSubdir().split('/');
|
|
25
25
|
|
|
26
26
|
if (paths.pop() === PRIVATE_KEYWORD) {
|
|
27
|
-
logging.error(new errors.
|
|
27
|
+
logging.error(new errors.InternalServerError({
|
|
28
28
|
message: tpl(messages.urlCannotContainPrivateSubdir.error),
|
|
29
29
|
context: tpl(messages.urlCannotContainPrivateSubdir.description),
|
|
30
30
|
help: tpl(messages.urlCannotContainPrivateSubdir.help)
|
|
@@ -18,7 +18,7 @@ module.exports = {
|
|
|
18
18
|
|
|
19
19
|
return Promise.map(appsToLoad, appName => loader.activateAppByName(appName))
|
|
20
20
|
.catch(function (err) {
|
|
21
|
-
logging.error(new errors.
|
|
21
|
+
logging.error(new errors.InternalServerError({
|
|
22
22
|
err: err,
|
|
23
23
|
context: tpl(messages.appWillNotBeLoadedError),
|
|
24
24
|
help: tpl(messages.appWillNotBeLoadedHelp)
|
|
@@ -40,9 +40,9 @@ module.exports = {
|
|
|
40
40
|
|
|
41
41
|
// Check for an activate() method on the app.
|
|
42
42
|
if (!_.isFunction(app.activate)) {
|
|
43
|
-
return Promise.reject(new errors.IncorrectUsageError(
|
|
44
|
-
tpl(messages.noActivateMethodLoadingAppError, {name: name})
|
|
45
|
-
));
|
|
43
|
+
return Promise.reject(new errors.IncorrectUsageError({
|
|
44
|
+
message: tpl(messages.noActivateMethodLoadingAppError, {name: name})
|
|
45
|
+
}));
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// Wrapping the activate() with a when because it's possible
|
|
@@ -17,7 +17,7 @@ function asyncHelperWrapper(hbsInstance, name, fn) {
|
|
|
17
17
|
Promise.resolve(fn.call(this, context, options)).then(function asyncHelperSuccess(result) {
|
|
18
18
|
cb(result);
|
|
19
19
|
}).catch(function asyncHelperError(err) {
|
|
20
|
-
const wrappedErr =
|
|
20
|
+
const wrappedErr = errors.utils.isGhostError(err) ? err : new errors.IncorrectUsageError({
|
|
21
21
|
err: err,
|
|
22
22
|
context: 'registerAsyncThemeHelper: ' + name,
|
|
23
23
|
errorDetails: {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const errors = require('@tryghost/errors');
|
|
2
|
+
const tpl = require('@tryghost/tpl');
|
|
3
|
+
|
|
4
|
+
const activeTheme = require('../active');
|
|
5
|
+
const settingsCache = require('../../../../shared/settings-cache');
|
|
6
|
+
|
|
7
|
+
const messages = {
|
|
8
|
+
missingTheme: 'The currently active theme "{theme}" is missing.'
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// ### Ensure Active Theme
|
|
12
|
+
// Ensure there's a properly set & mounted active theme before attempting to serve a site request
|
|
13
|
+
// If there is no active theme, throw an error
|
|
14
|
+
// Else, ensure the active theme is mounted
|
|
15
|
+
function ensureActiveTheme(req, res, next) {
|
|
16
|
+
// CASE: this means that the theme hasn't been loaded yet i.e. there is no active theme
|
|
17
|
+
if (!activeTheme.get()) {
|
|
18
|
+
// This is the one place we ACTUALLY throw an error for a missing theme as it's a request we cannot serve
|
|
19
|
+
return next(new errors.InternalServerError({
|
|
20
|
+
// We use the settingsCache here, because the setting will be set,
|
|
21
|
+
// even if the theme itself is not usable because it is invalid or missing.
|
|
22
|
+
message: tpl(messages.missingTheme, {theme: settingsCache.get('active_theme')})
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// If the active theme has not yet been mounted, mount it into express
|
|
27
|
+
if (!activeTheme.get().mounted) {
|
|
28
|
+
activeTheme.get().mount(req.app);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
next();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = ensureActiveTheme;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const hbs = require('../engine');
|
|
2
|
+
const urlUtils = require('../../../../shared/url-utils');
|
|
3
|
+
const {api} = require('../../proxy');
|
|
4
|
+
const settingsCache = require('../../../../shared/settings-cache');
|
|
5
|
+
const customThemeSettingsCache = require('../../../../shared/custom-theme-settings-cache');
|
|
6
|
+
const labs = require('../../../../shared/labs');
|
|
7
|
+
const activeTheme = require('../active');
|
|
8
|
+
|
|
9
|
+
function calculateLegacyPriceData(products) {
|
|
10
|
+
const defaultPrice = {
|
|
11
|
+
amount: 0,
|
|
12
|
+
currency: 'usd',
|
|
13
|
+
interval: 'year',
|
|
14
|
+
nickname: ''
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function makePriceObject(price) {
|
|
18
|
+
const numberAmount = 0 + price.amount;
|
|
19
|
+
const dollarAmount = numberAmount ? Math.round(numberAmount / 100) : 0;
|
|
20
|
+
return {
|
|
21
|
+
valueOf() {
|
|
22
|
+
return dollarAmount;
|
|
23
|
+
},
|
|
24
|
+
amount: numberAmount,
|
|
25
|
+
currency: price.currency,
|
|
26
|
+
nickname: price.name,
|
|
27
|
+
interval: price.interval
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const defaultProduct = products[0] || {};
|
|
32
|
+
|
|
33
|
+
const monthlyPrice = makePriceObject(defaultProduct.monthly_price || defaultPrice);
|
|
34
|
+
|
|
35
|
+
const yearlyPrice = makePriceObject(defaultProduct.yearly_price || defaultPrice);
|
|
36
|
+
|
|
37
|
+
const priceData = {
|
|
38
|
+
monthly: monthlyPrice,
|
|
39
|
+
yearly: yearlyPrice,
|
|
40
|
+
currency: monthlyPrice ? monthlyPrice.currency : defaultPrice.currency
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return priceData;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function getProductAndPricesData() {
|
|
47
|
+
try {
|
|
48
|
+
const page = await api.canary.productsPublic.browse({
|
|
49
|
+
include: ['monthly_price', 'yearly_price'],
|
|
50
|
+
limit: 'all'
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return page.products;
|
|
54
|
+
} catch (err) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getSiteData() {
|
|
60
|
+
let siteData = settingsCache.getPublic();
|
|
61
|
+
|
|
62
|
+
// theme-only computed property added to @site
|
|
63
|
+
if (settingsCache.get('members_signup_access') === 'none') {
|
|
64
|
+
const escapedUrl = encodeURIComponent(urlUtils.urlFor({relativeUrl: '/rss/'}, true));
|
|
65
|
+
siteData.signup_url = `https://feedly.com/i/subscription/feed/${escapedUrl}`;
|
|
66
|
+
} else {
|
|
67
|
+
siteData.signup_url = '#/portal';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return siteData;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function updateGlobalTemplateOptions(req, res, next) {
|
|
74
|
+
// Static information, same for every request unless the settings change
|
|
75
|
+
// @TODO: bind this once and then update based on events?
|
|
76
|
+
// @TODO: decouple theme layer from settings cache using the Content API
|
|
77
|
+
const siteData = getSiteData();
|
|
78
|
+
const labsData = labs.getAll();
|
|
79
|
+
|
|
80
|
+
const themeData = {
|
|
81
|
+
posts_per_page: activeTheme.get().config('posts_per_page'),
|
|
82
|
+
image_sizes: activeTheme.get().config('image_sizes')
|
|
83
|
+
};
|
|
84
|
+
const themeSettingsData = customThemeSettingsCache.getAll();
|
|
85
|
+
const productData = await getProductAndPricesData();
|
|
86
|
+
const priceData = calculateLegacyPriceData(productData);
|
|
87
|
+
|
|
88
|
+
let products = null;
|
|
89
|
+
let product = null;
|
|
90
|
+
if (productData.length === 1) {
|
|
91
|
+
product = productData[0];
|
|
92
|
+
} else {
|
|
93
|
+
products = productData;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// @TODO: only do this if something changed?
|
|
97
|
+
// @TODO: remove blog in a major where we are happy to break more themes
|
|
98
|
+
{
|
|
99
|
+
hbs.updateTemplateOptions({
|
|
100
|
+
data: {
|
|
101
|
+
blog: siteData,
|
|
102
|
+
site: siteData,
|
|
103
|
+
labs: labsData,
|
|
104
|
+
config: themeData,
|
|
105
|
+
price: priceData,
|
|
106
|
+
product,
|
|
107
|
+
products,
|
|
108
|
+
custom: themeSettingsData
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
next();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = updateGlobalTemplateOptions;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
function updateLocalTemplateData(req, res, next) {
|
|
2
|
+
// Pass 'secure' flag to the view engine
|
|
3
|
+
// so that templates can choose to render https or http 'url', see url utility
|
|
4
|
+
res.locals.secure = req.secure;
|
|
5
|
+
|
|
6
|
+
next();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
module.exports = updateLocalTemplateData;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const hbs = require('../engine');
|
|
3
|
+
const urlUtils = require('../../../../shared/url-utils');
|
|
4
|
+
const customThemeSettingsCache = require('../../../../shared/custom-theme-settings-cache');
|
|
5
|
+
const labs = require('../../../../shared/labs');
|
|
6
|
+
const preview = require('../preview');
|
|
7
|
+
|
|
8
|
+
function updateLocalTemplateOptions(req, res, next) {
|
|
9
|
+
const localTemplateOptions = hbs.getLocalTemplateOptions(res.locals);
|
|
10
|
+
|
|
11
|
+
// adjust @site.url for http/https based on the incoming request
|
|
12
|
+
const siteData = {
|
|
13
|
+
url: urlUtils.urlFor('home', {secure: req.secure, trailingSlash: false}, true)
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// @TODO: it would be nicer if this was proper middleware somehow...
|
|
17
|
+
const previewData = preview.handle(req, Object.keys(customThemeSettingsCache.getAll()));
|
|
18
|
+
|
|
19
|
+
// strip custom off of preview data so it doesn't get merged into @site
|
|
20
|
+
const customThemeSettingsPreviewData = previewData.custom;
|
|
21
|
+
delete previewData.custom;
|
|
22
|
+
let customData = {};
|
|
23
|
+
if (labs.isSet('customThemeSettings')) {
|
|
24
|
+
customData = customThemeSettingsPreviewData;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// update site data with any preview values from the request
|
|
28
|
+
Object.assign(siteData, previewData);
|
|
29
|
+
|
|
30
|
+
const member = req.member ? {
|
|
31
|
+
uuid: req.member.uuid,
|
|
32
|
+
email: req.member.email,
|
|
33
|
+
name: req.member.name,
|
|
34
|
+
firstname: req.member.name && req.member.name.split(' ')[0],
|
|
35
|
+
avatar_image: req.member.avatar_image,
|
|
36
|
+
subscriptions: req.member.subscriptions && req.member.subscriptions.map((sub) => {
|
|
37
|
+
return Object.assign({}, sub, {
|
|
38
|
+
default_payment_card_last4: sub.default_payment_card_last4 || '****'
|
|
39
|
+
});
|
|
40
|
+
}),
|
|
41
|
+
paid: req.member.status !== 'free'
|
|
42
|
+
} : null;
|
|
43
|
+
|
|
44
|
+
hbs.updateLocalTemplateOptions(res.locals, _.merge({}, localTemplateOptions, {
|
|
45
|
+
data: {
|
|
46
|
+
member: member,
|
|
47
|
+
site: siteData,
|
|
48
|
+
custom: customData,
|
|
49
|
+
// @deprecated: a gscan warning for @blog was added before 3.0 which replaced it with @site
|
|
50
|
+
blog: siteData
|
|
51
|
+
}
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
next();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = updateLocalTemplateOptions;
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
.kg-card
|
|
1
|
+
.kg-callout-card {
|
|
2
2
|
display: flex;
|
|
3
3
|
padding: 20px 28px;
|
|
4
4
|
border-radius: 3px;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
.kg-card-
|
|
7
|
+
.kg-callout-card-grey {
|
|
8
8
|
background: rgba(124, 139, 154, 0.13);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
.kg-card-
|
|
11
|
+
.kg-callout-card-white {
|
|
12
12
|
background: transparent;
|
|
13
13
|
box-shadow: inset 0 0 0 1px rgba(124, 139, 154, 0.25);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
.kg-card-
|
|
16
|
+
.kg-callout-card-blue {
|
|
17
17
|
background: rgba(33, 172, 232, 0.12);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
.kg-card-
|
|
20
|
+
.kg-callout-card-green {
|
|
21
21
|
background: rgba(52, 183, 67, 0.12);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
.kg-card-
|
|
24
|
+
.kg-callout-card-yellow {
|
|
25
25
|
background: rgba(240, 165, 15, 0.13);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
.kg-card-
|
|
28
|
+
.kg-callout-card-red {
|
|
29
29
|
background: rgba(209, 46, 46, 0.11);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
.kg-card-
|
|
32
|
+
.kg-callout-card-pink {
|
|
33
33
|
background: rgba(225, 71, 174, 0.11);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
.kg-card-
|
|
36
|
+
.kg-callout-card-purple {
|
|
37
37
|
background: rgba(135, 85, 236, 0.12);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
.kg-card-
|
|
40
|
+
.kg-callout-card-accent {
|
|
41
41
|
background: var(--ghost-accent-color);
|
|
42
42
|
color: #fff;
|
|
43
43
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const hbs = require('express-hbs');
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
const tpl = require('@tryghost/tpl');
|
|
4
|
+
const sentry = require('../../../shared/sentry');
|
|
5
|
+
|
|
6
|
+
const config = require('../../../shared/config');
|
|
7
|
+
const helpers = require('../../services/routing/helpers');
|
|
8
|
+
|
|
9
|
+
// @TODO: make this properly shared code
|
|
10
|
+
const shared = require('../../../server/web/shared/middleware/error-handler');
|
|
11
|
+
|
|
12
|
+
const messages = {
|
|
13
|
+
oopsErrorTemplateHasError: 'Oops, seems there is an error in the error template.',
|
|
14
|
+
encounteredError: 'Encountered the error: ',
|
|
15
|
+
whilstTryingToRender: 'whilst trying to render an error page for the error: '
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const escapeExpression = hbs.Utils.escapeExpression;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* This is a bare minimum setup, which allows us to render the error page
|
|
22
|
+
* It uses the {{asset}} helper, and nothing more
|
|
23
|
+
*/
|
|
24
|
+
const createHbsEngine = () => {
|
|
25
|
+
const engine = hbs.create();
|
|
26
|
+
engine.registerHelper('asset', require('../../helpers/asset'));
|
|
27
|
+
|
|
28
|
+
return engine.express4();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const errorFallbackMessage = err => `<h1>${tpl(messages.oopsErrorTemplateHasError)}</h1>
|
|
32
|
+
<p>${tpl(messages.encounteredError)}</p>
|
|
33
|
+
<pre>${escapeExpression(err.message || err)}</pre>
|
|
34
|
+
<br ><p>${tpl(messages.whilstTryingToRender)}</p>
|
|
35
|
+
${err.statusCode} <pre>${escapeExpression(err.message || err)}</pre>`;
|
|
36
|
+
|
|
37
|
+
const themeErrorRenderer = (err, req, res, next) => {
|
|
38
|
+
// If the error code is explicitly set to STATIC_FILE_NOT_FOUND,
|
|
39
|
+
// Skip trying to render an HTML error, and move on to the basic error renderer
|
|
40
|
+
// We do this because customised 404 templates could reference the image that's missing
|
|
41
|
+
// A better long term solution might be to do this based on extension
|
|
42
|
+
if (err.code === 'STATIC_FILE_NOT_FOUND') {
|
|
43
|
+
return next(err);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Renderer begin
|
|
47
|
+
// Format Data
|
|
48
|
+
const data = {
|
|
49
|
+
message: err.message,
|
|
50
|
+
// @deprecated Remove in Ghost 5.0
|
|
51
|
+
code: err.statusCode,
|
|
52
|
+
statusCode: err.statusCode,
|
|
53
|
+
errorDetails: err.errorDetails || []
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Template
|
|
57
|
+
// @TODO: very dirty !!!!!!
|
|
58
|
+
helpers.templates.setTemplate(req, res);
|
|
59
|
+
|
|
60
|
+
// It can be that something went wrong with the theme or otherwise loading handlebars
|
|
61
|
+
// This ensures that no matter what res.render will work here
|
|
62
|
+
// @TODO: split the error handler for assets, admin & theme to refactor this away
|
|
63
|
+
if (_.isEmpty(req.app.engines)) {
|
|
64
|
+
res._template = 'error';
|
|
65
|
+
req.app.engine('hbs', createHbsEngine());
|
|
66
|
+
req.app.set('view engine', 'hbs');
|
|
67
|
+
req.app.set('views', config.get('paths').defaultViews);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// @TODO use renderer here?!
|
|
71
|
+
// Render Call - featuring an error handler for what happens if rendering fails
|
|
72
|
+
res.render(res._template, data, (_err, html) => {
|
|
73
|
+
if (!_err) {
|
|
74
|
+
return res.send(html);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// re-attach new error e.g. error template has syntax error or misusage
|
|
78
|
+
req.err = _err;
|
|
79
|
+
|
|
80
|
+
// And then try to explain things to the user...
|
|
81
|
+
// Cheat and output the error using handlebars escapeExpression
|
|
82
|
+
return res.status(500).send(errorFallbackMessage(_err));
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
module.exports.handleThemeResponse = [
|
|
87
|
+
// Make sure the error can be served
|
|
88
|
+
shared.prepareError,
|
|
89
|
+
// Handle the error in Sentry
|
|
90
|
+
sentry.errorHandler,
|
|
91
|
+
// Render the error using theme template
|
|
92
|
+
themeErrorRenderer
|
|
93
|
+
];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const {
|
|
3
|
+
const {NoContentError} = require('@tryghost/errors');
|
|
4
4
|
const imageTransform = require('@tryghost/image-transform');
|
|
5
5
|
const storage = require('../../../server/adapters/storage');
|
|
6
6
|
const activeTheme = require('../../services/theme-engine/active');
|
|
@@ -102,10 +102,7 @@ module.exports = function (req, res, next) {
|
|
|
102
102
|
})
|
|
103
103
|
.then((originalImageBuffer) => {
|
|
104
104
|
if (originalImageBuffer.length <= 0) {
|
|
105
|
-
throw new
|
|
106
|
-
errorType: 'NoContentError',
|
|
107
|
-
statusCode: 204
|
|
108
|
-
});
|
|
105
|
+
throw new NoContentError();
|
|
109
106
|
}
|
|
110
107
|
return imageTransform.resizeFromBuffer(originalImageBuffer, imageDimensionConfig);
|
|
111
108
|
})
|
|
@@ -115,7 +112,7 @@ module.exports = function (req, res, next) {
|
|
|
115
112
|
}).then(() => {
|
|
116
113
|
next();
|
|
117
114
|
}).catch(function (err) {
|
|
118
|
-
if (err.code === 'SHARP_INSTALLATION' || err.errorType === 'NoContentError') {
|
|
115
|
+
if (err.code === 'SHARP_INSTALLATION' || err.code === 'IMAGE_PROCESSING' || err.errorType === 'NoContentError') {
|
|
119
116
|
return redirectToOriginal();
|
|
120
117
|
}
|
|
121
118
|
next(err);
|
|
@@ -184,7 +184,7 @@ module.exports = function setupSiteApp(options = {}) {
|
|
|
184
184
|
app.setupErrorHandling(siteApp);
|
|
185
185
|
}
|
|
186
186
|
});
|
|
187
|
-
siteApp.use(
|
|
187
|
+
siteApp.use(mw.errorHandler.handleThemeResponse);
|
|
188
188
|
|
|
189
189
|
debug('Site setup end');
|
|
190
190
|
|
|
@@ -307,7 +307,7 @@ SchedulingDefault.prototype._pingUrl = function (object) {
|
|
|
307
307
|
this._pingUrl(object);
|
|
308
308
|
}, this.retryTimeoutInMs);
|
|
309
309
|
|
|
310
|
-
logging.error(new errors.
|
|
310
|
+
logging.error(new errors.InternalServerError({
|
|
311
311
|
err,
|
|
312
312
|
context: 'Retrying...',
|
|
313
313
|
level: 'normal'
|
|
@@ -316,7 +316,7 @@ SchedulingDefault.prototype._pingUrl = function (object) {
|
|
|
316
316
|
return;
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
-
logging.error(new errors.
|
|
319
|
+
logging.error(new errors.InternalServerError({
|
|
320
320
|
err,
|
|
321
321
|
level: 'critical'
|
|
322
322
|
}));
|
|
@@ -147,7 +147,7 @@ class LocalStorageBase extends StorageBase {
|
|
|
147
147
|
return next(new errors.NoPermissionError({err: err}));
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
return next(new errors.
|
|
150
|
+
return next(new errors.InternalServerError({err: err}));
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
next();
|
|
@@ -196,7 +196,7 @@ class LocalStorageBase extends StorageBase {
|
|
|
196
196
|
return reject(new errors.NoPermissionError({err: err}));
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
return reject(new errors.
|
|
199
|
+
return reject(new errors.InternalServerError({
|
|
200
200
|
err: err,
|
|
201
201
|
message: tpl(this.errorMessages.cannotRead, {file: options.path})
|
|
202
202
|
}));
|
|
@@ -62,7 +62,7 @@ module.exports = {
|
|
|
62
62
|
return Promise.resolve()
|
|
63
63
|
.then(() => exporter.doExport({include: frame.options.withRelated}))
|
|
64
64
|
.catch((err) => {
|
|
65
|
-
return Promise.reject(new errors.
|
|
65
|
+
return Promise.reject(new errors.InternalServerError({err: err}));
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
},
|
|
@@ -124,7 +124,7 @@ module.exports = {
|
|
|
124
124
|
}, {concurrency: 100});
|
|
125
125
|
})
|
|
126
126
|
.catch((err) => {
|
|
127
|
-
throw new errors.
|
|
127
|
+
throw new errors.InternalServerError({
|
|
128
128
|
err: err
|
|
129
129
|
});
|
|
130
130
|
});
|
|
@@ -3,9 +3,24 @@ const externalRequest = require('../../lib/request-external');
|
|
|
3
3
|
|
|
4
4
|
const OEmbed = require('../../services/oembed');
|
|
5
5
|
const oembed = new OEmbed({config, externalRequest});
|
|
6
|
+
|
|
6
7
|
const NFT = require('../../services/nft-oembed');
|
|
7
|
-
const nft = new NFT(
|
|
8
|
+
const nft = new NFT({
|
|
9
|
+
config: {
|
|
10
|
+
apiKey: config.get('opensea').privateReadOnlyApiKey
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const Twitter = require('../../services/twitter-embed');
|
|
15
|
+
const twitter = new Twitter({
|
|
16
|
+
config: {
|
|
17
|
+
bearerToken: config.get('twitter').privateReadOnlyToken
|
|
18
|
+
},
|
|
19
|
+
logging: require('@tryghost/logging')
|
|
20
|
+
});
|
|
21
|
+
|
|
8
22
|
oembed.registerProvider(nft);
|
|
23
|
+
oembed.registerProvider(twitter);
|
|
9
24
|
|
|
10
25
|
module.exports = {
|
|
11
26
|
docName: 'oembed',
|
|
@@ -43,7 +43,7 @@ module.exports = {
|
|
|
43
43
|
return models.Base.Model.generateSlug(allowedTypes[frame.options.type], frame.data.name, {status: 'all'})
|
|
44
44
|
.then((slug) => {
|
|
45
45
|
if (!slug) {
|
|
46
|
-
return Promise.reject(new errors.
|
|
46
|
+
return Promise.reject(new errors.InternalServerError({
|
|
47
47
|
message: tpl(messages.couldNotGenerateSlug)
|
|
48
48
|
}));
|
|
49
49
|
}
|
|
@@ -66,11 +66,11 @@ const nonePublicAuth = (apiConfig, frame) => {
|
|
|
66
66
|
return Promise.reject(err);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
if (errors.utils.
|
|
69
|
+
if (errors.utils.isGhostError(err)) {
|
|
70
70
|
return Promise.reject(err);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
return Promise.reject(new errors.
|
|
73
|
+
return Promise.reject(new errors.InternalServerError({
|
|
74
74
|
err: err
|
|
75
75
|
}));
|
|
76
76
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
|
-
const labs = require('../../../../../../shared/labs');
|
|
3
2
|
const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:config');
|
|
4
3
|
|
|
5
4
|
module.exports = {
|
|
@@ -18,13 +17,10 @@ module.exports = {
|
|
|
18
17
|
'stripeDirect',
|
|
19
18
|
'mailgunIsConfigured',
|
|
20
19
|
'emailAnalytics',
|
|
21
|
-
'hostSettings'
|
|
20
|
+
'hostSettings',
|
|
21
|
+
'tenor'
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
-
if (labs.isSet('gifsCard')) {
|
|
25
|
-
keys.push('tenor');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
24
|
frame.response = {
|
|
29
25
|
config: _.pick(data, keys)
|
|
30
26
|
};
|