ghost 5.119.3 → 5.120.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-i18n-5.120.0.tgz +0 -0
- package/core/boot.js +0 -2
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +7555 -7216
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-60ce658c.mjs → CodeEditorView-1c5b0683.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-8480baa8.mjs → index-14e518a7.mjs} +3 -3
- package/core/built/admin/assets/admin-x-settings/{index-a2648c61.mjs → index-fc9f985b.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{modals-6900c1d5.mjs → modals-15bc6a0f.mjs} +7192 -6656
- package/core/built/admin/assets/{chunk.137.c9bf40f01afeeadb4660.js → chunk.383.25fca2f09b4896656125.js} +76 -59
- package/core/built/admin/assets/chunk.524.1657b12c0ab25dd9fb79.js +28 -0
- package/core/built/admin/assets/{chunk.582.2697b46a5652693fc674.js → chunk.582.09869b1f1a3cc0ab81f6.js} +19 -26
- package/core/built/admin/assets/{ghost-843572e9507d099162ae744d791daba1.js → ghost-b3b44421acca3b3eec76bfbb6ba0e81b.js} +3 -3
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +12578 -12352
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +423 -211
- package/core/built/admin/assets/posts/posts.js +13680 -13671
- package/core/built/admin/assets/stats/stats.js +16457 -16635
- package/core/built/admin/assets/{vendor-8f805740fee4db959a5b2119001a56b1.js → vendor-4ce6d282a2a00fe486a0951e0591da19.js} +11 -9
- package/core/built/admin/index.html +5 -5
- package/core/frontend/helpers/match.js +6 -0
- package/core/frontend/services/routing/ParentRouter.js +1 -1
- package/core/frontend/services/routing/controllers/email-post.js +0 -2
- package/core/frontend/services/routing/controllers/previews.js +0 -3
- package/core/frontend/web/middleware/frontend-caching.js +2 -2
- package/core/server/api/endpoints/authentication.js +37 -73
- package/core/server/api/endpoints/authors-public.js +8 -9
- package/core/server/api/endpoints/db.js +34 -35
- package/core/server/api/endpoints/emails.js +8 -10
- package/core/server/api/endpoints/integrations.js +20 -18
- package/core/server/api/endpoints/invites.js +8 -10
- package/core/server/api/endpoints/labels.js +19 -23
- package/core/server/api/endpoints/notifications.js +3 -4
- package/core/server/api/endpoints/pages-public.js +8 -10
- package/core/server/api/endpoints/pages.js +14 -18
- package/core/server/api/endpoints/posts-public.js +8 -10
- package/core/server/api/endpoints/posts.js +6 -8
- package/core/server/api/endpoints/previews.js +8 -10
- package/core/server/api/endpoints/redirects.js +7 -8
- package/core/server/api/endpoints/schedules.js +5 -7
- package/core/server/api/endpoints/slugs.js +7 -9
- package/core/server/api/endpoints/snippets.js +16 -20
- package/core/server/api/endpoints/tags-public.js +8 -10
- package/core/server/api/endpoints/tags.js +19 -23
- package/core/server/api/endpoints/themes.js +6 -8
- package/core/server/api/endpoints/users.js +31 -36
- package/core/server/api/endpoints/utils/permissions.js +10 -10
- package/core/server/api/endpoints/utils/serializers/output/roles.js +9 -10
- package/core/server/api/endpoints/utils/validators/input/images.js +43 -52
- package/core/server/api/endpoints/utils/validators/input/invites.js +6 -8
- package/core/server/api/endpoints/webhooks.js +38 -42
- package/core/server/data/migrations/versions/5.120/2025-05-07-14-57-38-add-newsletters-button-corners-column.js +8 -0
- package/core/server/data/migrations/versions/5.120/2025-05-13-17-36-56-add-newsletters-button-style-column.js +8 -0
- package/core/server/data/migrations/versions/5.120/2025-05-14-20-00-15-add-newsletters-setting-columns.js +22 -0
- package/core/server/data/schema/schema.js +6 -1
- package/core/server/lib/image/Gravatar.js +12 -13
- package/core/server/lib/lexical.js +3 -1
- package/core/server/models/newsletter.js +6 -1
- package/core/server/services/api-version-compatibility/index.js +1 -33
- package/core/server/services/auth/session/emails/signin.js +3 -3
- package/core/server/services/email-address/EmailAddressParser.js +52 -0
- package/core/server/services/email-address/EmailAddressParser.js.d.ts +13 -0
- package/core/server/services/email-address/EmailAddressService.js +142 -0
- package/core/server/services/email-address/EmailAddressService.ts +183 -0
- package/core/server/services/email-address/EmailAddressServiceWrapper.js +2 -4
- package/core/server/services/email-analytics/EmailAnalyticsService.js +1 -1
- package/core/server/services/email-analytics/EmailAnalyticsServiceWrapper.js +2 -1
- package/core/server/services/email-service/BatchSendingService.js +703 -0
- package/core/server/services/email-service/EmailBodyCache.js +20 -0
- package/core/server/services/email-service/EmailController.js +94 -0
- package/core/server/services/email-service/EmailEventProcessor.js +267 -0
- package/core/server/services/email-service/EmailEventStorage.js +187 -0
- package/core/server/services/email-service/EmailRenderer.js +1263 -0
- package/core/server/services/email-service/EmailSegmenter.js +74 -0
- package/core/server/services/email-service/EmailService.js +310 -0
- package/core/server/services/email-service/EmailServiceWrapper.js +9 -2
- package/core/server/services/email-service/MailgunEmailProvider.js +191 -0
- package/core/server/services/email-service/SendingService.js +173 -0
- package/core/server/services/email-service/email-templates/partials/feedback-button.hbs +7 -0
- package/core/server/services/email-service/email-templates/partials/latest-posts.hbs +39 -0
- package/core/server/services/email-service/email-templates/partials/paywall.hbs +20 -0
- package/core/server/services/email-service/email-templates/partials/styles.hbs +2348 -0
- package/core/server/services/email-service/email-templates/template.hbs +238 -0
- package/core/server/services/email-service/events/EmailBouncedEvent.js +63 -0
- package/core/server/services/email-service/events/EmailDeliveredEvent.js +49 -0
- package/core/server/services/email-service/events/EmailOpenedEvent.js +49 -0
- package/core/server/services/email-service/events/EmailTemporaryBouncedEvent.js +63 -0
- package/core/server/services/email-service/events/EmailUnsubscribedEvent.js +42 -0
- package/core/server/services/email-service/events/SpamComplaintEvent.js +42 -0
- package/core/server/services/email-service/helpers/register-helpers.js +59 -0
- package/core/server/services/email-suppression-list/MailgunEmailSuppressionList.js +2 -1
- package/core/server/services/explore-ping/index.js +2 -1
- package/core/server/services/mail/GhostMailer.js +1 -1
- package/core/server/services/media-inliner/ExternalMediaInliner.js +2 -1
- package/core/server/services/members/api.js +15 -15
- package/core/server/services/members/emails/signin.js +4 -4
- package/core/server/services/members/emails/signup-paid.js +3 -4
- package/core/server/services/members/emails/signup.js +3 -3
- package/core/server/services/members/emails/subscribe.js +3 -3
- package/core/server/services/members/members-api/repositories/MemberRepository.js +92 -92
- package/core/server/services/members-events/LastSeenAtUpdater.js +1 -1
- package/core/server/services/settings-helpers/SettingsHelpers.js +1 -1
- package/core/server/services/staff/StaffServiceEmails.js +1 -1
- package/core/server/services/stats/PostsStatsService.js +28 -7
- package/core/server/web/api/app.js +0 -1
- package/core/server/web/api/endpoints/admin/app.js +0 -2
- package/core/server/web/api/endpoints/content/app.js +0 -2
- package/core/server/web/api/middleware/upload.js +2 -2
- package/core/shared/custom-theme-settings-cache/CustomThemeSettingsService.js +2 -1
- package/package.json +39 -97
- package/tsconfig.tsbuildinfo +1 -1
- package/yarn.lock +385 -517
- package/components/tryghost-api-framework-5.119.3.tgz +0 -0
- package/components/tryghost-custom-fonts-5.119.3.tgz +0 -0
- package/components/tryghost-domain-events-5.119.3.tgz +0 -0
- package/components/tryghost-email-addresses-5.119.3.tgz +0 -0
- package/components/tryghost-email-service-5.119.3.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.119.3.tgz +0 -0
- package/components/tryghost-i18n-5.119.3.tgz +0 -0
- package/components/tryghost-job-manager-5.119.3.tgz +0 -0
- package/components/tryghost-members-csv-5.119.3.tgz +0 -0
- package/components/tryghost-mw-error-handler-5.119.3.tgz +0 -0
- package/components/tryghost-mw-vhost-5.119.3.tgz +0 -0
- package/components/tryghost-prometheus-metrics-5.119.3.tgz +0 -0
- package/components/tryghost-security-5.119.3.tgz +0 -0
- package/core/built/admin/assets/chunk.524.c86e2e1b3e94d7cb1e4c.js +0 -35
- package/core/server/services/api-version-compatibility/APIVersionCompatibilityService.js +0 -99
- package/core/server/services/api-version-compatibility/VersionNotificationsDataService.js +0 -80
- package/core/server/services/api-version-compatibility/extract-api-key.js +0 -57
- package/core/server/services/api-version-compatibility/mw-api-version-mismatch.js +0 -31
- /package/core/built/admin/assets/{chunk.137.c9bf40f01afeeadb4660.js.LICENSE.txt → chunk.383.25fca2f09b4896656125.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
const validator = require('@tryghost/validator');
|
|
2
|
+
const logging = require('@tryghost/logging');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {object} EmailData
|
|
6
|
+
* @prop {string} html
|
|
7
|
+
* @prop {string} plaintext
|
|
8
|
+
* @prop {string} subject
|
|
9
|
+
* @prop {string} from
|
|
10
|
+
* @prop {string} emailId
|
|
11
|
+
* @prop {string} [replyTo]
|
|
12
|
+
* @prop {Recipient[]} recipients
|
|
13
|
+
* @prop {import("./EmailRenderer").ReplacementDefinition[]} replacementDefinitions
|
|
14
|
+
*
|
|
15
|
+
* @typedef {object} IEmailProviderService
|
|
16
|
+
* @prop {(emailData: EmailData, options: EmailSendingOptions) => Promise<EmailProviderSuccessResponse>} send
|
|
17
|
+
* @prop {() => number} getMaximumRecipients
|
|
18
|
+
* @prop {() => number} getTargetDeliveryWindow
|
|
19
|
+
*
|
|
20
|
+
* @typedef {object} Post
|
|
21
|
+
* @typedef {object} Newsletter
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {import("./EmailRenderer")} EmailRenderer
|
|
26
|
+
* @typedef {import("./EmailRenderer").EmailBody} EmailBody
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {object} EmailSendingOptions
|
|
31
|
+
* @prop {boolean} clickTrackingEnabled
|
|
32
|
+
* @prop {boolean} openTrackingEnabled
|
|
33
|
+
* @prop {Date} deliveryTime
|
|
34
|
+
* @prop {{get(id: string): EmailBody | null, set(id: string, body: EmailBody): void}} [emailBodyCache]
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {import("./EmailRenderer").MemberLike} MemberLike
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {object} Recipient
|
|
43
|
+
* @prop {string} email
|
|
44
|
+
* @prop {Replacement[]} replacements
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {object} Replacement
|
|
49
|
+
* @prop {string} id
|
|
50
|
+
* @prop {RegExp} token
|
|
51
|
+
* @prop {string} value
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @typedef {object} EmailProviderSuccessResponse
|
|
56
|
+
* @prop {string} id
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
class SendingService {
|
|
60
|
+
#emailProvider;
|
|
61
|
+
#emailRenderer;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {object} dependencies
|
|
65
|
+
* @param {IEmailProviderService} dependencies.emailProvider
|
|
66
|
+
* @param {EmailRenderer} dependencies.emailRenderer
|
|
67
|
+
*/
|
|
68
|
+
constructor({
|
|
69
|
+
emailProvider,
|
|
70
|
+
emailRenderer
|
|
71
|
+
}) {
|
|
72
|
+
this.#emailProvider = emailProvider;
|
|
73
|
+
this.#emailRenderer = emailRenderer;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getMaximumRecipients() {
|
|
77
|
+
return this.#emailProvider.getMaximumRecipients();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Returns the configured target delivery window in seconds
|
|
82
|
+
*
|
|
83
|
+
* @returns {number}
|
|
84
|
+
*/
|
|
85
|
+
getTargetDeliveryWindow() {
|
|
86
|
+
return this.#emailProvider.getTargetDeliveryWindow();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Send a given post, rendered for a given newsletter and segment to the members provided in the list
|
|
91
|
+
* @param {object} data
|
|
92
|
+
* @param {Post} data.post
|
|
93
|
+
* @param {Newsletter} data.newsletter
|
|
94
|
+
* @param {string|null} data.segment
|
|
95
|
+
* @param {string|null} data.emailId
|
|
96
|
+
* @param {MemberLike[]} data.members
|
|
97
|
+
* @param {EmailSendingOptions} options
|
|
98
|
+
* @returns {Promise<EmailProviderSuccessResponse>}
|
|
99
|
+
*/
|
|
100
|
+
async send({post, newsletter, segment, members, emailId}, options) {
|
|
101
|
+
const cacheId = emailId + '-' + (segment ?? 'null');
|
|
102
|
+
const isTestEmail = options.isTestEmail ?? false;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @type {EmailBody | null}
|
|
106
|
+
*/
|
|
107
|
+
let emailBody = null;
|
|
108
|
+
|
|
109
|
+
if (options.emailBodyCache) {
|
|
110
|
+
emailBody = options.emailBodyCache.get(cacheId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!emailBody) {
|
|
114
|
+
emailBody = await this.#emailRenderer.renderBody(
|
|
115
|
+
post,
|
|
116
|
+
newsletter,
|
|
117
|
+
segment,
|
|
118
|
+
{
|
|
119
|
+
clickTrackingEnabled: !!options.clickTrackingEnabled
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
if (options.emailBodyCache) {
|
|
123
|
+
options.emailBodyCache.set(cacheId, emailBody);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const recipients = this.buildRecipients(members, emailBody.replacements);
|
|
128
|
+
return await this.#emailProvider.send({
|
|
129
|
+
subject: this.#emailRenderer.getSubject(post, isTestEmail),
|
|
130
|
+
from: this.#emailRenderer.getFromAddress(post, newsletter),
|
|
131
|
+
replyTo: this.#emailRenderer.getReplyToAddress(post, newsletter) ?? undefined,
|
|
132
|
+
html: emailBody.html,
|
|
133
|
+
plaintext: emailBody.plaintext,
|
|
134
|
+
recipients,
|
|
135
|
+
emailId: emailId,
|
|
136
|
+
replacementDefinitions: emailBody.replacements
|
|
137
|
+
}, {
|
|
138
|
+
clickTrackingEnabled: !!options.clickTrackingEnabled,
|
|
139
|
+
openTrackingEnabled: !!options.openTrackingEnabled,
|
|
140
|
+
...(options.deliveryTime && {deliveryTime: options.deliveryTime})
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @private
|
|
146
|
+
* @param {MemberLike[]} members
|
|
147
|
+
* @param {import("./EmailRenderer").ReplacementDefinition[]} replacementDefinitions
|
|
148
|
+
* @returns {Recipient[]}
|
|
149
|
+
*/
|
|
150
|
+
buildRecipients(members, replacementDefinitions) {
|
|
151
|
+
return members.map((member) => {
|
|
152
|
+
return {
|
|
153
|
+
email: member.email?.trim(),
|
|
154
|
+
replacements: replacementDefinitions.map((def) => {
|
|
155
|
+
return {
|
|
156
|
+
id: def.id,
|
|
157
|
+
token: def.token,
|
|
158
|
+
value: def.getValue(member) || ''
|
|
159
|
+
};
|
|
160
|
+
})
|
|
161
|
+
};
|
|
162
|
+
}).filter((recipient) => {
|
|
163
|
+
// Remove invalid recipient email addresses
|
|
164
|
+
const isValidRecipient = validator.isEmail(recipient.email, {legacy: false});
|
|
165
|
+
if (!isValidRecipient) {
|
|
166
|
+
logging.warn(`Removed recipient ${recipient.email} from list because it is not a valid email address`);
|
|
167
|
+
}
|
|
168
|
+
return isValidRecipient;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = SendingService;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<td dir="ltr" valign="top" align="center" style="display: inline-block; vertical-align: top; font-family: inherit; text-align: center; padding: 0 4px 4px; cursor: pointer; width: 30%;">
|
|
2
|
+
<a href="{{href}}" target="_blank">
|
|
3
|
+
<img src={{iconUrl}} border="0" width={{width}} height={{height}} alt="{{buttonText}}">
|
|
4
|
+
<p class="feedback-button-text">{{t buttonText}}</p>
|
|
5
|
+
{{!-- Button text possible values: {{t 'More like this'}} {{t 'Less like this'}} {{t 'Comment'}} --}}
|
|
6
|
+
</a>
|
|
7
|
+
</td>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{{#each latestPosts}}
|
|
2
|
+
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
|
3
|
+
<tr>
|
|
4
|
+
<td class="latest-post">
|
|
5
|
+
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
|
6
|
+
<tr>
|
|
7
|
+
<td valign="top" align="left" class="latest-post-title">
|
|
8
|
+
<h4 class="{{#if ../latestPostsHasImages}}{{#if (not featureImage)}}no-image{{/if}}{{/if}}">
|
|
9
|
+
<a href="{{url}}">{{{title}}}</a>
|
|
10
|
+
</h4>
|
|
11
|
+
{{#if excerpt}}
|
|
12
|
+
<p class="latest-post-excerpt">
|
|
13
|
+
<a href="{{url}}">{{{excerpt}}}</a>
|
|
14
|
+
</p>
|
|
15
|
+
{{/if}}
|
|
16
|
+
</td>
|
|
17
|
+
{{#if ../latestPostsHasImages}}
|
|
18
|
+
{{#if featureImage}}
|
|
19
|
+
<td width="100" class="latest-post-img">
|
|
20
|
+
<a href="{{url}}">
|
|
21
|
+
<img
|
|
22
|
+
src="{{featureImage.src}}"
|
|
23
|
+
{{#if featureImage.width }}
|
|
24
|
+
width="{{featureImage.width}}"
|
|
25
|
+
{{/if}}
|
|
26
|
+
{{#if featureImage.height }}
|
|
27
|
+
height="{{featureImage.height}}"
|
|
28
|
+
{{/if}}
|
|
29
|
+
/>
|
|
30
|
+
</a>
|
|
31
|
+
</td>
|
|
32
|
+
{{/if}}
|
|
33
|
+
{{/if}}
|
|
34
|
+
</tr>
|
|
35
|
+
</table>
|
|
36
|
+
</td>
|
|
37
|
+
</tr>
|
|
38
|
+
</table>
|
|
39
|
+
{{/each}}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<div class="align-center" style="text-align: center;">
|
|
2
|
+
<hr
|
|
3
|
+
style="position: relative; display: block; width: 100%; margin: 3em 0; padding: 0; height: 1px; border: 0; border-top: 1px solid #e0e7eb;">
|
|
4
|
+
<h3
|
|
5
|
+
style="margin-top: 0; font-family: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; line-height: 1.11em; font-weight: 700; text-rendering: optimizeLegibility; margin: 1.5em 0 0.5em 0; text-wrap: pretty;">
|
|
6
|
+
{{t 'Upgrade to continue reading.'}}</h3>
|
|
7
|
+
<p style="margin: 0 auto 1.5em auto; line-height: 1.6em; max-width: 440px; text-wrap: pretty;">{{{t 'Become a paid member of {site} to get access to all premium content.' site=site.title}}}</p>
|
|
8
|
+
<div class="btn btn-accent" style="box-sizing: border-box; width: 100%; display: table;">
|
|
9
|
+
<table border="0" cellspacing="0" cellpadding="0" align="center" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
|
|
10
|
+
<tbody>
|
|
11
|
+
<tr>
|
|
12
|
+
<td align="center" valign="top" bgcolor="{{accentColor}}" style="vertical-align: top; text-align: center; border-radius: 5px;">
|
|
13
|
+
<a href="{{paywall.signupUrl}}" style="overflow-wrap: anywhere; border: solid 1px #3498db; border-radius: 5px; box-sizing: border-box; cursor: pointer; display: inline-block; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px; text-decoration: none; background-color: {{accentColor}}; border-color: {{accentColor}}; color: #FFFFFF;" target="_blank">{{t 'Upgrade'}}</a>
|
|
14
|
+
</td>
|
|
15
|
+
</tr>
|
|
16
|
+
</tbody>
|
|
17
|
+
</table>
|
|
18
|
+
</div>
|
|
19
|
+
<p style="margin: 0 0 1.5em 0; line-height: 1.6em;"></p>
|
|
20
|
+
</div>
|