ghost 4.22.4 → 4.25.1
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/app.js +12 -1
- package/core/boot.js +33 -19
- package/core/bridge.js +10 -10
- package/core/built/assets/ghost-dark-f67240a9636407594be38571c615629c.css +1 -0
- package/core/built/assets/{ghost.min-2e3e64eb258cf424c59c3e308b4bc6e6.js → ghost.min-3441c3282e390002626a2dc1d7586185.js} +544 -619
- package/core/built/assets/ghost.min-ee5bd95a831378b4c8ccefb37d26eac0.css +1 -0
- package/core/built/assets/icons/audio-upload.svg +8 -0
- package/core/built/assets/{vendor.min-c9002845b6c30ac978abdadde9f33d7c.js → vendor.min-6fc912d1248c906f95efad2cb3eebb7d.js} +2656 -2118
- package/core/frontend/apps/amp/lib/helpers/amp_content.js +2 -2
- package/core/frontend/apps/amp/lib/views/amp.hbs +70 -0
- package/core/frontend/apps/private-blogging/index.js +1 -1
- package/core/frontend/helpers/url.js +18 -1
- package/core/frontend/services/apps/index.js +1 -1
- package/core/frontend/services/apps/loader.js +3 -3
- package/core/frontend/services/card-assets/index.js +0 -12
- package/core/frontend/services/card-assets/service.js +22 -21
- 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/blockquote.css +29 -0
- package/core/frontend/src/cards/css/bookmark.css +7 -0
- package/core/frontend/src/cards/css/button.css +4 -0
- package/core/frontend/src/cards/css/callout.css +23 -15
- package/core/frontend/src/cards/css/gallery.css +13 -3
- package/core/frontend/src/cards/css/toggle.css +36 -16
- 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/middleware/serve-public-file.js +25 -8
- package/core/frontend/web/site.js +2 -5
- 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 +59 -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/email-analytics/jobs/index.js +1 -1
- package/core/server/services/mega/mega.js +6 -4
- package/core/server/services/mega/template.js +31 -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 +36 -28
- package/core/server/services/permissions/can-this.js +1 -1
- package/core/server/services/redirects/api.js +20 -25
- package/core/server/services/redirects/index.js +18 -10
- package/core/server/services/redirects/utils.js +14 -0
- package/core/server/services/redirects/validation.js +10 -0
- package/core/server/services/route-settings/default-settings-manager.js +1 -1
- package/core/server/services/route-settings/index.js +40 -17
- package/core/server/services/route-settings/route-settings.js +120 -115
- package/core/server/services/route-settings/settings-loader.js +18 -36
- package/core/server/services/route-settings/yaml-parser.js +1 -1
- package/core/server/services/slack.js +1 -1
- package/core/server/services/themes/activation-bridge.js +3 -3
- package/core/server/services/themes/storage.js +2 -2
- package/core/server/services/twitter-embed.js +80 -0
- package/core/server/services/url/LocalFileCache.js +75 -0
- package/core/server/services/url/UrlService.js +15 -47
- package/core/server/services/url/index.js +17 -4
- package/core/server/services/xmlrpc.js +2 -2
- package/core/server/web/admin/app.js +2 -5
- package/core/server/web/admin/controller.js +35 -12
- package/core/server/web/admin/middleware/redirect-admin-urls.js +15 -0
- 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/app.js +0 -3
- package/core/server/web/api/canary/admin/middleware.js +1 -1
- package/core/server/web/api/canary/content/app.js +0 -3
- package/core/server/web/api/v2/admin/app.js +0 -3
- package/core/server/web/api/v2/admin/middleware.js +1 -1
- package/core/server/web/api/v2/content/app.js +0 -3
- package/core/server/web/api/v3/admin/app.js +0 -3
- package/core/server/web/api/v3/admin/middleware.js +1 -1
- package/core/server/web/api/v3/content/app.js +0 -3
- package/core/server/web/members/app.js +0 -3
- package/core/server/web/oauth/app.js +0 -4
- package/core/server/web/parent/app.js +17 -8
- package/core/server/web/shared/middleware/error-handler.js +57 -162
- package/core/server/web/shared/middleware/index.js +0 -4
- package/core/shared/config/defaults.json +7 -1
- package/core/shared/labs.js +10 -5
- package/core/shared/sentry.js +1 -1
- package/package.json +43 -42
- package/yarn.lock +802 -923
- package/content/themes/casper/assets/js/gallery-card.js +0 -24
- package/core/built/assets/ghost-dark-42cf6e0c730578940ec069bda45aea41.css +0 -1
- package/core/built/assets/ghost.min-fcf6a0738421f86c47c55f20d00c5ba9.css +0 -1
- package/core/frontend/services/theme-engine/middleware.js +0 -209
- package/core/server/web/shared/middleware/maintenance.js +0 -25
|
@@ -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
|
|
@@ -281,6 +281,18 @@
|
|
|
281
281
|
background: var(--ghost-accent-color, #1292EE);
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
.post-content blockquote.kg-blockquote-alt {
|
|
285
|
+
font-size: 2rem;
|
|
286
|
+
line-height: 1.7;
|
|
287
|
+
text-align: center;
|
|
288
|
+
color: #738a94;
|
|
289
|
+
padding: 1rem 6rem 1.5rem;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.post-content blockquote.kg-blockquote-alt::before {
|
|
293
|
+
display: none;
|
|
294
|
+
}
|
|
295
|
+
|
|
284
296
|
.post-content :not(.kg-card):not([id]) + .kg-card {
|
|
285
297
|
margin-top: 40px;
|
|
286
298
|
}
|
|
@@ -457,6 +469,14 @@
|
|
|
457
469
|
margin: 0 .5em;
|
|
458
470
|
}
|
|
459
471
|
|
|
472
|
+
.kg-toggle-card-icon {
|
|
473
|
+
display: none;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.kg-toggle-content {
|
|
477
|
+
margin-top: 0.8rem;
|
|
478
|
+
}
|
|
479
|
+
|
|
460
480
|
.kg-nft-card-container {
|
|
461
481
|
position: relative;
|
|
462
482
|
display: flex;
|
|
@@ -561,6 +581,56 @@
|
|
|
561
581
|
color: #fff;
|
|
562
582
|
}
|
|
563
583
|
|
|
584
|
+
.kg-callout-card {
|
|
585
|
+
display: flex;
|
|
586
|
+
padding: 20px 28px;
|
|
587
|
+
border-radius: 3px;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.kg-callout-card-grey {
|
|
591
|
+
background: rgba(124, 139, 154, 0.13);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.kg-callout-card-white {
|
|
595
|
+
background: transparent;
|
|
596
|
+
box-shadow: inset 0 0 0 1px rgba(124, 139, 154, 0.25);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.kg-callout-card-blue {
|
|
600
|
+
background: rgba(33, 172, 232, 0.12);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.kg-callout-card-green {
|
|
604
|
+
background: rgba(52, 183, 67, 0.12);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.kg-callout-card-yellow {
|
|
608
|
+
background: rgba(240, 165, 15, 0.13);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
.kg-callout-card-red {
|
|
612
|
+
background: rgba(209, 46, 46, 0.11);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.kg-callout-card-pink {
|
|
616
|
+
background: rgba(225, 71, 174, 0.11);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
.kg-callout-card-purple {
|
|
620
|
+
background: rgba(135, 85, 236, 0.12);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.kg-callout-card-accent {
|
|
624
|
+
background: var(--ghost-accent-color);
|
|
625
|
+
color: #fff;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.kg-callout-emoji {
|
|
629
|
+
padding-right: 16px;
|
|
630
|
+
line-height: 1.3;
|
|
631
|
+
font-size: 1.25em;
|
|
632
|
+
}
|
|
633
|
+
|
|
564
634
|
.kg-width-full.kg-card-hascaption {
|
|
565
635
|
display: grid;
|
|
566
636
|
grid-template-columns: inherit;
|
|
@@ -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)
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
const {metaData} = require('../services/proxy');
|
|
8
8
|
const {SafeString} = require('../services/rendering');
|
|
9
|
+
const logging = require('@tryghost/logging');
|
|
10
|
+
const sentry = require('../../shared/sentry');
|
|
11
|
+
const errors = require('@tryghost/errors');
|
|
9
12
|
|
|
10
13
|
const {getMetaDataUrl} = metaData;
|
|
11
14
|
|
|
@@ -13,7 +16,21 @@ module.exports = function url(options) {
|
|
|
13
16
|
const absolute = options && options.hash.absolute && options.hash.absolute !== 'false';
|
|
14
17
|
let outputUrl = getMetaDataUrl(this, absolute);
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
try {
|
|
20
|
+
outputUrl = encodeURI(decodeURI(outputUrl));
|
|
21
|
+
} catch (err) {
|
|
22
|
+
// Happens when the outputURL contains an invalid URI character like "%%" or "%80"
|
|
23
|
+
|
|
24
|
+
// Send the error not to be blind to these
|
|
25
|
+
const error = new errors.IncorrectUsageError({
|
|
26
|
+
message: `The url "${outputUrl}" couldn't be escaped correctly`,
|
|
27
|
+
err: err
|
|
28
|
+
});
|
|
29
|
+
sentry.captureException(error);
|
|
30
|
+
logging.error(error);
|
|
31
|
+
|
|
32
|
+
return new SafeString('');
|
|
33
|
+
}
|
|
17
34
|
|
|
18
35
|
return new SafeString(outputUrl);
|
|
19
36
|
};
|
|
@@ -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
|
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
const debug = require('@tryghost/debug')('card-assets');
|
|
2
|
-
const themeEngine = require('../theme-engine');
|
|
3
|
-
|
|
4
1
|
const CardAssetService = require('./service');
|
|
5
2
|
let cardAssetService = new CardAssetService();
|
|
6
3
|
|
|
7
|
-
const initFn = async () => {
|
|
8
|
-
const cardAssetConfig = themeEngine.getActive().config('card_assets');
|
|
9
|
-
debug('initialising with config', cardAssetConfig);
|
|
10
|
-
|
|
11
|
-
await cardAssetService.load(cardAssetConfig);
|
|
12
|
-
};
|
|
13
|
-
|
|
14
4
|
module.exports = cardAssetService;
|
|
15
|
-
|
|
16
|
-
module.exports.init = initFn;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const debug = require('@tryghost/debug')('card-assets');
|
|
1
2
|
const Minifier = require('@tryghost/minifier');
|
|
2
3
|
const _ = require('lodash');
|
|
3
4
|
const path = require('path');
|
|
@@ -52,35 +53,31 @@ class CardAssetService {
|
|
|
52
53
|
async minify(globs) {
|
|
53
54
|
try {
|
|
54
55
|
return await this.minifier.minify(globs);
|
|
55
|
-
} catch (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error.code === 'EACCES') {
|
|
58
|
+
logging.error('Ghost was not able to write card asset files due to permissions.');
|
|
59
|
+
return;
|
|
59
60
|
}
|
|
61
|
+
|
|
62
|
+
throw error;
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
async clearFiles() {
|
|
64
67
|
this.files = [];
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
} catch (error) {
|
|
70
|
-
// Don't worry if the file didn't exist or we don't have perms here
|
|
71
|
-
if (error.code !== 'ENOENT' && error.code !== 'EACCES') {
|
|
72
|
-
throw error;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
69
|
+
const rmFile = async (name) => {
|
|
70
|
+
await fs.unlink(path.join(this.dest, name));
|
|
71
|
+
};
|
|
75
72
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
73
|
+
let promises = [
|
|
74
|
+
// @deprecated switch this to use fs.rm when we drop support for Node v12
|
|
75
|
+
rmFile('cards.min.css'),
|
|
76
|
+
rmFile('cards.min.js')
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
// We don't care if removing these files fails as it's valid for them to not exist
|
|
80
|
+
return Promise.allSettled(promises);
|
|
84
81
|
}
|
|
85
82
|
|
|
86
83
|
hasFile(type) {
|
|
@@ -98,10 +95,14 @@ class CardAssetService {
|
|
|
98
95
|
this.config = cardAssetConfig;
|
|
99
96
|
}
|
|
100
97
|
|
|
98
|
+
debug('loading with config', cardAssetConfig);
|
|
99
|
+
|
|
101
100
|
await this.clearFiles();
|
|
102
101
|
|
|
103
102
|
const globs = this.generateGlobs();
|
|
104
103
|
|
|
104
|
+
debug('globs', globs);
|
|
105
|
+
|
|
105
106
|
this.files = await this.minify(globs) || [];
|
|
106
107
|
}
|
|
107
108
|
}
|
|
@@ -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;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
blockquote.kg-blockquote-alt {
|
|
2
|
+
background: none;
|
|
3
|
+
font-size: 2.4rem;
|
|
4
|
+
line-height: 1.7;
|
|
5
|
+
text-align: center;
|
|
6
|
+
color: var(--color-midgrey);
|
|
7
|
+
padding: 1rem 8rem 1.5rem;
|
|
8
|
+
border: 0 none;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
blockquote.kg-blockquote-alt::before {
|
|
12
|
+
display: none;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@media (max-width: 800px) {
|
|
16
|
+
blockquote.kg-blockquote-alt {
|
|
17
|
+
font-size: 2.2rem;
|
|
18
|
+
padding-left: 6rem;
|
|
19
|
+
padding-right: 6rem;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@media (max-width: 600px) {
|
|
24
|
+
blockquote.kg-blockquote-alt {
|
|
25
|
+
font-size: 2rem;
|
|
26
|
+
padding-left: 3rem;
|
|
27
|
+
padding-right: 3rem;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
align-items: flex-start;
|
|
22
22
|
justify-content: flex-start;
|
|
23
23
|
padding: 20px;
|
|
24
|
+
overflow: hidden;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
.kg-bookmark-title {
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
width: 100%;
|
|
50
51
|
font-size: 1.4rem;
|
|
51
52
|
font-weight: 500;
|
|
53
|
+
white-space: nowrap;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
.kg-bookmark-metadata > *:not(img) {
|
|
@@ -84,6 +86,11 @@
|
|
|
84
86
|
margin: 0 6px;
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
.kg-bookmark-metadata > span:last-of-type {
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
text-overflow: ellipsis;
|
|
92
|
+
}
|
|
93
|
+
|
|
87
94
|
.kg-bookmark-thumbnail {
|
|
88
95
|
position: relative;
|
|
89
96
|
flex-grow: 1;
|
|
@@ -1,50 +1,58 @@
|
|
|
1
|
-
.kg-card
|
|
1
|
+
.kg-callout-card {
|
|
2
2
|
display: flex;
|
|
3
|
-
|
|
4
|
-
padding: 20px 28px;
|
|
3
|
+
padding: 1.2em 1.6em;
|
|
5
4
|
border-radius: 3px;
|
|
6
5
|
}
|
|
7
6
|
|
|
8
|
-
.kg-card-
|
|
7
|
+
.kg-callout-card-grey {
|
|
9
8
|
background: rgba(124, 139, 154, 0.13);
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
.kg-card-
|
|
11
|
+
.kg-callout-card-white {
|
|
13
12
|
background: transparent;
|
|
14
13
|
box-shadow: inset 0 0 0 1px rgba(124, 139, 154, 0.25);
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
.kg-card-
|
|
16
|
+
.kg-callout-card-blue {
|
|
18
17
|
background: rgba(33, 172, 232, 0.12);
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
.kg-card-
|
|
20
|
+
.kg-callout-card-green {
|
|
22
21
|
background: rgba(52, 183, 67, 0.12);
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
.kg-card-
|
|
24
|
+
.kg-callout-card-yellow {
|
|
26
25
|
background: rgba(240, 165, 15, 0.13);
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
.kg-card-
|
|
28
|
+
.kg-callout-card-red {
|
|
30
29
|
background: rgba(209, 46, 46, 0.11);
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
.kg-card-
|
|
32
|
+
.kg-callout-card-pink {
|
|
34
33
|
background: rgba(225, 71, 174, 0.11);
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
.kg-card-
|
|
36
|
+
.kg-callout-card-purple {
|
|
38
37
|
background: rgba(135, 85, 236, 0.12);
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
.kg-card-
|
|
40
|
+
.kg-callout-card-accent {
|
|
42
41
|
background: var(--ghost-accent-color);
|
|
43
42
|
color: #fff;
|
|
44
43
|
}
|
|
45
44
|
|
|
45
|
+
.kg-callout-card-accent a {
|
|
46
|
+
color: #fff;
|
|
47
|
+
}
|
|
48
|
+
|
|
46
49
|
.kg-callout-emoji {
|
|
47
|
-
padding-right:
|
|
48
|
-
line-height: 1.
|
|
49
|
-
font-size:
|
|
50
|
+
padding-right: .8em;
|
|
51
|
+
line-height: 1.3em;
|
|
52
|
+
font-size: 1.2em;
|
|
50
53
|
}
|
|
54
|
+
|
|
55
|
+
.kg-callout-text {
|
|
56
|
+
font-size: .95em;
|
|
57
|
+
line-height: 1.5em;
|
|
58
|
+
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
.kg-gallery-card {
|
|
2
|
+
--gap: 1.2rem;
|
|
3
|
+
}
|
|
4
|
+
|
|
1
5
|
.kg-image-card:not(.kg-card-hascaption) + .kg-image-card,
|
|
2
6
|
.kg-image-card:not(.kg-card-hascaption) + .kg-gallery-card,
|
|
3
7
|
.kg-gallery-card:not(.kg-card-hascaption) + .kg-image-card,
|
|
4
8
|
.kg-gallery-card:not(.kg-card-hascaption) + .kg-gallery-card {
|
|
5
|
-
margin-top:
|
|
9
|
+
margin-top: var(--gap);
|
|
6
10
|
}
|
|
7
11
|
|
|
8
12
|
.kg-gallery-container {
|
|
@@ -23,9 +27,15 @@
|
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
.kg-gallery-row:not(:first-of-type) {
|
|
26
|
-
margin:
|
|
30
|
+
margin: var(--gap) 0 0;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
.kg-gallery-image:not(:first-of-type) {
|
|
30
|
-
margin: 0 0 0
|
|
34
|
+
margin: 0 0 0 var(--gap);
|
|
31
35
|
}
|
|
36
|
+
|
|
37
|
+
@media (max-width: 600px) {
|
|
38
|
+
.kg-gallery-card {
|
|
39
|
+
--gap: 0.6rem;
|
|
40
|
+
}
|
|
41
|
+
}
|