ghost 5.14.2 → 5.16.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.16.0.tgz +0 -0
- package/components/tryghost-api-framework-5.16.0.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.16.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.16.0.tgz +0 -0
- package/components/tryghost-constants-5.16.0.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.16.0.tgz +0 -0
- package/components/tryghost-domain-events-5.16.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.16.0.tgz +0 -0
- package/components/{tryghost-email-analytics-service-5.14.2.tgz → tryghost-email-analytics-service-5.16.0.tgz} +0 -0
- package/components/tryghost-email-content-generator-5.16.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.16.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.16.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.16.0.tgz +0 -0
- package/components/{tryghost-job-manager-5.14.2.tgz → tryghost-job-manager-5.16.0.tgz} +0 -0
- package/components/tryghost-link-redirects-5.16.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.16.0.tgz +0 -0
- package/components/tryghost-link-tracking-5.16.0.tgz +0 -0
- package/components/tryghost-magic-link-5.16.0.tgz +0 -0
- package/components/{tryghost-mailgun-client-5.14.2.tgz → tryghost-mailgun-client-5.16.0.tgz} +0 -0
- package/components/tryghost-member-analytics-service-5.16.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.16.0.tgz +0 -0
- package/components/tryghost-member-events-5.16.0.tgz +0 -0
- package/components/tryghost-members-analytics-ingress-5.16.0.tgz +0 -0
- package/components/tryghost-members-api-5.16.0.tgz +0 -0
- package/components/tryghost-members-csv-5.16.0.tgz +0 -0
- package/components/tryghost-members-events-service-5.16.0.tgz +0 -0
- package/components/tryghost-members-importer-5.16.0.tgz +0 -0
- package/components/tryghost-members-offers-5.16.0.tgz +0 -0
- package/components/{tryghost-members-payments-5.14.2.tgz → tryghost-members-payments-5.16.0.tgz} +0 -0
- package/components/{tryghost-members-ssr-5.14.2.tgz → tryghost-members-ssr-5.16.0.tgz} +0 -0
- package/components/tryghost-members-stripe-service-5.16.0.tgz +0 -0
- package/components/tryghost-minifier-5.16.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.16.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.16.0.tgz +0 -0
- package/components/{tryghost-mw-error-handler-5.14.2.tgz → tryghost-mw-error-handler-5.16.0.tgz} +0 -0
- package/components/tryghost-mw-session-from-token-5.16.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.16.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.16.0.tgz +0 -0
- package/components/{tryghost-oembed-service-5.14.2.tgz → tryghost-oembed-service-5.16.0.tgz} +0 -0
- package/components/{tryghost-package-json-5.14.2.tgz → tryghost-package-json-5.16.0.tgz} +0 -0
- package/components/tryghost-referrers-5.16.0.tgz +0 -0
- package/components/{tryghost-security-5.14.2.tgz → tryghost-security-5.16.0.tgz} +0 -0
- package/components/tryghost-session-service-5.16.0.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.16.0.tgz +0 -0
- package/components/tryghost-staff-service-5.16.0.tgz +0 -0
- package/components/tryghost-stats-service-5.16.0.tgz +0 -0
- package/components/tryghost-update-check-service-5.16.0.tgz +0 -0
- package/components/{tryghost-verification-trigger-5.14.2.tgz → tryghost-verification-trigger-5.16.0.tgz} +0 -0
- package/components/{tryghost-version-notifications-data-service-5.14.2.tgz → tryghost-version-notifications-data-service-5.16.0.tgz} +0 -0
- package/content/themes/casper/default.hbs +2 -2
- package/core/boot.js +10 -3
- package/core/built/admin/assets/{chunk.143.a5ef705453da0d58b75a.js → chunk.143.a281d460e6059cd0210a.js} +21 -21
- 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.68eca2346b6f343991e7.js} +4 -4
- package/core/built/admin/assets/{chunk.579.2de3f4300baf25f9a0db.js → chunk.579.d14c3688558f34afeb3e.js} +8872 -7851
- package/core/built/admin/assets/{chunk.579.2de3f4300baf25f9a0db.js.LICENSE.txt → chunk.579.d14c3688558f34afeb3e.js.LICENSE.txt} +45 -0
- package/core/built/admin/assets/fonts/{Inter.ttf → Inter-e19174fb2c0e19b1fa67492a07886c75.ttf} +0 -0
- package/core/built/admin/assets/ghost-6491d134c450ca676911ea17e16cd7d4.css +1 -0
- package/core/built/admin/assets/ghost-dark-297ab2fcf4cadd1c950b84089a38c5e2.css +1 -0
- package/core/built/admin/assets/{ghost-8919656440ad4617a07bb31069b1f71b.js → ghost-f2bf99b26aee662cf37fe59f87b1ceb5.js} +593 -511
- 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/marketing/analytics-1-aa2d72c4e7347a3cb5666d07916b92aa.jpg +0 -0
- package/core/built/admin/assets/img/marketing/analytics-2-389d53f80041ff98111cce79facf66b8.jpg +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-b2375e2f383cbc3fd73340c4b656c993.js} +59 -47
- package/core/built/admin/assets/videos/logo-loader.mp4 +0 -0
- package/core/built/admin/index.html +11 -8
- package/core/frontend/helpers/search.js +1 -15
- package/core/frontend/src/member-attribution/member-attribution.js +64 -3
- package/core/frontend/web/site.js +10 -7
- package/core/server/api/endpoints/index.js +4 -0
- package/core/server/api/endpoints/links.js +25 -0
- package/core/server/api/endpoints/posts.js +2 -1
- package/core/server/api/endpoints/redirects.js +6 -8
- package/core/server/api/endpoints/stats.js +24 -0
- 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 +13 -8
- package/core/server/api/endpoints/utils/serializers/input/settings.js +1 -0
- package/core/server/api/endpoints/utils/serializers/output/mappers/activity-feed-events.js +51 -0
- package/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +10 -1
- package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +1 -1
- 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 +4 -1
- 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/migrations/versions/5.16/2022-09-19-09-04-add-link-redirects-table.js +10 -0
- package/core/server/data/migrations/versions/5.16/2022-09-19-09-05-add-members-link-click-events-table.js +8 -0
- package/core/server/data/migrations/versions/5.16/2022-09-19-17-44-add-referrer-columns-to-member-events-table.js +21 -0
- package/core/server/data/migrations/versions/5.16/2022-09-19-17-44-add-referrer-columns-to-subscription-events-table.js +21 -0
- package/core/server/data/schema/default-settings/default-settings.json +8 -0
- package/core/server/data/schema/schema.js +29 -1
- package/core/server/lib/lexical.js +12 -0
- package/core/server/models/base/plugins/user-type.js +4 -6
- package/core/server/models/link-redirect.js +65 -0
- package/core/server/models/member-link-click-event.js +26 -0
- package/core/server/models/post-revision.js +35 -0
- package/core/server/models/post.js +90 -9
- package/core/server/services/bulk-email/bulk-email-processor.js +9 -10
- 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-redirection/LinkRedirectRepository.js +88 -0
- package/core/server/services/link-redirection/index.js +31 -0
- package/core/server/services/link-tracking/LinkClickRepository.js +69 -0
- package/core/server/services/link-tracking/PostLinkRepository.js +62 -0
- package/core/server/services/link-tracking/index.js +48 -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 +101 -27
- package/core/server/services/member-attribution/index.js +12 -5
- package/core/server/services/members/api.js +1 -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/server/web/admin/app.js +8 -2
- package/core/server/web/api/endpoints/admin/routes.js +5 -0
- package/core/shared/config/defaults.json +7 -7
- package/core/shared/config/overrides.json +3 -2
- package/core/shared/labs.js +4 -2
- package/package.json +115 -107
- package/yarn.lock +828 -414
- package/components/tryghost-adapter-manager-5.14.2.tgz +0 -0
- 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-custom-theme-settings-service-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-magic-link-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-session-service-5.14.2.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.14.2.tgz +0 -0
- package/components/tryghost-staff-service-5.14.2.tgz +0 -0
- package/components/tryghost-update-check-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
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>Ghost Admin</title>
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.
|
|
11
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.16%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
|
|
12
12
|
|
|
13
13
|
<meta name="HandheldFriendly" content="True" />
|
|
14
14
|
<meta name="MobileOptimized" content="320" />
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
|
21
21
|
<meta name="apple-mobile-web-app-title" content="Ghost" />
|
|
22
22
|
|
|
23
|
-
<link rel="shortcut icon" href="assets/img/favicon.ico" />
|
|
23
|
+
<link rel="shortcut icon" href="assets/img/favicon-a9c6dbdcdc3ae568f4e0dad92149a0e3.ico" />
|
|
24
24
|
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon-74680e326a7e87b159d366c7d4fb3d4b.png" />
|
|
25
25
|
|
|
26
26
|
<meta name="application-name" content="Ghost" />
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
</style>
|
|
38
38
|
|
|
39
39
|
<link integrity="" rel="stylesheet" href="assets/vendor-733135cd6cbca8126c6fa223d63a5bf3.css">
|
|
40
|
-
<link integrity="" rel="stylesheet" href="assets/ghost-
|
|
40
|
+
<link integrity="" rel="stylesheet" href="assets/ghost-6491d134c450ca676911ea17e16cd7d4.css" title="light">
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
</head>
|
|
@@ -45,7 +45,10 @@
|
|
|
45
45
|
|
|
46
46
|
<div class="ember-load-indicator">
|
|
47
47
|
<div class="gh-loading-content">
|
|
48
|
-
<
|
|
48
|
+
<video width="100" height="100" loop="" autoplay="" muted="" playsinline="" preload="metadata">
|
|
49
|
+
<source src="assets/videos/logo-loader.mp4" type="video/mp4" />
|
|
50
|
+
<div class="gh-loading-spinner"></div>
|
|
51
|
+
</video>
|
|
49
52
|
</div>
|
|
50
53
|
</div>
|
|
51
54
|
|
|
@@ -53,9 +56,9 @@
|
|
|
53
56
|
|
|
54
57
|
<div id="ember-basic-dropdown-wormhole"></div>
|
|
55
58
|
|
|
56
|
-
<script src="assets/vendor-
|
|
57
|
-
<script src="assets/chunk.579.
|
|
58
|
-
<script src="assets/chunk.143.
|
|
59
|
-
<script src="assets/ghost-
|
|
59
|
+
<script src="assets/vendor-b2375e2f383cbc3fd73340c4b656c993.js"></script>
|
|
60
|
+
<script src="assets/chunk.579.d14c3688558f34afeb3e.js"></script>
|
|
61
|
+
<script src="assets/chunk.143.a281d460e6059cd0210a.js"></script>
|
|
62
|
+
<script src="assets/ghost-f2bf99b26aee662cf37fe59f87b1ceb5.js"></script>
|
|
60
63
|
</body>
|
|
61
64
|
</html>
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// # search helper
|
|
2
2
|
|
|
3
3
|
const {SafeString} = require('../services/handlebars');
|
|
4
|
-
const {labs} = require('../services/proxy');
|
|
5
4
|
|
|
6
|
-
function search() {
|
|
5
|
+
module.exports = function search() {
|
|
7
6
|
// We want this to output as one line, but splitting for readability
|
|
8
7
|
const svg = '<button class="gh-search-icon" aria-label="search" data-ghost-search '
|
|
9
8
|
+ 'style="display: inline-flex; justify-content: center; align-items: center; width: 32px; height: 32px; padding: 0; border: 0; color: inherit; background-color: transparent; cursor: pointer; outline: none;">'
|
|
@@ -11,17 +10,4 @@ function search() {
|
|
|
11
10
|
+ '<path d="M10 3a7 7 0 1 0 0 14 7 7 0 0 0 0-14Zm-9 7a9 9 0 1 1 18 0 9 9 0 0 1-18 0Z" fill="currentColor"/></svg></button>';
|
|
12
11
|
|
|
13
12
|
return new SafeString(svg);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
module.exports = function searchLabsWrapper() {
|
|
17
|
-
let self = this;
|
|
18
|
-
let args = arguments;
|
|
19
|
-
|
|
20
|
-
return labs.enabledHelper({
|
|
21
|
-
flagKey: 'searchHelper',
|
|
22
|
-
flagName: 'Search helper',
|
|
23
|
-
helperName: 'search'
|
|
24
|
-
}, () => {
|
|
25
|
-
return search.apply(self, args); // eslint-disable-line camelcase
|
|
26
|
-
});
|
|
27
13
|
};
|
|
@@ -15,6 +15,11 @@ const LIMIT = 15;
|
|
|
15
15
|
// "path": "/about/"
|
|
16
16
|
// },
|
|
17
17
|
// {
|
|
18
|
+
// "time": 12341234,
|
|
19
|
+
// "id": "manually-added-id",
|
|
20
|
+
// "type": "post",
|
|
21
|
+
// },
|
|
22
|
+
// {
|
|
18
23
|
// "time": 12341235,
|
|
19
24
|
// "path": "/welcome/"
|
|
20
25
|
// }
|
|
@@ -57,7 +62,7 @@ const LIMIT = 15;
|
|
|
57
62
|
// Valid item (so all following items are also valid by definition)
|
|
58
63
|
return true;
|
|
59
64
|
});
|
|
60
|
-
|
|
65
|
+
|
|
61
66
|
if (firstNotExpiredIndex > 0) {
|
|
62
67
|
// Remove until the first valid item
|
|
63
68
|
history.splice(0, firstNotExpiredIndex);
|
|
@@ -66,17 +71,73 @@ const LIMIT = 15;
|
|
|
66
71
|
history = [];
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
// Fetch referrer data from query params
|
|
75
|
+
let refParam;
|
|
76
|
+
let sourceParam;
|
|
77
|
+
let utmSourceParam;
|
|
78
|
+
let utmMediumParam;
|
|
79
|
+
try {
|
|
80
|
+
// Fetch source/medium from query param
|
|
81
|
+
const url = new URL(window.location.href);
|
|
82
|
+
refParam = url.searchParams.get('ref');
|
|
83
|
+
sourceParam = url.searchParams.get('source');
|
|
84
|
+
utmSourceParam = url.searchParams.get('utm_source');
|
|
85
|
+
utmMediumParam = url.searchParams.get('utm_medium');
|
|
86
|
+
} catch (e) {
|
|
87
|
+
console.error('[Member Attribution] Parsing referrer from querystring failed', e);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const refSource = refParam || sourceParam || utmSourceParam || null;
|
|
91
|
+
const refMedium = utmMediumParam || null;
|
|
92
|
+
const refUrl = window.document.referrer || null;
|
|
93
|
+
|
|
94
|
+
// Do we have attributions in the query string?
|
|
95
|
+
try {
|
|
96
|
+
const url = new URL(window.location.href);
|
|
97
|
+
const params = url.searchParams;
|
|
98
|
+
if (params.get('attribution_id') && params.get('attribution_type')) {
|
|
99
|
+
// Add attribution to history before the current path
|
|
100
|
+
history.push({
|
|
101
|
+
time: currentTime,
|
|
102
|
+
id: params.get('attribution_id'),
|
|
103
|
+
type: params.get('attribution_type'),
|
|
104
|
+
refSource,
|
|
105
|
+
refMedium,
|
|
106
|
+
refUrl
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Remove attribution from query string
|
|
110
|
+
params.delete('attribution_id');
|
|
111
|
+
params.delete('attribution_type');
|
|
112
|
+
url.search = '?' + params.toString();
|
|
113
|
+
window.history.replaceState({}, '', `${url.pathname}${url.search}${url.hash}`);
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error('[Member Attribution] Parsing attribution from querystring failed', error);
|
|
117
|
+
}
|
|
118
|
+
|
|
69
119
|
const currentPath = window.location.pathname;
|
|
70
120
|
|
|
71
121
|
if (history.length === 0 || history[history.length - 1].path !== currentPath) {
|
|
72
122
|
history.push({
|
|
73
123
|
path: currentPath,
|
|
74
|
-
time: currentTime
|
|
124
|
+
time: currentTime,
|
|
125
|
+
refSource,
|
|
126
|
+
refMedium,
|
|
127
|
+
refUrl
|
|
75
128
|
});
|
|
76
129
|
} else if (history.length > 0) {
|
|
77
130
|
history[history.length - 1].time = currentTime;
|
|
131
|
+
// Update referrer information for same path if available (e.g. when opening a link on same path via external referrer)
|
|
132
|
+
if (refSource) {
|
|
133
|
+
history[history.length - 1].refSource = refSource;
|
|
134
|
+
history[history.length - 1].refMedium = refMedium;
|
|
135
|
+
}
|
|
136
|
+
if (refUrl) {
|
|
137
|
+
history[history.length - 1].refUrl = refUrl;
|
|
138
|
+
}
|
|
78
139
|
}
|
|
79
|
-
|
|
140
|
+
|
|
80
141
|
// Restrict length
|
|
81
142
|
if (history.length > LIMIT) {
|
|
82
143
|
history = history.slice(-LIMIT);
|
|
@@ -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
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const linkTrackingService = require('../../services/link-tracking');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
docName: 'links',
|
|
5
|
+
browse: {
|
|
6
|
+
options: [
|
|
7
|
+
'filter'
|
|
8
|
+
],
|
|
9
|
+
permissions: false,
|
|
10
|
+
async query(frame) {
|
|
11
|
+
const links = await linkTrackingService.service.getLinks(frame.options);
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
data: links,
|
|
15
|
+
meta: {
|
|
16
|
+
pagination: {
|
|
17
|
+
total: links.length,
|
|
18
|
+
page: 1,
|
|
19
|
+
pages: 1
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -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
|
};
|
|
@@ -28,5 +28,29 @@ module.exports = {
|
|
|
28
28
|
async query() {
|
|
29
29
|
return await statsService.getSubscriptionCountHistory();
|
|
30
30
|
}
|
|
31
|
+
},
|
|
32
|
+
postReferrers: {
|
|
33
|
+
data: [
|
|
34
|
+
'id'
|
|
35
|
+
],
|
|
36
|
+
permissions: {
|
|
37
|
+
docName: 'posts',
|
|
38
|
+
method: 'browse'
|
|
39
|
+
},
|
|
40
|
+
async query(frame) {
|
|
41
|
+
return await statsService.getPostReferrers(frame.data.id);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
referrersHistory: {
|
|
45
|
+
data: [
|
|
46
|
+
'id'
|
|
47
|
+
],
|
|
48
|
+
permissions: {
|
|
49
|
+
docName: 'posts',
|
|
50
|
+
method: 'browse'
|
|
51
|
+
},
|
|
52
|
+
async query() {
|
|
53
|
+
return await statsService.getReferrersHistory();
|
|
54
|
+
}
|
|
31
55
|
}
|
|
32
56
|
};
|
|
@@ -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
|
}
|
|
@@ -6,11 +6,12 @@ const localUtils = require('../../index');
|
|
|
6
6
|
const mobiledoc = require('../../../../../lib/mobiledoc');
|
|
7
7
|
const postsMetaSchema = require('../../../../../data/schema').tables.posts_meta;
|
|
8
8
|
const clean = require('./utils/clean');
|
|
9
|
+
const labs = require('../../../../../../shared/labs');
|
|
9
10
|
|
|
10
|
-
function
|
|
11
|
-
if (frame.options.formats
|
|
11
|
+
function removeSourceFormats(frame) {
|
|
12
|
+
if (frame.options.formats?.includes('mobiledoc') || frame.options.formats?.includes('lexical')) {
|
|
12
13
|
frame.options.formats = frame.options.formats.filter((format) => {
|
|
13
|
-
return
|
|
14
|
+
return !['mobiledoc', 'lexical'].includes(format);
|
|
14
15
|
});
|
|
15
16
|
}
|
|
16
17
|
}
|
|
@@ -24,7 +25,11 @@ function defaultRelations(frame) {
|
|
|
24
25
|
return false;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
if (labs.isSet('emailClicks')) {
|
|
29
|
+
frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter', 'count.signups', 'count.conversions', 'count.clicks'];
|
|
30
|
+
} else {
|
|
31
|
+
frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter', 'count.signups', 'count.conversions'];
|
|
32
|
+
}
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
function setDefaultOrder(frame) {
|
|
@@ -101,8 +106,8 @@ module.exports = {
|
|
|
101
106
|
* - user exists? admin api access
|
|
102
107
|
*/
|
|
103
108
|
if (localUtils.isContentAPI(frame)) {
|
|
104
|
-
// CASE: the content api endpoint for posts should not return mobiledoc
|
|
105
|
-
|
|
109
|
+
// CASE: the content api endpoint for posts should not return mobiledoc or lexical
|
|
110
|
+
removeSourceFormats(frame);
|
|
106
111
|
|
|
107
112
|
setDefaultOrder(frame);
|
|
108
113
|
forceVisibilityColumn(frame);
|
|
@@ -127,8 +132,8 @@ module.exports = {
|
|
|
127
132
|
* - user exists? admin api access
|
|
128
133
|
*/
|
|
129
134
|
if (localUtils.isContentAPI(frame)) {
|
|
130
|
-
// CASE: the content api endpoint for posts should not return mobiledoc
|
|
131
|
-
|
|
135
|
+
// CASE: the content api endpoint for posts should not return mobiledoc or lexical
|
|
136
|
+
removeSourceFormats(frame);
|
|
132
137
|
|
|
133
138
|
setDefaultOrder(frame);
|
|
134
139
|
forceVisibilityColumn(frame);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const mapComment = require('./comments');
|
|
2
|
+
const url = require('../utils/url');
|
|
3
|
+
const _ = require('lodash');
|
|
2
4
|
|
|
3
5
|
const commentEventMapper = (json, frame) => {
|
|
4
6
|
return {
|
|
@@ -7,10 +9,59 @@ const commentEventMapper = (json, frame) => {
|
|
|
7
9
|
};
|
|
8
10
|
};
|
|
9
11
|
|
|
12
|
+
const clickEventMapper = (json, frame) => {
|
|
13
|
+
const memberFields = [
|
|
14
|
+
'id',
|
|
15
|
+
'uuid',
|
|
16
|
+
'name',
|
|
17
|
+
'email',
|
|
18
|
+
'avatar_image'
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const linkFields = [
|
|
22
|
+
'from',
|
|
23
|
+
'to'
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const postFields = [
|
|
27
|
+
'id',
|
|
28
|
+
'uuid',
|
|
29
|
+
'title',
|
|
30
|
+
'url'
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const data = json.data;
|
|
34
|
+
const response = {};
|
|
35
|
+
|
|
36
|
+
if (data.link && data.link.post) {
|
|
37
|
+
// We could use the post mapper here, but we need less field + don't need al the async behavior support
|
|
38
|
+
url.forPost(data.link.post.id, data.link.post, frame);
|
|
39
|
+
response.post = _.pick(data.link.post, postFields);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (data.link) {
|
|
43
|
+
response.link = _.pick(data.link, linkFields);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (data.member) {
|
|
47
|
+
response.member = _.pick(data.member, memberFields);
|
|
48
|
+
} else {
|
|
49
|
+
response.member = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...json,
|
|
54
|
+
data: response
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
10
58
|
const activityFeedMapper = (event, frame) => {
|
|
11
59
|
if (event.type === 'comment_event') {
|
|
12
60
|
return commentEventMapper(event, frame);
|
|
13
61
|
}
|
|
62
|
+
if (event.type === 'click_event') {
|
|
63
|
+
return clickEventMapper(event, frame);
|
|
64
|
+
}
|
|
14
65
|
return event;
|
|
15
66
|
};
|
|
16
67
|
|
|
@@ -18,6 +18,15 @@ const memberFields = [
|
|
|
18
18
|
'avatar_image'
|
|
19
19
|
];
|
|
20
20
|
|
|
21
|
+
const memberFieldsAdmin = [
|
|
22
|
+
'id',
|
|
23
|
+
'uuid',
|
|
24
|
+
'name',
|
|
25
|
+
'email',
|
|
26
|
+
'expertise',
|
|
27
|
+
'avatar_image'
|
|
28
|
+
];
|
|
29
|
+
|
|
21
30
|
const postFields = [
|
|
22
31
|
'id',
|
|
23
32
|
'uuid',
|
|
@@ -36,7 +45,7 @@ const commentMapper = (model, frame) => {
|
|
|
36
45
|
const response = _.pick(jsonModel, commentFields);
|
|
37
46
|
|
|
38
47
|
if (jsonModel.member) {
|
|
39
|
-
response.member = _.pick(jsonModel.member, memberFields);
|
|
48
|
+
response.member = _.pick(jsonModel.member, utils.isMembersAPI(frame) ? memberFields : memberFieldsAdmin);
|
|
40
49
|
} else {
|
|
41
50
|
response.member = null;
|
|
42
51
|
}
|
|
@@ -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
|
};
|