ghost 5.127.0 → 5.127.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.
Files changed (23) hide show
  1. package/components/tryghost-i18n-5.127.1.tgz +0 -0
  2. package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +2 -2
  3. package/core/built/admin/assets/admin-x-activitypub/{index-9vyYGTFg.mjs → index-BhzxZm-7.mjs} +8319 -8130
  4. package/core/built/admin/assets/admin-x-activitypub/{index-DsFs5lK0.mjs → index-BqFrbO6E.mjs} +2 -2
  5. package/core/built/admin/assets/{chunk.524.26236112ccb590199030.js → chunk.524.e6b0293eefcd2d23848f.js} +6 -6
  6. package/core/built/admin/assets/{chunk.582.8f3427a3fdd4dc0c14b4.js → chunk.582.dbda4c7f49cd241a0767.js} +8 -8
  7. package/core/built/admin/assets/{ghost-f22a17dd4f8592f0bf6ae28d6d454d9b.js → ghost-71ab6cab4367841fc79ac68cd664b0de.js} +214 -259
  8. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +95521 -87956
  9. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +1008 -126
  10. package/core/built/admin/assets/posts/posts.js +630 -733
  11. package/core/built/admin/assets/stats/stats.js +31295 -31199
  12. package/core/built/admin/index.html +3 -3
  13. package/core/server/api/endpoints/stats.js +40 -30
  14. package/core/server/services/email-service/email-templates/partials/styles.hbs +0 -8
  15. package/core/server/services/koenig/node-renderers/call-to-action-renderer.js +1 -1
  16. package/core/server/services/koenig/render-partials/email-button.js +5 -1
  17. package/core/server/services/stats/PostsStatsService.js +352 -91
  18. package/core/server/services/stats/ReferrersStatsService.js +215 -71
  19. package/core/server/services/stats/StatsService.js +9 -13
  20. package/core/server/web/api/endpoints/admin/routes.js +1 -1
  21. package/package.json +7 -7
  22. package/yarn.lock +57 -30
  23. package/components/tryghost-i18n-5.127.0.tgz +0 -0
@@ -1,5 +1,121 @@
1
1
  const moment = require('moment');
2
2
 
