ghost 5.129.2 → 5.130.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/components/tryghost-i18n-5.130.1.tgz +0 -0
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +2 -2
- package/core/built/admin/assets/admin-x-activitypub/{index-B7EmcyVj.mjs → index-B8te98RZ.mjs} +26908 -20750
- package/core/built/admin/assets/admin-x-activitypub/{index-B12913rO.mjs → index-C8qwgKWF.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-l2Ex2555.mjs → CodeEditorView-DaQbEVUf.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +3 -3
- package/core/built/admin/assets/admin-x-settings/index-Cmiy56KV.mjs +30462 -0
- package/core/built/admin/assets/admin-x-settings/{index-C6P_16OJ.mjs → index-zBk55R7z.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{modals-CY1xx4Em.mjs → modals-tqVrPtPy.mjs} +2418 -2416
- package/core/built/admin/assets/{chunk.524.c8313bccd308920abf9c.js → chunk.524.8a4cbb5b8ae5cf01697e.js} +7 -7
- package/core/built/admin/assets/{chunk.582.e4feab981886cfc91835.js → chunk.582.7b14e9ac2e84d285035e.js} +8 -8
- package/core/built/admin/assets/{chunk.728.214803966b81ffdb1acd.js → chunk.728.077782a432061228b91e.js} +141 -141
- package/core/built/admin/assets/{ghost-db9fcb8c1f65776f3ee11c39f19a660b.js → ghost-280b83af263b51bc4d6ce5bd8f536096.js} +48 -55
- package/core/built/admin/assets/posts/posts.js +21698 -21753
- package/core/built/admin/assets/stats/stats.js +21710 -21735
- package/core/built/admin/assets/{vendor-c89102f24c3d9502e9db741509767580.js → vendor-aed0068cf9b67d042dd23a6343545b7b.js} +1 -1
- package/core/built/admin/index.html +5 -5
- package/core/frontend/helpers/match.js +3 -0
- package/core/frontend/meta/schema.js +19 -0
- package/core/frontend/web/middleware/frontend-caching.js +6 -1
- package/core/frontend/web/middleware/static-theme.js +3 -5
- package/core/server/api/endpoints/utils/serializers/input/settings.js +3 -1
- package/core/server/api/endpoints/utils/serializers/input/utils/settings-key-group-mapper.js +3 -1
- package/core/server/api/endpoints/utils/serializers/input/utils/settings-key-type-mapper.js +3 -1
- package/core/server/api/endpoints/utils/serializers/output/config.js +2 -1
- package/core/server/data/migrations/versions/5.130/2025-07-11-14-14-54-add-explore-settings.js +16 -0
- package/core/server/data/schema/default-settings/default-settings.json +18 -0
- package/core/server/data/tinybird/README.md +9 -14
- package/core/server/services/activitypub/ActivityPubService.js +22 -2
- package/core/server/services/activitypub/ActivityPubService.ts +26 -6
- package/core/server/services/email-service/email-templates/partials/styles.hbs +0 -14
- package/core/server/services/explore-ping/ExplorePingService.js +44 -33
- package/core/server/services/public-config/config.js +4 -1
- package/core/server/services/themes/installer.js +17 -3
- package/core/server/web/admin/app.js +5 -6
- package/core/server/web/shared/middleware/cache-control.js +2 -1
- package/core/shared/config/defaults.json +6 -0
- package/core/shared/config/env/config.production.json +4 -0
- package/package.json +12 -12
- package/tsconfig.tsbuildinfo +1 -1
- package/yarn.lock +325 -249
- package/components/tryghost-i18n-5.129.2.tgz +0 -0
- package/core/built/admin/assets/admin-x-settings/index-DoLRADbr.mjs +0 -30308
- package/core/built/admin/assets/img/twitter-7a7a0ba12d9b5bfb8a2058764a827c31.svg +0 -4
- /package/core/built/admin/assets/{chunk.728.214803966b81ffdb1acd.js.LICENSE.txt → chunk.728.077782a432061228b91e.js.LICENSE.txt} +0 -0
|
@@ -9553,4 +9553,4 @@ e.default=class{constructor(e){if(this._data=new t.default,e)for(let t=0;t<e.len
|
|
|
9553
9553
|
return this}get(e){let t=this._data[e]
|
|
9554
9554
|
return t===r.UNDEFINED_KEY?void 0:t}set(e,t){return this._data[e]=t,this}delete(e){return this._data[e]=r.UNDEFINED_KEY,!0}}})
|
|
9555
9555
|
|
|
9556
|
-
//# sourceMappingURL=vendor-
|
|
9556
|
+
//# sourceMappingURL=vendor-326b46cbc2845d47f1e0af43ba21caec.map
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>Ghost</title>
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%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.
|
|
9
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%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.130%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%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%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%2237bd1e3e4d%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%22575f27df11%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%22b38d27bb5e%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%229a7e1b885b%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%225445ed6ca9%22%2C%22adminXActivitypubCustomUrl%22%3A%22https%3A%2F%2Fcdn.jsdelivr.net%2Fghost%2Fadmin-x-activitypub%400%2Fdist%2Fadmin-x-activitypub.js%22%7D" />
|
|
10
10
|
|
|
11
11
|
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1, minimal-ui, viewport-fit=cover" />
|
|
12
12
|
<meta name="pinterest" content="nopin" />
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
|
|
48
48
|
<div id="ember-basic-dropdown-wormhole"></div>
|
|
49
49
|
|
|
50
|
-
<script src="assets/vendor-
|
|
51
|
-
<script src="assets/chunk.728.
|
|
52
|
-
<script src="assets/chunk.524.
|
|
53
|
-
<script src="assets/ghost-
|
|
50
|
+
<script src="assets/vendor-aed0068cf9b67d042dd23a6343545b7b.js"></script>
|
|
51
|
+
<script src="assets/chunk.728.077782a432061228b91e.js"></script>
|
|
52
|
+
<script src="assets/chunk.524.8a4cbb5b8ae5cf01697e.js"></script>
|
|
53
|
+
<script src="assets/ghost-280b83af263b51bc4d6ce5bd8f536096.js"></script>
|
|
54
54
|
</body>
|
|
55
55
|
</html>
|
|
@@ -57,6 +57,9 @@ const handleMatch = (data, operator, value) => {
|
|
|
57
57
|
case '<=':
|
|
58
58
|
result = data <= value;
|
|
59
59
|
break;
|
|
60
|
+
case '~':
|
|
61
|
+
result = _.isString(data) && _.isString(value) && data.includes(value);
|
|
62
|
+
break;
|
|
60
63
|
case '~^':
|
|
61
64
|
result = _.isString(data) && _.isString(value) && data.startsWith(value);
|
|
62
65
|
break;
|
|
@@ -78,6 +78,24 @@ function trimSameAs(author) {
|
|
|
78
78
|
return sameAs;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Build contributor objects for schema.org Article schema.
|
|
83
|
+
*
|
|
84
|
+
* @param {Object[]} authors - Array of author objects (excluding primary author)
|
|
85
|
+
*/
|
|
86
|
+
function buildContributorObjects(authors) {
|
|
87
|
+
return authors.map(author => trimSchema({
|
|
88
|
+
'@type': 'Person',
|
|
89
|
+
name: escapeExpression(author.name),
|
|
90
|
+
image: author.profile_image ? schemaImageObject({url: author.profile_image}) : null,
|
|
91
|
+
url: author.url || null,
|
|
92
|
+
sameAs: trimSameAs(author),
|
|
93
|
+
description: author.meta_description ?
|
|
94
|
+
escapeExpression(author.meta_description) :
|
|
95
|
+
null
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
81
99
|
function getPostSchema(metaData, data) {
|
|
82
100
|
// CASE: metaData.excerpt for post context is populated by either the custom excerpt, the meta description,
|
|
83
101
|
// or the automated excerpt of 50 words. It is empty for any other context.
|
|
@@ -101,6 +119,7 @@ function getPostSchema(metaData, data) {
|
|
|
101
119
|
escapeExpression(data[context].primary_author.metaDescription) :
|
|
102
120
|
null
|
|
103
121
|
},
|
|
122
|
+
contributor: data[context].authors && data[context].authors.length > 1 ? buildContributorObjects(data[context].authors.slice(1)) : null,
|
|
104
123
|
headline: escapeExpression(metaData.metaTitle),
|
|
105
124
|
url: metaData.url,
|
|
106
125
|
datePublished: metaData.publishedDate,
|
|
@@ -51,6 +51,11 @@ const getMiddleware = async (getFreeTier = async () => {
|
|
|
51
51
|
return shared.middleware.cacheControl('private')(req, res, next);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// CASE: Never cache preview routes
|
|
55
|
+
if (req.path?.startsWith('/p/')) {
|
|
56
|
+
return shared.middleware.cacheControl('noCache')(req, res, next);
|
|
57
|
+
}
|
|
58
|
+
|
|
54
59
|
// CASE: Cache member's content if this feature is enabled
|
|
55
60
|
if (req.member && shouldCacheMembersContent) {
|
|
56
61
|
// Set the 'cache-control' header to 'public'
|
|
@@ -74,4 +79,4 @@ const getMiddleware = async (getFreeTier = async () => {
|
|
|
74
79
|
module.exports = {
|
|
75
80
|
getMiddleware,
|
|
76
81
|
calculateMemberTier // exported for testing
|
|
77
|
-
};
|
|
82
|
+
};
|
|
@@ -59,12 +59,10 @@ function forwardToExpressStatic(req, res, next) {
|
|
|
59
59
|
return next();
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const configMaxAge = config.get('caching:theme:maxAge');
|
|
63
|
-
|
|
64
|
-
// @NOTE: the maxAge config passed below are in milliseconds and the config
|
|
65
|
-
// is specified in seconds. See https://github.com/expressjs/serve-static/issues/150 for more context
|
|
66
62
|
express.static(themeEngine.getActive().path, {
|
|
67
|
-
|
|
63
|
+
// @NOTE: the maxAge config passed below are in milliseconds and the config
|
|
64
|
+
// is specified in seconds. See https://github.com/expressjs/serve-static/issues/150 for more context
|
|
65
|
+
maxAge: config.get('caching:theme:maxAge') * 1000
|
|
68
66
|
}
|
|
69
67
|
)(req, res, next);
|
|
70
68
|
}
|
package/core/server/api/endpoints/utils/serializers/input/utils/settings-key-group-mapper.js
CHANGED
|
@@ -48,7 +48,9 @@ const keyGroupMapping = {
|
|
|
48
48
|
portal_name: 'portal',
|
|
49
49
|
portal_button: 'portal',
|
|
50
50
|
portal_plans: 'portal',
|
|
51
|
-
require_email_mfa: 'security'
|
|
51
|
+
require_email_mfa: 'security',
|
|
52
|
+
explore_ping: 'explore',
|
|
53
|
+
explore_ping_growth: 'explore'
|
|
52
54
|
};
|
|
53
55
|
|
|
54
56
|
const mapKeyToGroup = (key) => {
|
|
@@ -55,7 +55,9 @@ const keyTypeMapping = {
|
|
|
55
55
|
labs: 'object',
|
|
56
56
|
unsplash: 'object',
|
|
57
57
|
bulk_email_settings: 'object',
|
|
58
|
-
require_email_mfa: 'boolean'
|
|
58
|
+
require_email_mfa: 'boolean',
|
|
59
|
+
explore_ping: 'boolean',
|
|
60
|
+
explore_ping_growth: 'boolean'
|
|
59
61
|
};
|
|
60
62
|
|
|
61
63
|
const mapKeyToType = (key) => {
|
package/core/server/data/migrations/versions/5.130/2025-07-11-14-14-54-add-explore-settings.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const {combineTransactionalMigrations, addSetting} = require('../../utils');
|
|
2
|
+
|
|
3
|
+
module.exports = combineTransactionalMigrations(
|
|
4
|
+
addSetting({
|
|
5
|
+
key: 'explore_ping',
|
|
6
|
+
value: 'true',
|
|
7
|
+
type: 'boolean',
|
|
8
|
+
group: 'explore'
|
|
9
|
+
}),
|
|
10
|
+
addSetting({
|
|
11
|
+
key: 'explore_ping_growth',
|
|
12
|
+
value: 'false',
|
|
13
|
+
type: 'boolean',
|
|
14
|
+
group: 'explore'
|
|
15
|
+
})
|
|
16
|
+
);
|
|
@@ -628,5 +628,23 @@
|
|
|
628
628
|
},
|
|
629
629
|
"type": "boolean"
|
|
630
630
|
}
|
|
631
|
+
},
|
|
632
|
+
"explore": {
|
|
633
|
+
"explore_ping": {
|
|
634
|
+
"defaultValue": "true",
|
|
635
|
+
"validations": {
|
|
636
|
+
"isEmpty": false,
|
|
637
|
+
"isIn": [["true", "false"]]
|
|
638
|
+
},
|
|
639
|
+
"type": "boolean"
|
|
640
|
+
},
|
|
641
|
+
"explore_ping_growth": {
|
|
642
|
+
"defaultValue": "false",
|
|
643
|
+
"validations": {
|
|
644
|
+
"isEmpty": false,
|
|
645
|
+
"isIn": [["true", "false"]]
|
|
646
|
+
},
|
|
647
|
+
"type": "boolean"
|
|
648
|
+
}
|
|
631
649
|
}
|
|
632
650
|
}
|
|
@@ -31,7 +31,7 @@ with the following information.
|
|
|
31
31
|
|
|
32
32
|
### Config
|
|
33
33
|
Sample config:
|
|
34
|
-
```
|
|
34
|
+
```jsonc
|
|
35
35
|
{
|
|
36
36
|
"someOtherConfigurationForEmail": {
|
|
37
37
|
"transport": "SMTP",
|
|
@@ -40,25 +40,20 @@ Sample config:
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"tinybird": {
|
|
43
|
+
"workspaceId": "workspace-id-from-tinybird",
|
|
44
|
+
"adminToken": "admin-token-from-tinybird",
|
|
43
45
|
"tracker": {
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
"datasource": "analytics_events",
|
|
47
|
-
"local": {
|
|
48
|
-
"enabled": true,
|
|
49
|
-
"token": "xxxxx",
|
|
50
|
-
"endpoint": "http://localhost:7181/v0/events",
|
|
51
|
-
"datasource": "analytics_events"
|
|
52
|
-
}
|
|
46
|
+
// -- needs to be present, and required Traffic Analytics service running with correct setup
|
|
47
|
+
"endpoint": "http://localhost:3000/tb/web_analytics"
|
|
53
48
|
},
|
|
54
49
|
"stats": {
|
|
50
|
+
// -- optional override for site uuid
|
|
51
|
+
// "id": "106a623d-9792-4b63-acde-4a0c28ead3dc",
|
|
55
52
|
"endpoint": "https://api.tinybird.co",
|
|
56
|
-
|
|
53
|
+
// -- tinybird local configuration (optional)
|
|
57
54
|
"local": {
|
|
58
55
|
"enabled": true,
|
|
59
|
-
"token": "
|
|
60
|
-
"endpoint": "http://localhost:7181",
|
|
61
|
-
"datasource": "analytics_events"
|
|
56
|
+
"token": "local-stats-or-admin-token",
|
|
62
57
|
}
|
|
63
58
|
}
|
|
64
59
|
}
|
|
@@ -18,12 +18,32 @@ class ActivityPubService {
|
|
|
18
18
|
this.identityTokenService = identityTokenService;
|
|
19
19
|
}
|
|
20
20
|
getExpectedWebhooks(secret) {
|
|
21
|
-
return [
|
|
21
|
+
return [
|
|
22
|
+
{
|
|
22
23
|
event: 'post.published',
|
|
23
24
|
target_url: new URL('.ghost/activitypub/v1/webhooks/post/published', this.siteUrl),
|
|
24
25
|
api_version: 'v5.100.0',
|
|
25
26
|
secret
|
|
26
|
-
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
event: 'post.deleted',
|
|
30
|
+
target_url: new URL('.ghost/activitypub/v1/webhooks/post/deleted', this.siteUrl),
|
|
31
|
+
api_version: 'v5.100.0',
|
|
32
|
+
secret
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
event: 'post.unpublished',
|
|
36
|
+
target_url: new URL('.ghost/activitypub/v1/webhooks/post/unpublished', this.siteUrl),
|
|
37
|
+
api_version: 'v5.100.0',
|
|
38
|
+
secret
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
event: 'post.published.edited',
|
|
42
|
+
target_url: new URL('.ghost/activitypub/v1/webhooks/post/updated', this.siteUrl),
|
|
43
|
+
api_version: 'v5.100.0',
|
|
44
|
+
secret
|
|
45
|
+
}
|
|
46
|
+
];
|
|
27
47
|
}
|
|
28
48
|
async checkWebhookState(expectedWebhooks, integration) {
|
|
29
49
|
this.logging.info(`Checking ActivityPub Webhook state`);
|
|
@@ -25,12 +25,32 @@ export class ActivityPubService {
|
|
|
25
25
|
) {}
|
|
26
26
|
|
|
27
27
|
getExpectedWebhooks(secret: string): ExpectedWebhook[] {
|
|
28
|
-
return [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
event: 'post.published',
|
|
31
|
+
target_url: new URL('.ghost/activitypub/v1/webhooks/post/published', this.siteUrl),
|
|
32
|
+
api_version: 'v5.100.0',
|
|
33
|
+
secret
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
event: 'post.deleted',
|
|
37
|
+
target_url: new URL('.ghost/activitypub/v1/webhooks/post/deleted', this.siteUrl),
|
|
38
|
+
api_version: 'v5.100.0',
|
|
39
|
+
secret
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
event: 'post.unpublished',
|
|
43
|
+
target_url: new URL('.ghost/activitypub/v1/webhooks/post/unpublished', this.siteUrl),
|
|
44
|
+
api_version: 'v5.100.0',
|
|
45
|
+
secret
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
event: 'post.published.edited',
|
|
49
|
+
target_url: new URL('.ghost/activitypub/v1/webhooks/post/updated', this.siteUrl),
|
|
50
|
+
api_version: 'v5.100.0',
|
|
51
|
+
secret
|
|
52
|
+
}
|
|
53
|
+
];
|
|
34
54
|
}
|
|
35
55
|
|
|
36
56
|
async checkWebhookState(expectedWebhooks: ExpectedWebhook[], integration: {id: string}) {
|
|
@@ -381,7 +381,6 @@ h6 + .kg-paywall .kg-paywall-hr td {
|
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
/* Exclude CTA cards with colored backgrounds from custom text color, but allow transparent ones */
|
|
384
|
-
{{#hasFeature "emailCustomization"}}
|
|
385
384
|
{{#each ctaBgColors}}
|
|
386
385
|
.post-content-row .kg-cta-bg-{{this}} .kg-cta-text p,
|
|
387
386
|
.post-content-row .kg-cta-bg-{{this}} .kg-cta-text ul,
|
|
@@ -391,17 +390,6 @@ h6 + .kg-paywall .kg-paywall-hr td {
|
|
|
391
390
|
color: inherit !important;
|
|
392
391
|
}
|
|
393
392
|
{{/each}}
|
|
394
|
-
{{else}}
|
|
395
|
-
.post-content-row .kg-cta-bg-grey .kg-cta-text p,
|
|
396
|
-
.post-content-row .kg-cta-bg-blue .kg-cta-text p,
|
|
397
|
-
.post-content-row .kg-cta-bg-green .kg-cta-text p,
|
|
398
|
-
.post-content-row .kg-cta-bg-yellow .kg-cta-text p,
|
|
399
|
-
.post-content-row .kg-cta-bg-red .kg-cta-text p,
|
|
400
|
-
.post-content-row .kg-cta-bg-pink .kg-cta-text p,
|
|
401
|
-
.post-content-row .kg-cta-bg-purple .kg-cta-text p {
|
|
402
|
-
color: inherit !important;
|
|
403
|
-
}
|
|
404
|
-
{{/hasFeature}}
|
|
405
393
|
|
|
406
394
|
.kg-cta-bg-none .kg-cta-sponsor-label span,
|
|
407
395
|
.kg-cta-bg-white .kg-cta-sponsor-label span {
|
|
@@ -2542,10 +2530,8 @@ table.btn-accent a {
|
|
|
2542
2530
|
{{/if}}
|
|
2543
2531
|
|
|
2544
2532
|
</style>
|
|
2545
|
-
{{#hasFeature "emailCustomization"}}
|
|
2546
2533
|
<!--[if mso]>
|
|
2547
2534
|
<style type="text/css">
|
|
2548
2535
|
ul, ol { margin-left: 1.5em !important; } {{!-- fix bullets/numbers not appearing for lists in older Outlook versions --}}
|
|
2549
2536
|
</style>
|
|
2550
2537
|
<![endif]-->
|
|
2551
|
-
{{/hasFeature}}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module.exports = class ExplorePingService {
|
|
2
2
|
/**
|
|
3
3
|
* @param {object} deps
|
|
4
|
-
* @param {{
|
|
4
|
+
* @param {{get: (string) => string}} deps.settingsCache
|
|
5
5
|
* @param {object} deps.config
|
|
6
6
|
* @param {object} deps.labs
|
|
7
7
|
* @param {object} deps.logging
|
|
@@ -14,6 +14,7 @@ module.exports = class ExplorePingService {
|
|
|
14
14
|
* }}} deps.posts
|
|
15
15
|
* @param {{stats: {
|
|
16
16
|
* getTotalMembers: () => Promise<number>
|
|
17
|
+
* getMRRHistory: () => Promise<number>
|
|
17
18
|
* }}} deps.members
|
|
18
19
|
*/
|
|
19
20
|
constructor({settingsCache, config, labs, logging, ghostVersion, request, posts, members}) {
|
|
@@ -28,45 +29,50 @@ module.exports = class ExplorePingService {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
async constructPayload() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.posts.stats.getMostRecentlyPublishedPostDate(),
|
|
38
|
-
this.posts.stats.getFirstPublishedPostDate()
|
|
39
|
-
]);
|
|
32
|
+
const payload = {
|
|
33
|
+
ghost: this.ghostVersion.full,
|
|
34
|
+
site_uuid: this.settingsCache.get('site_uuid'),
|
|
35
|
+
url: this.config.get('url'),
|
|
36
|
+
theme: this.settingsCache.get('active_theme')
|
|
37
|
+
};
|
|
40
38
|
|
|
41
|
-
// Get member statistics with error handling
|
|
42
|
-
let totalMembers = null;
|
|
43
39
|
try {
|
|
44
|
-
|
|
40
|
+
const [totalPosts, lastPublishedAt, firstPublishedAt] = await Promise.all([
|
|
41
|
+
this.posts.stats.getTotalPostsPublished(),
|
|
42
|
+
this.posts.stats.getMostRecentlyPublishedPostDate(),
|
|
43
|
+
this.posts.stats.getFirstPublishedPostDate()
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
payload.posts_total = totalPosts;
|
|
47
|
+
payload.posts_last = lastPublishedAt ? lastPublishedAt.toISOString() : null;
|
|
48
|
+
payload.posts_first = firstPublishedAt ? firstPublishedAt.toISOString() : null;
|
|
45
49
|
} catch (err) {
|
|
46
|
-
this.logging.warn('Failed to fetch
|
|
50
|
+
this.logging.warn('Failed to fetch post statistics', {
|
|
47
51
|
error: err.message,
|
|
48
52
|
context: 'explore-ping-service'
|
|
49
53
|
});
|
|
50
|
-
|
|
54
|
+
payload.posts_total = null;
|
|
55
|
+
payload.posts_last = null;
|
|
56
|
+
payload.posts_first = null;
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
if (this.settingsCache.get('explore_ping_growth')) {
|
|
60
|
+
try {
|
|
61
|
+
const totalMembers = await this.members.stats.getTotalMembers();
|
|
62
|
+
const mrr = await this.members.stats.getMRRHistory();
|
|
63
|
+
payload.members_total = totalMembers;
|
|
64
|
+
payload.mrr = mrr;
|
|
65
|
+
} catch (err) {
|
|
66
|
+
this.logging.warn('Failed to fetch member statistics', {
|
|
67
|
+
error: err.message,
|
|
68
|
+
context: 'explore-ping-service'
|
|
69
|
+
});
|
|
70
|
+
payload.members_total = null;
|
|
71
|
+
payload.mrr = null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return payload;
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
async makeRequest(exploreUrl, payload) {
|
|
@@ -95,12 +101,17 @@ module.exports = class ExplorePingService {
|
|
|
95
101
|
return;
|
|
96
102
|
}
|
|
97
103
|
|
|
98
|
-
const exploreUrl = this.config.get('explore:
|
|
104
|
+
const exploreUrl = this.config.get('explore:update_url');
|
|
99
105
|
if (!exploreUrl) {
|
|
100
106
|
this.logging.warn('Explore URL not set');
|
|
101
107
|
return;
|
|
102
108
|
}
|
|
103
109
|
|
|
110
|
+
if (!this.settingsCache.get('explore_ping')) {
|
|
111
|
+
this.logging.info('Explore ping disabled');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
104
115
|
const payload = await this.constructPayload();
|
|
105
116
|
await this.makeRequest(exploreUrl, payload);
|
|
106
117
|
}
|
|
@@ -25,7 +25,10 @@ module.exports = function getConfigProperties() {
|
|
|
25
25
|
security: config.get('security')
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
if (config.get('explore') && config.get('explore:testimonials_url')) {
|
|
29
|
+
configProperties.exploreTestimonialsUrl = config.get('explore:testimonials_url');
|
|
30
|
+
}
|
|
31
|
+
|
|
29
32
|
if (config.get('tinybird') && config.get('tinybird:stats')) {
|
|
30
33
|
const statsConfig = config.get('tinybird:stats');
|
|
31
34
|
const siteUuid = statsConfig.id || settingsCache.get('site_uuid');
|
|
@@ -19,9 +19,18 @@ const messages = {
|
|
|
19
19
|
const installFromGithub = async (ref) => {
|
|
20
20
|
const [org, repo] = ref.toLowerCase().split('/');
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
if (limitService.isLimited('customThemes')) {
|
|
23
|
+
// The custom theme limit might consist of only one single theme, so we can't rely on
|
|
24
|
+
// the org alone to determine if the request is allowed or not.
|
|
25
|
+
const noOtherThemesAllowed = limitService.limits.customThemes?.allowlist?.length === 1;
|
|
26
|
+
//TODO: move the organization check to config
|
|
27
|
+
const isNotOfficialThemeRequest = org.toLowerCase() !== 'tryghost';
|
|
28
|
+
|
|
29
|
+
const checkThemeLimit = noOtherThemesAllowed || isNotOfficialThemeRequest;
|
|
30
|
+
|
|
31
|
+
if (checkThemeLimit) {
|
|
32
|
+
await limitService.errorIfWouldGoOverLimit('customThemes', {value: repo.toLowerCase()});
|
|
33
|
+
}
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
// omit /:ref so we fetch the default branch
|
|
@@ -62,6 +71,11 @@ const installFromGithub = async (ref) => {
|
|
|
62
71
|
}));
|
|
63
72
|
}
|
|
64
73
|
|
|
74
|
+
if (e instanceof errors.HostLimitError) {
|
|
75
|
+
// If the error is a HostLimitError, we can assume that the theme name is not allowed
|
|
76
|
+
return Promise.reject(e);
|
|
77
|
+
}
|
|
78
|
+
|
|
65
79
|
throw e;
|
|
66
80
|
} finally {
|
|
67
81
|
// clean up tmp dir with downloaded file
|
|
@@ -19,16 +19,15 @@ module.exports = function setupAdminApp() {
|
|
|
19
19
|
const adminApp = express('admin');
|
|
20
20
|
|
|
21
21
|
// Admin assets
|
|
22
|
-
// @TODO ensure this gets a local 404 error handler
|
|
23
|
-
const configMaxAge = config.get('caching:admin:maxAge');
|
|
24
22
|
// @NOTE: when we start working on HTTP/3 optimizations the immutable headers
|
|
25
23
|
// produced below should be split into separate 'Cache-Control' entry.
|
|
26
24
|
// For reference see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#validation_2
|
|
27
|
-
|
|
28
|
-
// is specified in seconds. See https://github.com/expressjs/serve-static/issues/150 for more context
|
|
25
|
+
|
|
29
26
|
adminApp.use('/assets', serveStatic(
|
|
30
27
|
path.join(config.get('paths').adminAssets, 'assets'), {
|
|
31
|
-
|
|
28
|
+
// @NOTE: the maxAge config passed below are in milliseconds and the config
|
|
29
|
+
// is specified in seconds. See https://github.com/expressjs/serve-static/issues/150 for more context
|
|
30
|
+
maxAge: config.get('caching:admin:maxAge') * 1000,
|
|
32
31
|
immutable: true,
|
|
33
32
|
fallthrough: false
|
|
34
33
|
}
|
|
@@ -94,4 +93,4 @@ module.exports = function setupAdminApp() {
|
|
|
94
93
|
debug('Admin setup end');
|
|
95
94
|
|
|
96
95
|
return adminApp;
|
|
97
|
-
};
|
|
96
|
+
};
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
const isString = require('lodash/isString');
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* @param {'public'|'private'} profile Use "private" if you do not want caching
|
|
12
|
+
* @param {'public'|'private'|'noCache'} profile Use "private" if you do not want caching
|
|
13
13
|
* @param {object} [options]
|
|
14
14
|
* @param {number} [options.maxAge] The max-age in seconds to use when profile is "public"
|
|
15
15
|
* @param {number} [options.staleWhileRevalidate] The stale-while-revalidate in seconds to use when profile is "public"
|
|
@@ -24,6 +24,7 @@ const cacheControl = (profile, options = {maxAge: 0}) => {
|
|
|
24
24
|
|
|
25
25
|
const profiles = {
|
|
26
26
|
public: publicOptions.filter(option => option).join(', '),
|
|
27
|
+
noCache: 'no-cache, max-age=0, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0',
|
|
27
28
|
private: 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'
|
|
28
29
|
};
|
|
29
30
|
|