ghost 5.14.2 → 5.15.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/components/{tryghost-adapter-manager-5.14.2.tgz → tryghost-adapter-manager-5.15.0.tgz} +0 -0
- package/components/tryghost-api-framework-5.15.0.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.15.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.15.0.tgz +0 -0
- package/components/tryghost-constants-5.15.0.tgz +0 -0
- package/components/{tryghost-custom-theme-settings-service-5.14.2.tgz → tryghost-custom-theme-settings-service-5.15.0.tgz} +0 -0
- package/components/tryghost-domain-events-5.15.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.15.0.tgz +0 -0
- package/components/{tryghost-email-analytics-service-5.14.2.tgz → tryghost-email-analytics-service-5.15.0.tgz} +0 -0
- package/components/tryghost-email-content-generator-5.15.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.15.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.15.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.15.0.tgz +0 -0
- package/components/{tryghost-job-manager-5.14.2.tgz → tryghost-job-manager-5.15.0.tgz} +0 -0
- package/components/tryghost-link-redirects-5.15.0.tgz +0 -0
- package/components/tryghost-link-replacement-5.15.0.tgz +0 -0
- package/components/tryghost-link-tracking-5.15.0.tgz +0 -0
- package/components/{tryghost-magic-link-5.14.2.tgz → tryghost-magic-link-5.15.0.tgz} +0 -0
- package/components/tryghost-mailgun-client-5.15.0.tgz +0 -0
- package/components/tryghost-member-analytics-service-5.15.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.15.0.tgz +0 -0
- package/components/tryghost-member-events-5.15.0.tgz +0 -0
- package/components/tryghost-members-analytics-ingress-5.15.0.tgz +0 -0
- package/components/tryghost-members-api-5.15.0.tgz +0 -0
- package/components/tryghost-members-csv-5.15.0.tgz +0 -0
- package/components/tryghost-members-events-service-5.15.0.tgz +0 -0
- package/components/tryghost-members-importer-5.15.0.tgz +0 -0
- package/components/tryghost-members-offers-5.15.0.tgz +0 -0
- package/components/{tryghost-members-payments-5.14.2.tgz → tryghost-members-payments-5.15.0.tgz} +0 -0
- package/components/{tryghost-members-ssr-5.14.2.tgz → tryghost-members-ssr-5.15.0.tgz} +0 -0
- package/components/tryghost-members-stripe-service-5.15.0.tgz +0 -0
- package/components/tryghost-minifier-5.15.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.15.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.15.0.tgz +0 -0
- package/components/{tryghost-mw-error-handler-5.14.2.tgz → tryghost-mw-error-handler-5.15.0.tgz} +0 -0
- package/components/tryghost-mw-session-from-token-5.15.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.15.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.15.0.tgz +0 -0
- package/components/{tryghost-oembed-service-5.14.2.tgz → tryghost-oembed-service-5.15.0.tgz} +0 -0
- package/components/{tryghost-package-json-5.14.2.tgz → tryghost-package-json-5.15.0.tgz} +0 -0
- package/components/tryghost-security-5.15.0.tgz +0 -0
- package/components/{tryghost-session-service-5.14.2.tgz → tryghost-session-service-5.15.0.tgz} +0 -0
- package/components/{tryghost-settings-path-manager-5.14.2.tgz → tryghost-settings-path-manager-5.15.0.tgz} +0 -0
- package/components/tryghost-staff-service-5.15.0.tgz +0 -0
- package/components/{tryghost-update-check-service-5.14.2.tgz → tryghost-update-check-service-5.15.0.tgz} +0 -0
- package/components/{tryghost-verification-trigger-5.14.2.tgz → tryghost-verification-trigger-5.15.0.tgz} +0 -0
- package/components/{tryghost-version-notifications-data-service-5.14.2.tgz → tryghost-version-notifications-data-service-5.15.0.tgz} +0 -0
- package/content/themes/casper/default.hbs +2 -2
- package/core/boot.js +12 -3
- package/core/built/admin/assets/{chunk.143.a5ef705453da0d58b75a.js → chunk.143.558b9943af7b15f189ae.js} +20 -20
- package/core/built/admin/assets/{chunk.174.2edaa0869bfc2d88cf90.js → chunk.174.e1e89637eab79fdd5c5d.js} +68 -68
- package/core/built/admin/assets/{chunk.178.579a6edabc75a2d7378f.js → chunk.178.a9f6ddaea01e2bc76235.js} +4 -4
- package/core/built/admin/assets/{chunk.579.2de3f4300baf25f9a0db.js → chunk.579.dc11bf8dda5cf4406708.js} +5464 -4961
- package/core/built/admin/assets/{chunk.579.2de3f4300baf25f9a0db.js.LICENSE.txt → chunk.579.dc11bf8dda5cf4406708.js.LICENSE.txt} +0 -0
- package/core/built/admin/assets/fonts/{Inter.ttf → Inter-e19174fb2c0e19b1fa67492a07886c75.ttf} +0 -0
- package/core/built/admin/assets/{ghost-8919656440ad4617a07bb31069b1f71b.js → ghost-4b1b550e34300f5f4774a261aac29557.js} +487 -470
- package/core/built/admin/assets/ghost-c933adafb359b75ea1577365ce252e76.css +1 -0
- package/core/built/admin/assets/ghost-dark-04981c84bf590e0fae0a8e83e018190f.css +1 -0
- package/core/built/admin/assets/img/{amp.svg → amp-d7b72aae3315fda95921fb575dfca100.svg} +0 -0
- package/core/built/admin/assets/img/{disqus.svg → disqus-43503a3fa4f38dc8c61c7358b811f343.svg} +0 -0
- package/core/built/admin/assets/img/{favicon.ico → favicon-a9c6dbdcdc3ae568f4e0dad92149a0e3.ico} +0 -0
- package/core/built/admin/assets/img/{github.svg → github-c3a739c59df26fed12c10ffb00b33bd4.svg} +0 -0
- package/core/built/admin/assets/img/{google-docs.svg → google-docs-1e42cc272fc088da49e4b0ddfb01b006.svg} +0 -0
- package/core/built/admin/assets/img/{mailchimp.svg → mailchimp-f22b1e130aac764965b9306d7265a6b2.svg} +0 -0
- package/core/built/admin/assets/img/{patreon.svg → patreon-b19a5e6418a72977a16b30039d374d04.svg} +0 -0
- package/core/built/admin/assets/img/{paypal.svg → paypal-38e9448ce7549ea4caf8e7753ae661d6.svg} +0 -0
- package/core/built/admin/assets/img/{twitter.svg → twitter-7a7a0ba12d9b5bfb8a2058764a827c31.svg} +0 -0
- package/core/built/admin/assets/img/{typeform.svg → typeform-9f23f8712d776a7515594676285266f5.svg} +0 -0
- package/core/built/admin/assets/img/{unsplash.svg → unsplash-5b329eef0b11447b4117eaf817ebad6f.svg} +0 -0
- package/core/built/admin/assets/img/{zapier.svg → zapier-bf93bc440a3fd43b73489a63c215cdc7.svg} +0 -0
- package/core/built/admin/assets/img/{zapier-logo.svg → zapier-logo-a125f24313dfe01ef49af01fc90061fb.svg} +0 -0
- package/core/built/admin/assets/{vendor-eb76d0236a09b8b6f44675dba45b1fc6.js → vendor-271c32988ab16ba175a9bfa2acb2887a.js} +45 -39
- package/core/built/admin/assets/videos/logo-loader.mp4 +0 -0
- package/core/built/admin/index.html +11 -8
- package/core/frontend/src/member-attribution/member-attribution.js +27 -0
- package/core/frontend/web/site.js +10 -7
- package/core/server/api/endpoints/redirects.js +6 -8
- package/core/server/api/endpoints/utils/permissions.js +2 -16
- package/core/server/api/endpoints/utils/serializers/input/pages.js +5 -5
- package/core/server/api/endpoints/utils/serializers/input/posts.js +7 -7
- package/core/server/api/endpoints/utils/serializers/input/settings.js +1 -0
- package/core/server/api/endpoints/utils/validators/input/pages.js +24 -9
- package/core/server/api/endpoints/utils/validators/input/posts.js +24 -9
- package/core/server/data/exporter/table-lists.js +1 -0
- package/core/server/data/migrations/utils/settings.js +1 -3
- package/core/server/data/migrations/versions/5.15/2022-09-12-16-10-add-posts-lexical-column.js +8 -0
- package/core/server/data/migrations/versions/5.15/2022-09-14-12-46-add-email-track-clicks-setting.js +8 -0
- package/core/server/data/migrations/versions/5.15/2022-09-16-08-22-add-post-revisions-table.js +9 -0
- package/core/server/data/schema/default-settings/default-settings.json +8 -0
- package/core/server/data/schema/schema.js +8 -0
- package/core/server/lib/lexical.js +12 -0
- package/core/server/models/base/plugins/user-type.js +4 -6
- package/core/server/models/post-revision.js +35 -0
- package/core/server/models/post.js +72 -7
- package/core/server/services/bulk-email/bulk-email-processor.js +2 -5
- package/core/server/services/{redirects → custom-redirects}/api.js +0 -0
- package/core/server/services/{redirects → custom-redirects}/index.js +0 -0
- package/core/server/services/{redirects → custom-redirects}/utils.js +0 -0
- package/core/server/services/{redirects → custom-redirects}/validation.js +0 -0
- package/core/server/services/explore/service.js +5 -3
- package/core/server/services/link-click-tracking/index.js +25 -0
- package/core/server/services/link-redirection/index.js +33 -0
- package/core/server/services/link-replacement/index.js +24 -0
- package/core/server/services/mega/email-preview.js +7 -0
- package/core/server/services/mega/mega.js +1 -1
- package/core/server/services/mega/post-email-serializer.js +75 -27
- package/core/server/services/members/api.js +0 -2
- package/core/server/services/permissions/index.js +1 -2
- package/core/server/services/posts/posts-service.js +7 -16
- package/core/server/services/posts/stats/post-stats.js +35 -0
- package/core/server/services/staff/index.js +10 -1
- package/core/server/services/url/config.js +2 -0
- package/core/shared/config/defaults.json +2 -2
- package/core/shared/config/overrides.json +3 -2
- package/core/shared/labs.js +4 -2
- package/package.json +97 -90
- package/yarn.lock +395 -198
- package/components/tryghost-api-framework-5.14.2.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.14.2.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.14.2.tgz +0 -0
- package/components/tryghost-constants-5.14.2.tgz +0 -0
- package/components/tryghost-domain-events-5.14.2.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.14.2.tgz +0 -0
- package/components/tryghost-email-content-generator-5.14.2.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.14.2.tgz +0 -0
- package/components/tryghost-extract-api-key-5.14.2.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.14.2.tgz +0 -0
- package/components/tryghost-mailgun-client-5.14.2.tgz +0 -0
- package/components/tryghost-member-analytics-service-5.14.2.tgz +0 -0
- package/components/tryghost-member-attribution-5.14.2.tgz +0 -0
- package/components/tryghost-member-events-5.14.2.tgz +0 -0
- package/components/tryghost-members-analytics-ingress-5.14.2.tgz +0 -0
- package/components/tryghost-members-api-5.14.2.tgz +0 -0
- package/components/tryghost-members-csv-5.14.2.tgz +0 -0
- package/components/tryghost-members-events-service-5.14.2.tgz +0 -0
- package/components/tryghost-members-importer-5.14.2.tgz +0 -0
- package/components/tryghost-members-offers-5.14.2.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.14.2.tgz +0 -0
- package/components/tryghost-minifier-5.14.2.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.14.2.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.14.2.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.14.2.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.14.2.tgz +0 -0
- package/components/tryghost-mw-vhost-5.14.2.tgz +0 -0
- package/components/tryghost-security-5.14.2.tgz +0 -0
- package/components/tryghost-staff-service-5.14.2.tgz +0 -0
- package/core/built/admin/assets/ghost-40adc8310dcdd0be163cbf7b9d89c59a.css +0 -1
- package/core/built/admin/assets/ghost-dark-13b669d50f494edf24d832b32ece2177.css +0 -1
- package/core/server/services/permissions/public.js +0 -76
|
@@ -14,7 +14,8 @@ const themeEngine = require('../services/theme-engine');
|
|
|
14
14
|
const themeMiddleware = themeEngine.middleware;
|
|
15
15
|
const membersService = require('../../server/services/members');
|
|
16
16
|
const offersService = require('../../server/services/offers');
|
|
17
|
-
const customRedirects = require('../../server/services/redirects');
|
|
17
|
+
const customRedirects = require('../../server/services/custom-redirects');
|
|
18
|
+
const linkRedirects = require('../../server/services/link-redirection');
|
|
18
19
|
const siteRoutes = require('./routes');
|
|
19
20
|
const shared = require('../../server/web/shared');
|
|
20
21
|
const errorHandler = require('@tryghost/mw-error-handler');
|
|
@@ -49,6 +50,8 @@ module.exports = function setupSiteApp(routerConfig) {
|
|
|
49
50
|
|
|
50
51
|
siteApp.use(offersService.middleware);
|
|
51
52
|
|
|
53
|
+
siteApp.use(linkRedirects.service.handleRequest);
|
|
54
|
+
|
|
52
55
|
// you can extend Ghost with a custom redirects file
|
|
53
56
|
// see https://github.com/TryGhost/Ghost/issues/7707
|
|
54
57
|
siteApp.use(customRedirects.middleware);
|
|
@@ -78,11 +81,11 @@ module.exports = function setupSiteApp(routerConfig) {
|
|
|
78
81
|
// Member attribution
|
|
79
82
|
siteApp.use(mw.servePublicFile('built', 'public/member-attribution.min.js', 'application/javascript', constants.ONE_YEAR_S));
|
|
80
83
|
|
|
81
|
-
// Serve
|
|
84
|
+
// Serve site images using the storage adapter
|
|
82
85
|
siteApp.use(STATIC_IMAGE_URL_PREFIX, mw.handleImageSizes, storage.getStorage('images').serve());
|
|
83
|
-
// Serve
|
|
86
|
+
// Serve site media using the storage adapter
|
|
84
87
|
siteApp.use(STATIC_MEDIA_URL_PREFIX, storage.getStorage('media').serve());
|
|
85
|
-
// Serve
|
|
88
|
+
// Serve site files using the storage adapter
|
|
86
89
|
siteApp.use(STATIC_FILES_URL_PREFIX, storage.getStorage('files').serve());
|
|
87
90
|
|
|
88
91
|
// Global handling for member session, ensures a member is logged in to the frontend
|
|
@@ -91,7 +94,7 @@ module.exports = function setupSiteApp(routerConfig) {
|
|
|
91
94
|
// /member/.well-known/* serves files (e.g. jwks.json) so it needs to be mounted before the prettyUrl mw to avoid trailing slashes
|
|
92
95
|
siteApp.use(
|
|
93
96
|
'/members/.well-known',
|
|
94
|
-
shared.middleware.cacheControl('public', {maxAge:
|
|
97
|
+
shared.middleware.cacheControl('public', {maxAge: constants.ONE_DAY_S}),
|
|
95
98
|
(req, res, next) => membersService.api.middleware.wellKnown(req, res, next)
|
|
96
99
|
);
|
|
97
100
|
|
|
@@ -127,7 +130,7 @@ module.exports = function setupSiteApp(routerConfig) {
|
|
|
127
130
|
|
|
128
131
|
// ### Caching
|
|
129
132
|
siteApp.use(function (req, res, next) {
|
|
130
|
-
// Site frontend is cacheable UNLESS request made by a member or
|
|
133
|
+
// Site frontend is cacheable UNLESS request made by a member or site is in private mode
|
|
131
134
|
if (req.member || res.isPrivateBlog) {
|
|
132
135
|
return shared.middleware.cacheControl('private')(req, res, next);
|
|
133
136
|
} else {
|
|
@@ -148,7 +151,7 @@ module.exports = function setupSiteApp(routerConfig) {
|
|
|
148
151
|
router = siteRoutes(routerConfig);
|
|
149
152
|
Object.setPrototypeOf(SiteRouter, router);
|
|
150
153
|
|
|
151
|
-
// Set up Frontend routes (including private
|
|
154
|
+
// Set up Frontend routes (including private site routes)
|
|
152
155
|
siteApp.use(SiteRouter);
|
|
153
156
|
|
|
154
157
|
// ### Error handlers
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const customRedirects = require('../../services/custom-redirects');
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
6
|
docName: 'redirects',
|
|
@@ -10,11 +10,9 @@ module.exports = {
|
|
|
10
10
|
disposition: {
|
|
11
11
|
type: 'file',
|
|
12
12
|
value() {
|
|
13
|
-
return
|
|
13
|
+
return customRedirects.api.getRedirectsFilePath()
|
|
14
14
|
.then((filePath) => {
|
|
15
|
-
//
|
|
16
|
-
// When .yaml becomes default or .json is removed at v4,
|
|
17
|
-
// This part should be changed.
|
|
15
|
+
// @deprecated: .json was deprecated in v4.0 but is still the default for backwards compat
|
|
18
16
|
return filePath === null || path.extname(filePath) === '.json'
|
|
19
17
|
? 'redirects.json'
|
|
20
18
|
: 'redirects.yaml';
|
|
@@ -25,13 +23,13 @@ module.exports = {
|
|
|
25
23
|
permissions: true,
|
|
26
24
|
response: {
|
|
27
25
|
async format() {
|
|
28
|
-
const filePath = await
|
|
26
|
+
const filePath = await customRedirects.api.getRedirectsFilePath();
|
|
29
27
|
|
|
30
28
|
return filePath === null || path.extname(filePath) === '.json' ? 'json' : 'plain';
|
|
31
29
|
}
|
|
32
30
|
},
|
|
33
31
|
query() {
|
|
34
|
-
return
|
|
32
|
+
return customRedirects.api.get();
|
|
35
33
|
}
|
|
36
34
|
},
|
|
37
35
|
|
|
@@ -41,7 +39,7 @@ module.exports = {
|
|
|
41
39
|
cacheInvalidate: true
|
|
42
40
|
},
|
|
43
41
|
query(frame) {
|
|
44
|
-
return
|
|
42
|
+
return customRedirects.api.setFromFilePath(frame.file.path, frame.file.ext);
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
45
|
};
|
|
@@ -93,22 +93,8 @@ module.exports = {
|
|
|
93
93
|
|
|
94
94
|
// CASE: Content API access
|
|
95
95
|
if (frame.options.context.public && frame.apiType !== 'comments') {
|
|
96
|
-
debug('
|
|
97
|
-
|
|
98
|
-
// @TODO: Remove when we drop v0.1
|
|
99
|
-
// @TODO: https://github.com/TryGhost/Ghost/issues/10733
|
|
100
|
-
return permissions.applyPublicRules(apiConfig.docName, apiConfig.method, {
|
|
101
|
-
status: frame.options.status,
|
|
102
|
-
id: frame.options.id,
|
|
103
|
-
uuid: frame.options.uuid,
|
|
104
|
-
slug: frame.options.slug,
|
|
105
|
-
data: {
|
|
106
|
-
status: frame.data.status,
|
|
107
|
-
id: frame.data.id,
|
|
108
|
-
uuid: frame.data.uuid,
|
|
109
|
-
slug: frame.data.slug
|
|
110
|
-
}
|
|
111
|
-
});
|
|
96
|
+
debug('content api permissions pass-through');
|
|
97
|
+
return Promise.resolve(frame.options);
|
|
112
98
|
}
|
|
113
99
|
|
|
114
100
|
return nonePublicAuth(apiConfig, frame);
|
|
@@ -7,10 +7,10 @@ const localUtils = require('../../index');
|
|
|
7
7
|
const postsMetaSchema = require('../../../../../data/schema').tables.posts_meta;
|
|
8
8
|
const clean = require('./utils/clean');
|
|
9
9
|
|
|
10
|
-
function
|
|
11
|
-
if (frame.options.formats
|
|
10
|
+
function removeSourceFormats(frame) {
|
|
11
|
+
if (frame.options.formats?.includes('mobiledoc') || frame.options.formats?.includes('lexical')) {
|
|
12
12
|
frame.options.formats = frame.options.formats.filter((format) => {
|
|
13
|
-
return
|
|
13
|
+
return !['mobiledoc', 'lexical'].includes(format);
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -95,7 +95,7 @@ module.exports = {
|
|
|
95
95
|
forcePageFilter(frame);
|
|
96
96
|
|
|
97
97
|
if (localUtils.isContentAPI(frame)) {
|
|
98
|
-
|
|
98
|
+
removeSourceFormats(frame);
|
|
99
99
|
setDefaultOrder(frame);
|
|
100
100
|
forceVisibilityColumn(frame);
|
|
101
101
|
}
|
|
@@ -113,7 +113,7 @@ module.exports = {
|
|
|
113
113
|
forcePageFilter(frame);
|
|
114
114
|
|
|
115
115
|
if (localUtils.isContentAPI(frame)) {
|
|
116
|
-
|
|
116
|
+
removeSourceFormats(frame);
|
|
117
117
|
setDefaultOrder(frame);
|
|
118
118
|
forceVisibilityColumn(frame);
|
|
119
119
|
}
|
|
@@ -7,10 +7,10 @@ const mobiledoc = require('../../../../../lib/mobiledoc');
|
|
|
7
7
|
const postsMetaSchema = require('../../../../../data/schema').tables.posts_meta;
|
|
8
8
|
const clean = require('./utils/clean');
|
|
9
9
|
|
|
10
|
-
function
|
|
11
|
-
if (frame.options.formats
|
|
10
|
+
function removeSourceFormats(frame) {
|
|
11
|
+
if (frame.options.formats?.includes('mobiledoc') || frame.options.formats?.includes('lexical')) {
|
|
12
12
|
frame.options.formats = frame.options.formats.filter((format) => {
|
|
13
|
-
return
|
|
13
|
+
return !['mobiledoc', 'lexical'].includes(format);
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -101,8 +101,8 @@ module.exports = {
|
|
|
101
101
|
* - user exists? admin api access
|
|
102
102
|
*/
|
|
103
103
|
if (localUtils.isContentAPI(frame)) {
|
|
104
|
-
// CASE: the content api endpoint for posts should not return mobiledoc
|
|
105
|
-
|
|
104
|
+
// CASE: the content api endpoint for posts should not return mobiledoc or lexical
|
|
105
|
+
removeSourceFormats(frame);
|
|
106
106
|
|
|
107
107
|
setDefaultOrder(frame);
|
|
108
108
|
forceVisibilityColumn(frame);
|
|
@@ -127,8 +127,8 @@ module.exports = {
|
|
|
127
127
|
* - user exists? admin api access
|
|
128
128
|
*/
|
|
129
129
|
if (localUtils.isContentAPI(frame)) {
|
|
130
|
-
// CASE: the content api endpoint for posts should not return mobiledoc
|
|
131
|
-
|
|
130
|
+
// CASE: the content api endpoint for posts should not return mobiledoc or lexical
|
|
131
|
+
removeSourceFormats(frame);
|
|
132
132
|
|
|
133
133
|
setDefaultOrder(frame);
|
|
134
134
|
forceVisibilityColumn(frame);
|
|
@@ -4,7 +4,8 @@ const {ValidationError} = require('@tryghost/errors');
|
|
|
4
4
|
const tpl = require('@tryghost/tpl');
|
|
5
5
|
|
|
6
6
|
const messages = {
|
|
7
|
-
invalidVisibilityFilter: 'Invalid filter in visibility_filter property'
|
|
7
|
+
invalidVisibilityFilter: 'Invalid filter in visibility_filter property',
|
|
8
|
+
onlySingleContentSource: 'It\'s only possible to save mobiledoc or lexical properties, not both'
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
const validateVisibility = async function (frame) {
|
|
@@ -33,15 +34,29 @@ const validateVisibility = async function (frame) {
|
|
|
33
34
|
}
|
|
34
35
|
};
|
|
35
36
|
|
|
37
|
+
const validateSingleContentSource = async function (frame) {
|
|
38
|
+
if (!frame.data.pages?.[0]) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const [page] = frame.data.pages;
|
|
43
|
+
if (page.mobiledoc && page.lexical) {
|
|
44
|
+
return Promise.reject(new ValidationError({
|
|
45
|
+
message: tpl(messages.onlySingleContentSource),
|
|
46
|
+
property: 'lexical'
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
36
51
|
module.exports = {
|
|
37
|
-
add(apiConfig, frame) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
async add(apiConfig, frame) {
|
|
53
|
+
await jsonSchema.validate(...arguments);
|
|
54
|
+
await validateVisibility(frame);
|
|
55
|
+
await validateSingleContentSource(frame);
|
|
41
56
|
},
|
|
42
|
-
edit(apiConfig, frame) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
57
|
+
async edit(apiConfig, frame) {
|
|
58
|
+
await jsonSchema.validate(...arguments);
|
|
59
|
+
await validateVisibility(frame);
|
|
60
|
+
await validateSingleContentSource(frame);
|
|
46
61
|
}
|
|
47
62
|
};
|
|
@@ -4,7 +4,8 @@ const {ValidationError} = require('@tryghost/errors');
|
|
|
4
4
|
const tpl = require('@tryghost/tpl');
|
|
5
5
|
|
|
6
6
|
const messages = {
|
|
7
|
-
invalidVisibilityFilter: 'Invalid filter in visibility_filter property'
|
|
7
|
+
invalidVisibilityFilter: 'Invalid filter in visibility_filter property',
|
|
8
|
+
onlySingleContentSource: 'It\'s only possible to save mobiledoc or lexical properties, not both'
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
const validateVisibility = async function (frame) {
|
|
@@ -33,15 +34,29 @@ const validateVisibility = async function (frame) {
|
|
|
33
34
|
}
|
|
34
35
|
};
|
|
35
36
|
|
|
37
|
+
const validateSingleContentSource = async function (frame) {
|
|
38
|
+
if (!frame.data.posts?.[0]) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const [post] = frame.data.posts;
|
|
43
|
+
if (post.mobiledoc && post.lexical) {
|
|
44
|
+
return Promise.reject(new ValidationError({
|
|
45
|
+
message: tpl(messages.onlySingleContentSource),
|
|
46
|
+
property: 'lexical'
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
36
51
|
module.exports = {
|
|
37
|
-
add(apiConfig, frame) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
async add(apiConfig, frame) {
|
|
53
|
+
await jsonSchema.validate(...arguments);
|
|
54
|
+
await validateVisibility(frame);
|
|
55
|
+
await validateSingleContentSource(frame);
|
|
41
56
|
},
|
|
42
|
-
edit(apiConfig, frame) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
57
|
+
async edit(apiConfig, frame) {
|
|
58
|
+
await jsonSchema.validate(...arguments);
|
|
59
|
+
await validateVisibility(frame);
|
|
60
|
+
await validateSingleContentSource(frame);
|
|
46
61
|
}
|
|
47
62
|
};
|
package/core/server/data/migrations/versions/5.15/2022-09-16-08-22-add-post-revisions-table.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const {addTable} = require('../../utils');
|
|
2
|
+
|
|
3
|
+
module.exports = addTable('post_revisions', {
|
|
4
|
+
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
5
|
+
post_id: {type: 'string', maxlength: 24, nullable: false, index: true},
|
|
6
|
+
lexical: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true},
|
|
7
|
+
created_at_ts: {type: 'bigInteger', nullable: false},
|
|
8
|
+
created_at: {type: 'dateTime', nullable: false}
|
|
9
|
+
});
|
|
@@ -360,6 +360,14 @@
|
|
|
360
360
|
},
|
|
361
361
|
"type": "boolean"
|
|
362
362
|
},
|
|
363
|
+
"email_track_clicks": {
|
|
364
|
+
"defaultValue": "true",
|
|
365
|
+
"validations": {
|
|
366
|
+
"isEmpty": false,
|
|
367
|
+
"isIn": [["true", "false"]]
|
|
368
|
+
},
|
|
369
|
+
"type": "boolean"
|
|
370
|
+
},
|
|
363
371
|
"email_verification_required": {
|
|
364
372
|
"defaultValue": "false",
|
|
365
373
|
"validations": {
|
|
@@ -45,6 +45,7 @@ module.exports = {
|
|
|
45
45
|
title: {type: 'string', maxlength: 2000, nullable: false, validations: {isLength: {max: 255}}},
|
|
46
46
|
slug: {type: 'string', maxlength: 191, nullable: false},
|
|
47
47
|
mobiledoc: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true},
|
|
48
|
+
lexical: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true},
|
|
48
49
|
html: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true},
|
|
49
50
|
comment_id: {type: 'string', maxlength: 50, nullable: true},
|
|
50
51
|
plaintext: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true},
|
|
@@ -386,6 +387,13 @@ module.exports = {
|
|
|
386
387
|
created_at_ts: {type: 'bigInteger', nullable: false},
|
|
387
388
|
created_at: {type: 'dateTime', nullable: false}
|
|
388
389
|
},
|
|
390
|
+
post_revisions: {
|
|
391
|
+
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
392
|
+
post_id: {type: 'string', maxlength: 24, nullable: false, index: true},
|
|
393
|
+
lexical: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true},
|
|
394
|
+
created_at_ts: {type: 'bigInteger', nullable: false},
|
|
395
|
+
created_at: {type: 'dateTime', nullable: false}
|
|
396
|
+
},
|
|
389
397
|
members: {
|
|
390
398
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
391
399
|
uuid: {type: 'string', maxlength: 36, nullable: true, unique: true, validations: {isUUID: true}},
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
let lexicalHtmlRenderer;
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
get lexicalHtmlRenderer() {
|
|
5
|
+
if (!lexicalHtmlRenderer) {
|
|
6
|
+
const LexicalHtmlRenderer = require('@tryghost/kg-lexical-html-renderer');
|
|
7
|
+
lexicalHtmlRenderer = new LexicalHtmlRenderer();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return lexicalHtmlRenderer;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
@@ -39,13 +39,11 @@ module.exports = function (Bookshelf) {
|
|
|
39
39
|
/**
|
|
40
40
|
* @NOTE:
|
|
41
41
|
*
|
|
42
|
-
* This is a dirty
|
|
43
|
-
*
|
|
42
|
+
* This is a dirty fix until we get rid of all the x_by columns
|
|
43
|
+
* @deprecated x_by columns are deprecated as of v1.0 - instead we should use the actions table
|
|
44
|
+
* see https://github.com/TryGhost/Ghost/issues/10286.
|
|
44
45
|
*
|
|
45
|
-
* We return the owner ID '1' in case an integration updates or creates
|
|
46
|
-
* resources. v0.1 will continue to use the `x_by` columns. v0.1 does not support integrations.
|
|
47
|
-
* API v2 will introduce a new feature to solve inserting/updating resources
|
|
48
|
-
* from users or integrations. API v2 won't expose `x_by` columns anymore.
|
|
46
|
+
* We return the owner ID '1' in case an integration updates or creates resources.
|
|
49
47
|
*
|
|
50
48
|
* ---
|
|
51
49
|
*
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const ghostBookshelf = require('./base');
|
|
2
|
+
|
|
3
|
+
const PostRevision = ghostBookshelf.Model.extend({
|
|
4
|
+
tableName: 'post_revisions'
|
|
5
|
+
}, {
|
|
6
|
+
permittedOptions(methodName) {
|
|
7
|
+
let options = ghostBookshelf.Model.permittedOptions.call(this, methodName);
|
|
8
|
+
const validOptions = {
|
|
9
|
+
findAll: ['filter', 'columns']
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
if (validOptions[methodName]) {
|
|
13
|
+
options = options.concat(validOptions[methodName]);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return options;
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
orderDefaultRaw() {
|
|
20
|
+
return 'created_at_ts DESC';
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
toJSON(unfilteredOptions) {
|
|
24
|
+
const options = PostRevision.filterOptions(unfilteredOptions, 'toJSON');
|
|
25
|
+
const attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options);
|
|
26
|
+
|
|
27
|
+
// CASE: only for internal accuracy
|
|
28
|
+
delete attrs.created_at_ts;
|
|
29
|
+
return attrs;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
PostRevision: ghostBookshelf.model('PostRevision', PostRevision)
|
|
35
|
+
};
|
|
@@ -13,6 +13,7 @@ const config = require('../../shared/config');
|
|
|
13
13
|
const settingsCache = require('../../shared/settings-cache');
|
|
14
14
|
const limitService = require('../services/limits');
|
|
15
15
|
const mobiledocLib = require('../lib/mobiledoc');
|
|
16
|
+
const lexicalLib = require('../lib/lexical');
|
|
16
17
|
const relations = require('./relations');
|
|
17
18
|
const urlUtils = require('../../shared/url-utils');
|
|
18
19
|
const {Tag} = require('./tag');
|
|
@@ -29,6 +30,7 @@ const messages = {
|
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
const MOBILEDOC_REVISIONS_COUNT = 10;
|
|
33
|
+
const POST_REVISIONS_COUNT = 10;
|
|
32
34
|
const ALL_STATUSES = ['published', 'draft', 'scheduled', 'sent'];
|
|
33
35
|
|
|
34
36
|
let Post;
|
|
@@ -90,7 +92,7 @@ Post = ghostBookshelf.Model.extend({
|
|
|
90
92
|
};
|
|
91
93
|
},
|
|
92
94
|
|
|
93
|
-
relationships: ['tags', 'authors', 'mobiledoc_revisions', 'posts_meta', 'tiers'],
|
|
95
|
+
relationships: ['tags', 'authors', 'mobiledoc_revisions', 'post_revisions', 'posts_meta', 'tiers'],
|
|
94
96
|
|
|
95
97
|
// NOTE: look up object, not super nice, but was easy to implement
|
|
96
98
|
relationshipBelongsTo: {
|
|
@@ -596,7 +598,7 @@ Post = ghostBookshelf.Model.extend({
|
|
|
596
598
|
});
|
|
597
599
|
}
|
|
598
600
|
|
|
599
|
-
if (!this.get('mobiledoc')) {
|
|
601
|
+
if (!this.get('mobiledoc') && !this.get('lexical')) {
|
|
600
602
|
this.set('mobiledoc', JSON.stringify(mobiledocLib.blankDocument));
|
|
601
603
|
}
|
|
602
604
|
|
|
@@ -610,9 +612,12 @@ Post = ghostBookshelf.Model.extend({
|
|
|
610
612
|
// CASE: ?force_rerender=true passed via Admin API
|
|
611
613
|
// CASE: html is null, but mobiledoc exists (only important for migrations & importing)
|
|
612
614
|
if (
|
|
613
|
-
this.
|
|
614
|
-
|
|
615
|
-
|
|
615
|
+
!this.get('lexical') &&
|
|
616
|
+
(
|
|
617
|
+
this.hasChanged('mobiledoc')
|
|
618
|
+
|| options.force_rerender
|
|
619
|
+
|| (!this.get('html') && (options.migrating || options.importing))
|
|
620
|
+
)
|
|
616
621
|
) {
|
|
617
622
|
try {
|
|
618
623
|
this.set('html', mobiledocLib.mobiledocHtmlRenderer.render(JSON.parse(this.get('mobiledoc'))));
|
|
@@ -624,6 +629,28 @@ Post = ghostBookshelf.Model.extend({
|
|
|
624
629
|
}
|
|
625
630
|
}
|
|
626
631
|
|
|
632
|
+
// CASE: lexical has changed, generate html
|
|
633
|
+
// CASE: ?force_rerender=true passed via Admin API
|
|
634
|
+
// CASE: html is null, but lexical exists (only important for migrations & importing)
|
|
635
|
+
if (
|
|
636
|
+
!this.get('mobiledoc') &&
|
|
637
|
+
(
|
|
638
|
+
this.hasChanged('lexical')
|
|
639
|
+
|| options.force_rerender
|
|
640
|
+
|| (!this.get('html') && (options.migrating || options.importing))
|
|
641
|
+
)
|
|
642
|
+
) {
|
|
643
|
+
try {
|
|
644
|
+
this.set('html', lexicalLib.lexicalHtmlRenderer.render(this.get('lexical')));
|
|
645
|
+
} catch (err) {
|
|
646
|
+
throw new errors.ValidationError({
|
|
647
|
+
message: 'Invalid lexical structure.',
|
|
648
|
+
help: 'https://ghost.org/docs/publishing/',
|
|
649
|
+
property: 'lexical'
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
627
654
|
if (this.hasChanged('html') || !this.get('plaintext')) {
|
|
628
655
|
let plaintext;
|
|
629
656
|
|
|
@@ -750,7 +777,7 @@ Post = ghostBookshelf.Model.extend({
|
|
|
750
777
|
}
|
|
751
778
|
|
|
752
779
|
// CASE: Handle mobiledoc backups/revisions. This is a pure database feature.
|
|
753
|
-
if (model.hasChanged('mobiledoc') && !options.importing && !options.migrating) {
|
|
780
|
+
if (model.hasChanged('mobiledoc') && !model.get('lexical') && !options.importing && !options.migrating) {
|
|
754
781
|
ops.push(function updateRevisions() {
|
|
755
782
|
return ghostBookshelf.model('MobiledocRevision')
|
|
756
783
|
.findAll(Object.assign({
|
|
@@ -794,6 +821,39 @@ Post = ghostBookshelf.Model.extend({
|
|
|
794
821
|
});
|
|
795
822
|
}
|
|
796
823
|
|
|
824
|
+
// CASE: Handle post backups/revisions. This is a pure database feature.
|
|
825
|
+
if (model.hasChanged('lexical') && !model.get('mobiledoc') && !options.importing && !options.migrating) {
|
|
826
|
+
ops.push(function updateRevisions() {
|
|
827
|
+
return ghostBookshelf.model('PostRevision')
|
|
828
|
+
.findAll(Object.assign({
|
|
829
|
+
filter: `post_id:${model.id}`,
|
|
830
|
+
columns: ['id']
|
|
831
|
+
}, _.pick(options, 'transacting')))
|
|
832
|
+
.then((revisions) => {
|
|
833
|
+
// Store previous + latest lexical content
|
|
834
|
+
if (!revisions.length && options.method !== 'insert') {
|
|
835
|
+
model.set('post_revisions', [{
|
|
836
|
+
post_id: model.id,
|
|
837
|
+
lexical: model.previous('lexical'),
|
|
838
|
+
created_at_ts: Date.now() - 1
|
|
839
|
+
}, {
|
|
840
|
+
post_id: model.id,
|
|
841
|
+
lexical: model.get('lexical'),
|
|
842
|
+
created_at_ts: Date.now()
|
|
843
|
+
}]);
|
|
844
|
+
} else {
|
|
845
|
+
const revisionsJSON = revisions.toJSON().slice(0, POST_REVISIONS_COUNT - 1);
|
|
846
|
+
|
|
847
|
+
model.set('post_revisions', revisionsJSON.concat([{
|
|
848
|
+
post_id: model.id,
|
|
849
|
+
lexical: model.get('lexical'),
|
|
850
|
+
created_at_ts: Date.now()
|
|
851
|
+
}]));
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
|
|
797
857
|
if (this.get('tiers')) {
|
|
798
858
|
this.set('tiers', this.get('tiers').map(t => ({
|
|
799
859
|
id: t.id
|
|
@@ -831,6 +891,10 @@ Post = ghostBookshelf.Model.extend({
|
|
|
831
891
|
return this.hasMany('MobiledocRevision', 'post_id');
|
|
832
892
|
},
|
|
833
893
|
|
|
894
|
+
post_revisions() {
|
|
895
|
+
return this.hasMany('PostRevision', 'post_id');
|
|
896
|
+
},
|
|
897
|
+
|
|
834
898
|
posts_meta: function postsMeta() {
|
|
835
899
|
return this.hasOne('PostsMeta', 'post_id');
|
|
836
900
|
},
|
|
@@ -901,6 +965,7 @@ Post = ghostBookshelf.Model.extend({
|
|
|
901
965
|
|
|
902
966
|
// CASE: never expose the revisions
|
|
903
967
|
delete attrs.mobiledoc_revisions;
|
|
968
|
+
delete attrs.post_revisions;
|
|
904
969
|
|
|
905
970
|
// If the current column settings allow it...
|
|
906
971
|
if (!options.columns || (options.columns && options.columns.indexOf('primary_tag') > -1)) {
|
|
@@ -974,7 +1039,7 @@ Post = ghostBookshelf.Model.extend({
|
|
|
974
1039
|
return filter;
|
|
975
1040
|
}
|
|
976
1041
|
}, {
|
|
977
|
-
allowedFormats: ['mobiledoc', 'html', 'plaintext'],
|
|
1042
|
+
allowedFormats: ['mobiledoc', 'lexical', 'html', 'plaintext'],
|
|
978
1043
|
|
|
979
1044
|
orderDefaultOptions: function orderDefaultOptions() {
|
|
980
1045
|
return {
|
|
@@ -7,7 +7,6 @@ const logging = require('@tryghost/logging');
|
|
|
7
7
|
const models = require('../../models');
|
|
8
8
|
const MailgunClient = require('@tryghost/mailgun-client');
|
|
9
9
|
const sentry = require('../../../shared/sentry');
|
|
10
|
-
const labs = require('../../../shared/labs');
|
|
11
10
|
const debug = require('@tryghost/debug')('mega');
|
|
12
11
|
const postEmailSerializer = require('../mega/post-email-serializer');
|
|
13
12
|
const configService = require('../../../shared/config');
|
|
@@ -173,10 +172,8 @@ module.exports = {
|
|
|
173
172
|
// Load newsletter data on email
|
|
174
173
|
await emailBatchModel.relations.email.getLazyRelation('newsletter', {require: false, ...knexOptions});
|
|
175
174
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
await emailBatchModel.relations.email.getLazyRelation('post', {require: false, ...knexOptions});
|
|
179
|
-
}
|
|
175
|
+
// Load post data on email - for content gating on paywall
|
|
176
|
+
await emailBatchModel.relations.email.getLazyRelation('post', {require: false, ...knexOptions});
|
|
180
177
|
|
|
181
178
|
// send the email
|
|
182
179
|
const sendResponse = await this.send(emailBatchModel.relations.email.toJSON(), recipientRows, memberSegment);
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|