3
+ // Source normalization mapping - consolidated from frontend apps
4
+ const SOURCE_NORMALIZATION_MAP = new Map([
5
+ // Social Media Consolidation
6
+ ['facebook', 'Facebook'],
7
+ ['www.facebook.com', 'Facebook'],
8
+ ['l.facebook.com', 'Facebook'],
9
+ ['lm.facebook.com', 'Facebook'],
10
+ ['m.facebook.com', 'Facebook'],
11
+ ['twitter', 'Twitter'],
12
+ ['x.com', 'Twitter'],
13
+ ['com.twitter.android', 'Twitter'],
14
+ ['go.bsky.app', 'Bluesky'],
15
+ ['bsky', 'Bluesky'],
16
+ ['bsky.app', 'Bluesky'],
17
+ ['linkedin', 'LinkedIn'],
18
+ ['www.linkedin.com', 'LinkedIn'],
19
+ ['linkedin.com', 'LinkedIn'],
20
+ ['instagram', 'Instagram'],
21
+ ['www.instagram.com', 'Instagram'],
22
+ ['instagram.com', 'Instagram'],
23
+ ['youtube', 'YouTube'],
24
+ ['www.youtube.com', 'YouTube'],
25
+ ['youtube.com', 'YouTube'],
26
+ ['m.youtube.com', 'YouTube'],
27
+ ['threads', 'Threads'],
28
+ ['www.threads.net', 'Threads'],
29
+ ['threads.net', 'Threads'],
30
+ ['tiktok', 'TikTok'],
31
+ ['www.tiktok.com', 'TikTok'],
32
+ ['tiktok.com', 'TikTok'],
33
+ ['pinterest', 'Pinterest'],
34
+ ['www.pinterest.com', 'Pinterest'],
35
+ ['pinterest.com', 'Pinterest'],
36
+ ['reddit', 'Reddit'],
37
+ ['www.reddit.com', 'Reddit'],
38
+ ['reddit.com', 'Reddit'],
39
+ ['whatsapp', 'WhatsApp'],
40
+ ['whatsapp.com', 'WhatsApp'],
41
+ ['www.whatsapp.com', 'WhatsApp'],
42
+ ['telegram', 'Telegram'],
43
+ ['telegram.org', 'Telegram'],
44
+ ['www.telegram.org', 'Telegram'],
45
+ ['t.me', 'Telegram'],
46
+ ['news.ycombinator.com', 'Hacker News'],
47
+ ['substack', 'Substack'],
48
+ ['substack.com', 'Substack'],
49
+ ['www.substack.com', 'Substack'],
50
+ ['medium', 'Medium'],
51
+ ['medium.com', 'Medium'],
52
+ ['www.medium.com', 'Medium'],
53
+
54
+ // Search Engines
55
+ ['google', 'Google'],
56
+ ['www.google.com', 'Google'],
57
+ ['google.com', 'Google'],
58
+ ['bing', 'Bing'],
59
+ ['www.bing.com', 'Bing'],
60
+ ['bing.com', 'Bing'],
61
+ ['yahoo', 'Yahoo'],
62
+ ['www.yahoo.com', 'Yahoo'],
63
+ ['yahoo.com', 'Yahoo'],
64
+ ['search.yahoo.com', 'Yahoo'],
65
+ ['duckduckgo', 'DuckDuckGo'],
66
+ ['duckduckgo.com', 'DuckDuckGo'],
67
+ ['www.duckduckgo.com', 'DuckDuckGo'],
68
+ ['search.brave.com', 'Brave Search'],
69
+ ['yandex', 'Yandex'],
70
+ ['yandex.com', 'Yandex'],
71
+ ['www.yandex.com', 'Yandex'],
72
+ ['baidu', 'Baidu'],
73
+ ['baidu.com', 'Baidu'],
74
+ ['www.baidu.com', 'Baidu'],
75
+ ['ecosia', 'Ecosia'],
76
+ ['www.ecosia.org', 'Ecosia'],
77
+ ['ecosia.org', 'Ecosia'],
78
+
79
+ // Email Platforms
80
+ ['gmail', 'Gmail'],
81
+ ['mail.google.com', 'Gmail'],
82
+ ['gmail.com', 'Gmail'],
83
+ ['outlook', 'Outlook'],
84
+ ['outlook.live.com', 'Outlook'],
85
+ ['outlook.com', 'Outlook'],
86
+ ['hotmail.com', 'Outlook'],
87
+ ['mail.yahoo.com', 'Yahoo Mail'],
88
+ ['ymail.com', 'Yahoo Mail'],
89
+ ['icloud.com', 'Apple Mail'],
90
+ ['me.com', 'Apple Mail'],
91
+ ['mac.com', 'Apple Mail'],
92
+
93
+ // News Aggregators
94
+ ['news.google.com', 'Google News'],
95
+ ['apple.news', 'Apple News'],
96
+ ['flipboard', 'Flipboard'],
97
+ ['flipboard.com', 'Flipboard'],
98
+ ['www.flipboard.com', 'Flipboard'],
99
+ ['smartnews', 'SmartNews'],
100
+ ['smartnews.com', 'SmartNews'],
101
+ ['www.smartnews.com', 'SmartNews']
102
+ ]);
103
+
104
+ /**
105
+ * Normalize source names to consistent display names
106
+ * @param {string|null} source - Raw source string from referrer data
107
+ * @returns {string} Normalized source name or 'Direct' for empty/null sources
108
+ */
109
+ function normalizeSource(source) {
110
+ if (!source || source === '') {
111
+ return 'Direct';
112
+ }
113
+
114
+ // Case-insensitive lookup
115
+ const lowerSource = source.toLowerCase();
116
+ return SOURCE_NORMALIZATION_MAP.get(lowerSource) || source;
117
+ }
118
+
3
119
  class ReferrersStatsService {
4
120
  /**
5
121
  * @param {object} deps
@@ -34,21 +150,25 @@ class ReferrersStatsService {
34
150
 
35
151
  const map = new Map();
36
152
  for (const row of signupRows) {
37
- map.set(row.referrer_source, {
38
- source: row.referrer_source,
39
- signups: row.total,
153
+ const normalizedSource = normalizeSource(row.referrer_source);
154
+ const existing = map.get(normalizedSource) || {
155
+ source: normalizedSource,
156
+ signups: 0,
40
157
  paid_conversions: 0
41
- });
158
+ };
159
+ existing.signups += row.total;
160
+ map.set(normalizedSource, existing);
42
161
  }
43
162
 
44
163
  for (const row of conversionRows) {
45
- const existing = map.get(row.referrer_source) ?? {
46
- source: row.referrer_source,
164
+ const normalizedSource = normalizeSource(row.referrer_source);
165
+ const existing = map.get(normalizedSource) || {
166
+ source: normalizedSource,
47
167
  signups: 0,
48
168
  paid_conversions: 0
49
169
  };
50
- existing.paid_conversions = row.total;
51
- map.set(row.referrer_source, existing);
170
+ existing.paid_conversions += row.total;
171
+ map.set(normalizedSource, existing);
52
172
  }
53
173
 
54
174
  return [...map.values()].sort((a, b) => b.paid_conversions - a.paid_conversions);
@@ -96,69 +216,6 @@ class ReferrersStatsService {
96
216
  };
97
217
  }
98
218
 
99
- /**
100
- * Return a list of all the attribution sources with date range filtering, including MRR data
101
- * @param {string} startDate - Start date in YYYY-MM-DD format
102
- * @param {string} endDate - End date in YYYY-MM-DD format
103
- * @returns {Promise<{data: AttributionCountStatWithMrr[], meta: {}}>}
104
- */
105
- async getReferrersHistoryWithRange(startDate, endDate) {
106
- const paidConversionEntries = await this.fetchPaidConversionSourcesWithRange(startDate, endDate);
107
- const signupEntries = await this.fetchSignupSourcesWithRange(startDate, endDate);
108
- const mrrEntries = await this.fetchMrrSourcesWithRange(startDate, endDate);
109
-
110
- const allEntries = signupEntries.map((entry) => {
111
- return {
112
- ...entry,
113
- paid_conversions: 0,
114
- mrr: 0,
115
- date: moment(entry.date).format('YYYY-MM-DD')
116
- };
117
- });
118
-
119
- paidConversionEntries.forEach((entry) => {
120
- const entryDate = moment(entry.date).format('YYYY-MM-DD');
121
- const existingEntry = allEntries.find(e => e.source === entry.source && e.date === entryDate);
122
-
123
- if (existingEntry) {
124
- existingEntry.paid_conversions = entry.paid_conversions;
125
- } else {
126
- allEntries.push({
127
- ...entry,
128
- signups: 0,
129
- mrr: 0,
130
- date: entryDate
131
- });
132
- }
133
- });
134
-
135
- mrrEntries.forEach((entry) => {
136
- const entryDate = moment(entry.date).format('YYYY-MM-DD');
137
- const existingEntry = allEntries.find(e => e.source === entry.source && e.date === entryDate);
138
-
139
- if (existingEntry) {
140
- existingEntry.mrr = entry.mrr;
141
- } else {
142
- allEntries.push({
143
- ...entry,
144
- signups: 0,
145
- paid_conversions: 0,
146
- date: entryDate
147
- });
148
- }
149
- });
150
-
151
- // sort allEntries in date ascending format
152
- allEntries.sort((a, b) => {
153
- return moment(a.date).diff(moment(b.date));
154
- });
155
-
156
- return {
157
- data: allEntries,
158
- meta: {}
159
- };
160
- }
161
-
162
219
  /**
163
220
  * @returns {Promise<PaidConversionsCountStatDate[]>}
164
221
  **/
@@ -268,9 +325,96 @@ class ReferrersStatsService {
268
325
 
269
326
  return rows;
270
327
  }
328
+
329
+ /**
330
+ * Return aggregated attribution sources for a date range, grouped by source only (not by date)
331
+ * This is used for "Top Sources" tables that need server-side sorting
332
+ * @param {string} startDate - Start date in YYYY-MM-DD format
333
+ * @param {string} endDate - End date in YYYY-MM-DD format
334
+ * @param {string} [orderBy='signups desc'] - Sort order: 'signups desc', 'paid_conversions desc', 'mrr desc', 'source desc'
335
+ * @param {number} [limit=50] - Maximum number of sources to return
336
+ * @returns {Promise<{data: AttributionCountStatWithMrr[], meta: {}}>}
337
+ */
338
+ async getTopSourcesWithRange(startDate, endDate, orderBy = 'signups desc', limit = 50) {
339
+ const paidConversionEntries = await this.fetchPaidConversionSourcesWithRange(startDate, endDate);
340
+ const signupEntries = await this.fetchSignupSourcesWithRange(startDate, endDate);
341
+ const mrrEntries = await this.fetchMrrSourcesWithRange(startDate, endDate);
342
+
343
+ // Aggregate by source (not by date + source)
344
+ const sourceMap = new Map();
345
+
346
+ // Add signup data
347
+ signupEntries.forEach((entry) => {
348
+ const source = normalizeSource(entry.source);
349
+ const existing = sourceMap.get(source) || {source, signups: 0, paid_conversions: 0, mrr: 0};
350
+ existing.signups += entry.signups;
351
+ sourceMap.set(source, existing);
352
+ });
353
+
354
+ // Add paid conversion data
355
+ paidConversionEntries.forEach((entry) => {
356
+ const source = normalizeSource(entry.source);
357
+ const existing = sourceMap.get(source) || {source, signups: 0, paid_conversions: 0, mrr: 0};
358
+ existing.paid_conversions += entry.paid_conversions;
359
+ sourceMap.set(source, existing);
360
+ });
361
+
362
+ // Add MRR data
363
+ mrrEntries.forEach((entry) => {
364
+ const source = normalizeSource(entry.source);
365
+ const existing = sourceMap.get(source) || {source, signups: 0, paid_conversions: 0, mrr: 0};
366
+ existing.mrr += entry.mrr;
367
+ sourceMap.set(source, existing);
368
+ });
369
+
370
+ // Convert to array and sort
371
+ let results = Array.from(sourceMap.values());
372
+
373
+ // Apply sorting - only allow descending sorts for sources
374
+ const [field] = orderBy.split(' ');
375
+
376
+ results.sort((a, b) => {
377
+ let valueA; let valueB;
378
+
379
+ switch (field) {
380
+ case 'signups':
381
+ valueA = a.signups;
382
+ valueB = b.signups;
383
+ break;
384
+ case 'paid_conversions':
385
+ valueA = a.paid_conversions;
386
+ valueB = b.paid_conversions;
387
+ break;
388
+ case 'mrr':
389
+ valueA = a.mrr;
390
+ valueB = b.mrr;
391
+ break;
392
+ case 'source':
393
+ valueA = a.source.toLowerCase();
394
+ valueB = b.source.toLowerCase();
395
+ break;
396
+ default:
397
+ return 0;
398
+ }
399
+
400
+ // Always sort in descending order (highest to lowest)
401
+ return valueA < valueB ? 1 : valueA > valueB ? -1 : 0;
402
+ });
403
+
404
+ // Apply limit
405
+ if (limit && limit > 0) {
406
+ results = results.slice(0, limit);
407
+ }
408
+
409
+ return {
410
+ data: results,
411
+ meta: {}
412
+ };
413
+ }
271
414
  }
272
415
 
273
416
  module.exports = ReferrersStatsService;
417
+ module.exports.normalizeSource = normalizeSource;
274
418
 
275
419
  /**
276
420
  * @typedef AttributionCountStat
@@ -53,14 +53,6 @@ class StatsService {
53
53
  return this.referrers.getReferrersHistory();
54
54
  }
55
55
 
56
- /**
57
- * @param {string} startDate - Start date in YYYY-MM-DD format
58
- * @param {string} endDate - End date in YYYY-MM-DD format
59
- */
60
- async getReferrersHistoryWithRange(startDate, endDate) {
61
- return this.referrers.getReferrersHistoryWithRange(startDate, endDate);
62
- }
63
-
64
56
  /**
65
57
  * @param {string} postId
66
58
  */
@@ -87,9 +79,9 @@ class StatsService {
87
79
  }
88
80
 
89
81
  /**
90
- * Get top posts by attribution metrics
82
+ * Get top posts by attribution metrics (includes all content that drove conversions)
91
83
  * @param {import('./PostsStatsService').TopPostsOptions} options
92
- * @returns {Promise<{data: import('./PostsStatsService').TopPostResult[]}>}
84
+ * @returns {Promise<{data: import('./PostsStatsService').AttributionResult[]}>}
93
85
  */
94
86
  async getTopPosts(options = {}) {
95
87
  // Return the original { data: results } structure
@@ -133,7 +125,7 @@ class StatsService {
133
125
  * @param {number} [options.limit=20] - Max number of results to return
134
126
  * @param {string} [options.date_from] - Start date filter in YYYY-MM-DD format
135
127
  * @param {string} [options.date_to] - End date filter in YYYY-MM-DD format
136
- * @returns {Promise<{data: Object[]}>}
128
+ * @returns {Promise<{data: import('./PostsStatsService').NewsletterStatResult[]}>}
137
129
  */
138
130
  async getNewsletterStats(options = {}) {
139
131
  // Extract newsletter_id from options
@@ -156,7 +148,7 @@ class StatsService {
156
148
  * @param {string} [options.newsletter_id] - ID of the specific newsletter to get stats for
157
149
  * @param {string} [options.date_from] - Start date filter in YYYY-MM-DD format
158
150
  * @param {string} [options.date_to] - End date filter in YYYY-MM-DD format
159
- * @returns {Promise<{data: Object}>}
151
+ * @returns {Promise<{data: import('./PostsStatsService').NewsletterSubscriberStats[]}>}
160
152
  */
161
153
  async getNewsletterSubscriberStats(options = {}) {
162
154
  // Extract newsletter_id from options
@@ -202,7 +194,7 @@ class StatsService {
202
194
  * @param {number} [options.limit=20] - Max number of results to return
203
195
  * @param {string} [options.date_from] - Start date filter in YYYY-MM-DD format
204
196
  * @param {string} [options.date_to] - End date filter in YYYY-MM-DD format
205
- * @returns {Promise<{data: Object[]}>}
197
+ * @returns {Promise<{data: import('./PostsStatsService').NewsletterStatResult[]}>}
206
198
  */
207
199
  async getNewsletterBasicStats(options = {}) {
208
200
  // Extract newsletter_id from options
@@ -239,6 +231,10 @@ class StatsService {
239
231
  return result;
240
232
  }
241
233
 
234
+ async getTopSourcesWithRange(startDate, endDate, orderBy, limit) {
235
+ return this.referrers.getTopSourcesWithRange(startDate, endDate, orderBy, limit);
236
+ }
237
+
242
238
  /**
243
239
  * @param {object} deps
244
240
  *
@@ -155,7 +155,6 @@ module.exports = function apiRoutes() {
155
155
  router.get('/stats/subscriptions', mw.authAdminApi, http(api.stats.subscriptions));
156
156
  router.get('/stats/referrers/posts/:id', mw.authAdminApi, http(api.stats.postReferrers));
157
157
  router.get('/stats/referrers', mw.authAdminApi, http(api.stats.referrersHistory));
158
- router.get('/stats/referrers-growth', mw.authAdminApi, http(api.stats.referrersHistoryWithRange));
159
158
  if (labs.isSet('trafficAnalytics')) {
160
159
  router.get('/stats/posts/:id/stats', mw.authAdminApi, http(api.stats.postStats));
161
160
  router.get('/stats/top-posts', mw.authAdminApi, http(api.stats.topPosts));
@@ -167,6 +166,7 @@ module.exports = function apiRoutes() {
167
166
  router.get('/stats/subscriber-count', mw.authAdminApi, http(api.stats.subscriberCount));
168
167
  router.get('/stats/posts/:id/top-referrers', mw.authAdminApi, http(api.stats.postReferrersAlpha));
169
168
  router.get('/stats/posts/:id/growth', mw.authAdminApi, http(api.stats.postGrowthStats));
169
+ router.get('/stats/top-sources-growth', mw.authAdminApi, http(api.stats.topSourcesGrowth));
170
170
  router.post('/stats/posts-visitor-counts', mw.authAdminApi, http(api.stats.postsVisitorCounts));
171
171
  router.post('/stats/posts-member-counts', mw.authAdminApi, http(api.stats.postsMemberCounts));
172
172
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghost",
3
- "version": "5.127.0",
3
+ "version": "5.127.1",
4
4
  "description": "The professional publishing platform",
5
5
  "author": "Ghost Foundation",
6
6
  "homepage": "https://ghost.org",
@@ -86,7 +86,7 @@
86
86
  "@tryghost/helpers": "1.1.95",
87
87
  "@tryghost/html-to-plaintext": "1.0.4",
88
88
  "@tryghost/http-cache-utils": "0.1.20",
89
- "@tryghost/i18n": "file:components/tryghost-i18n-5.127.0.tgz",
89
+ "@tryghost/i18n": "file:components/tryghost-i18n-5.127.1.tgz",
90
90
  "@tryghost/image-transform": "1.4.5",
91
91
  "@tryghost/job-manager": "1.0.1",
92
92
  "@tryghost/kg-card-factory": "5.1.2",
@@ -94,10 +94,10 @@
94
94
  "@tryghost/kg-converters": "1.1.7",
95
95
  "@tryghost/kg-default-atoms": "5.1.1",
96
96
  "@tryghost/kg-default-cards": "10.1.5",
97
- "@tryghost/kg-default-nodes": "2.0.0",
98
- "@tryghost/kg-default-transforms": "1.2.23",
99
- "@tryghost/kg-html-to-lexical": "1.2.23",
100
- "@tryghost/kg-lexical-html-renderer": "1.3.23",
97
+ "@tryghost/kg-default-nodes": "2.0.1",
98
+ "@tryghost/kg-default-transforms": "1.2.24",
99
+ "@tryghost/kg-html-to-lexical": "1.2.24",
100
+ "@tryghost/kg-lexical-html-renderer": "1.3.24",
101
101
  "@tryghost/kg-markdown-html-renderer": "7.1.3",
102
102
  "@tryghost/kg-mobiledoc-html-renderer": "7.1.3",
103
103
  "@tryghost/limit-service": "1.2.18",
@@ -276,7 +276,7 @@
276
276
  "jackspeak": "2.3.6",
277
277
  "moment": "2.24.0",
278
278
  "moment-timezone": "0.5.45",
279
- "@tryghost/i18n": "file:components/tryghost-i18n-5.127.0.tgz"
279
+ "@tryghost/i18n": "file:components/tryghost-i18n-5.127.1.tgz"
280
280
  },
281
281
  "nx": {
282
282
  "targets": {
package/yarn.lock CHANGED
@@ -8031,34 +8031,39 @@
8031
8031
  lodash "^4.17.21"
8032
8032
  luxon "^3.5.0"
8033
8033
 
8034
- "@tryghost/kg-default-nodes@2.0.0":
8035
- version "2.0.0"
8036
- resolved "https://registry.yarnpkg.com/@tryghost/kg-default-nodes/-/kg-default-nodes-2.0.0.tgz#f2ffc29fbdde9ad1e9fecff712e126221bb83631"
8037
- integrity sha512-GiiC2vz/xjh8d8MDT9s4VKNcMtju3/IFWF4SKux2oKZ/VuLI+cJK4xhzxflc6vurx82oPlydHj6+DrbbYGDWUg==
8034
+ "@tryghost/kg-default-nodes@2.0.1":
8035
+ version "2.0.1"
8036
+ resolved "https://registry.yarnpkg.com/@tryghost/kg-default-nodes/-/kg-default-nodes-2.0.1.tgz#987d7708a8f7395b934d4d5e849a594484b87150"
8037
+ integrity sha512-DPO1bvnrnIh7xlHxtnICCbs88qGKeslT0S3MowOtiJ0FG6xRdl1+J0h5JitS38NjQi+HZxSDW75hYdmWvVQfCg==
8038
8038
  dependencies:
8039
8039
  "@lexical/clipboard" "0.13.1"
8040
8040
  "@lexical/rich-text" "0.13.1"
8041
8041
  "@lexical/selection" "0.13.1"
8042
8042
  "@lexical/utils" "0.13.1"
8043
8043
  "@tryghost/kg-clean-basic-html" "4.2.7"
8044
+ "@tryghost/kg-markdown-html-renderer" "7.1.2"
8045
+ clsx "2.1.1"
8046
+ html-minifier "^4.0.0"
8044
8047
  jsdom "^24.1.1"
8045
8048
  lexical "0.13.1"
8049
+ lodash "^4.17.21"
8050
+ luxon "^3.5.0"
8046
8051
 
8047
- "@tryghost/kg-default-transforms@1.2.23":
8048
- version "1.2.23"
8049
- resolved "https://registry.yarnpkg.com/@tryghost/kg-default-transforms/-/kg-default-transforms-1.2.23.tgz#1962bf218458f137e1bb7bcb32d04fc6d1f9c089"
8050
- integrity sha512-TDKkjv/Ki1qqF6zCceSLloYwPqePy8KAaTSXn7FYKiUSDQAVvOuiFoGDGMsAGEIspVft3oV7Y29KOpYtqTj72A==
8052
+ "@tryghost/kg-default-transforms@1.2.24":
8053
+ version "1.2.24"
8054
+ resolved "https://registry.yarnpkg.com/@tryghost/kg-default-transforms/-/kg-default-transforms-1.2.24.tgz#f539b1b9763d81fc649bc31b9d7798a314b677f8"
8055
+ integrity sha512-AgoA7nUqAtsVJiROdA1FXvoNr92cNxEDSY4ndPd7nqXP1K2NHX5hqNYb6C2m2vds7lJKA6aXEFJPs+ddpgWS5w==
8051
8056
  dependencies:
8052
8057
  "@lexical/list" "0.13.1"
8053
8058
  "@lexical/rich-text" "0.13.1"
8054
8059
  "@lexical/utils" "0.13.1"
8055
- "@tryghost/kg-default-nodes" "2.0.0"
8060
+ "@tryghost/kg-default-nodes" "2.0.1"
8056
8061
  lexical "0.13.1"
8057
8062
 
8058
- "@tryghost/kg-html-to-lexical@1.2.23":
8059
- version "1.2.23"
8060
- resolved "https://registry.yarnpkg.com/@tryghost/kg-html-to-lexical/-/kg-html-to-lexical-1.2.23.tgz#0ec0c0502cf4e86491d337237cb2036625f79a0a"
8061
- integrity sha512-bIlgZWOBPiR9DFH2zIcyd6KpqP5QmuAnodzrwGgrq1dy8OWemM0XrnQP3IXRAiJDxOF6+4fKjAvlyl9we5K+iw==
8063
+ "@tryghost/kg-html-to-lexical@1.2.24":
8064
+ version "1.2.24"
8065
+ resolved "https://registry.yarnpkg.com/@tryghost/kg-html-to-lexical/-/kg-html-to-lexical-1.2.24.tgz#a51c27cb2fc2a77b3bca840ddc036f80bd22162e"
8066
+ integrity sha512-8XH3a9UiaSK3qludJj6IRJKaPvk9oFBFKSl9PZnnvraP/fVZBiBIERP4MmwYUMHrqAieA1SQx+ueESES+8xSCw==
8062
8067
  dependencies:
8063
8068
  "@lexical/clipboard" "0.13.1"
8064
8069
  "@lexical/headless" "0.13.1"
@@ -8066,15 +8071,15 @@
8066
8071
  "@lexical/link" "0.13.1"
8067
8072
  "@lexical/list" "0.13.1"
8068
8073
  "@lexical/rich-text" "0.13.1"
8069
- "@tryghost/kg-default-nodes" "2.0.0"
8070
- "@tryghost/kg-default-transforms" "1.2.23"
8074
+ "@tryghost/kg-default-nodes" "2.0.1"
8075
+ "@tryghost/kg-default-transforms" "1.2.24"
8071
8076
  jsdom "^24.1.1"
8072
8077
  lexical "0.13.1"
8073
8078
 
8074
- "@tryghost/kg-lexical-html-renderer@1.3.23":
8075
- version "1.3.23"
8076
- resolved "https://registry.yarnpkg.com/@tryghost/kg-lexical-html-renderer/-/kg-lexical-html-renderer-1.3.23.tgz#189ffa000eca596603b51526491c0829bfeeb87c"
8077
- integrity sha512-SvBnhq8iOTJQUUv6NeF4pfMEfD3rqKs0tRValpUoMchTbbVKeuUnZfjfN/9qyMQSXGY9a7WqGo5WWdohanxdrw==
8079
+ "@tryghost/kg-lexical-html-renderer@1.3.24":
8080
+ version "1.3.24"
8081
+ resolved "https://registry.yarnpkg.com/@tryghost/kg-lexical-html-renderer/-/kg-lexical-html-renderer-1.3.24.tgz#458b9adc66ac9d7853f25b93b258ffbba8412581"
8082
+ integrity sha512-Cuc4TL9itKhzcpM8FwQdxwQZQeb4Xe164jfJyygmsrkRg7mAwv7iRWmiHVL656nNaIyNFp4nplJxkHEXnLW5FQ==
8078
8083
  dependencies:
8079
8084
  "@lexical/clipboard" "0.13.1"
8080
8085
  "@lexical/code" "0.13.1"
@@ -8082,11 +8087,26 @@
8082
8087
  "@lexical/link" "0.13.1"
8083
8088
  "@lexical/list" "0.13.1"
8084
8089
  "@lexical/rich-text" "0.13.1"
8085
- "@tryghost/kg-default-nodes" "2.0.0"
8086
- "@tryghost/kg-default-transforms" "1.2.23"
8090
+ "@tryghost/kg-default-nodes" "2.0.1"
8091
+ "@tryghost/kg-default-transforms" "1.2.24"
8087
8092
  jsdom "^24.1.1"
8088
8093
  lexical "0.13.1"
8089
8094
 
8095
+ "@tryghost/kg-markdown-html-renderer@7.1.2":
8096
+ version "7.1.2"
8097
+ resolved "https://registry.yarnpkg.com/@tryghost/kg-markdown-html-renderer/-/kg-markdown-html-renderer-7.1.2.tgz#f896c70acdc8ab9e25174343dd68646d631a06f3"
8098
+ integrity sha512-7/im5iBkBcGvTy+ju5CKChsix+RBsjimLLnHC6agnhr5NzWhSPNzhV57J0msqihbwboDEFr5UPXb6XfiKJvBHw==
8099
+ dependencies:
8100
+ "@tryghost/kg-utils" "1.0.31"
8101
+ markdown-it "^14.0.0"
8102
+ markdown-it-footnote "^4.0.0"
8103
+ markdown-it-image-lazy-loading "^2.0.0"
8104
+ markdown-it-lazy-headers "^0.1.3"
8105
+ markdown-it-mark "^4.0.0"
8106
+ markdown-it-sub "^2.0.0"
8107
+ markdown-it-sup "^2.0.0"
8108
+ semver "^7.7.0"
8109
+
8090
8110
  "@tryghost/kg-markdown-html-renderer@7.1.3":
8091
8111
  version "7.1.3"
8092
8112
  resolved "https://registry.yarnpkg.com/@tryghost/kg-markdown-html-renderer/-/kg-markdown-html-renderer-7.1.3.tgz#f00370d0991cb519b62cd8d39421f1d5c5e19b63"
@@ -8118,10 +8138,17 @@
8118
8138
  dependencies:
8119
8139
  "@tryghost/kg-clean-basic-html" "4.2.7"
8120
8140
 
8121
- "@tryghost/kg-unsplash-selector@0.3.5":
8122
- version "0.3.5"
8123
- resolved "https://registry.yarnpkg.com/@tryghost/kg-unsplash-selector/-/kg-unsplash-selector-0.3.5.tgz#2cc6e777fe12c4025f7b2ad384991d551e2ca543"
8124
- integrity sha512-yPAoVVO2ICRxRXJSbrijSlp6YCJ6GozCMKVMBnCIg4xbagDQI7hFklXJVE65URpqv1twDazXCitfV+Jf+kJ9yA==
8141
+ "@tryghost/kg-unsplash-selector@0.3.6":
8142
+ version "0.3.6"
8143
+ resolved "https://registry.yarnpkg.com/@tryghost/kg-unsplash-selector/-/kg-unsplash-selector-0.3.6.tgz#7b573600efce885e3a7ba8382dfff16c409e3c49"
8144
+ integrity sha512-tjCa9ynS2ODtCCFsOetKMS5Ov3BqvtgVJHdBqLlk7cJBt1ZBJDI7IDuKcqkwOhDm15SHQHbG1lNPHGG2wRCGGg==
8145
+
8146
+ "@tryghost/kg-utils@1.0.31":
8147
+ version "1.0.31"
8148
+ resolved "https://registry.yarnpkg.com/@tryghost/kg-utils/-/kg-utils-1.0.31.tgz#00e29cec01a16c53bc12e9d0d7f2153e58fde5d4"
8149
+ integrity sha512-TJRTu3XCxfqclueDjs8cVi3p9o5lYrIIkA6abNl4EAHvT9FtR6nY0FQgEgDwtDDjpaWq+VKcDyo48abvAG456g==
8150
+ dependencies:
8151
+ semver "^7.7.0"
8125
8152
 
8126
8153
  "@tryghost/kg-utils@1.0.32":
8127
8154
  version "1.0.32"
@@ -8130,10 +8157,10 @@
8130
8157
  dependencies:
8131
8158
  semver "^7.7.0"
8132
8159
 
8133
- "@tryghost/koenig-lexical@1.7.1":
8134
- version "1.7.1"
8135
- resolved "https://registry.yarnpkg.com/@tryghost/koenig-lexical/-/koenig-lexical-1.7.1.tgz#61c54ac01f86e6820b95fab4a16a7ac9f4678038"
8136
- integrity sha512-bYcfQ7lsJHaeM8Uy+49g7x55T4SiAZkbdmxDgY77mxK8oF7la0xx3I4IbcdFWQpKSykYxZfQ3aMZ1mfDINQPnw==
8160
+ "@tryghost/koenig-lexical@1.7.2":
8161
+ version "1.7.2"
8162
+ resolved "https://registry.yarnpkg.com/@tryghost/koenig-lexical/-/koenig-lexical-1.7.2.tgz#03de94b698b1071f17a1a8a053fe060ed9c4f785"
8163
+ integrity sha512-GqE03JX6277X9slx1WoXblw1Sd2mGW1zWhvKpifGbs/ePR4oaBtpaq3OpNmMvWqfAaE3hXnHxYPy8J9gs/HnvQ==
8137
8164
 
8138
8165
  "@tryghost/limit-service@1.2.18":
8139
8166
  version "1.2.18"
@@ -19790,7 +19817,7 @@ html-escaper@^2.0.0:
19790
19817
  resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
19791
19818
  integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
19792
19819
 
19793
- html-minifier@4.0.0:
19820
+ html-minifier@4.0.0, html-minifier@^4.0.0:
19794
19821
  version "4.0.0"
19795
19822
  resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-4.0.0.tgz#cca9aad8bce1175e02e17a8c33e46d8988889f56"
19796
19823
  integrity sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==