ghost 6.0.7 → 6.0.8

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 (30) hide show
  1. package/components/tryghost-i18n-6.0.8.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-B-ckGCDl.mjs → index-1EXYCtPI.mjs} +13252 -12300
  4. package/core/built/admin/assets/admin-x-activitypub/{index-C81KQoIh.mjs → index-If44c6h0.mjs} +2 -2
  5. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-BfL5FINN.mjs → CodeEditorView-CzXlGImM.mjs} +2 -2
  6. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
  7. package/core/built/admin/assets/admin-x-settings/{index-Bmm3Xeuw.mjs → index-BgCSf8S1.mjs} +4 -5
  8. package/core/built/admin/assets/admin-x-settings/{index-DiNZ3HQD.mjs → index-D2pIApbM.mjs} +18 -11
  9. package/core/built/admin/assets/admin-x-settings/{index-C_ZS-INP.mjs → index-RKA3H0Lh.mjs} +2 -2
  10. package/core/built/admin/assets/admin-x-settings/{modals-BEiWgHsk.mjs → modals-D0f6kxWg.mjs} +8799 -8807
  11. package/core/built/admin/assets/{chunk.524.9f989b3664418d6271a7.js → chunk.524.099dcd3975a0e60c5579.js} +7 -7
  12. package/core/built/admin/assets/{chunk.582.2421014e45b977b43b68.js → chunk.582.6830378a89a17aeedd0b.js} +10 -10
  13. package/core/built/admin/assets/{ghost-182ed60de3f37fff8a40cdd65d3bd2ef.js → ghost-138bb4718f8b9d666bdd7a2b45330d58.js} +10 -11
  14. package/core/built/admin/assets/posts/posts.js +26648 -26478
  15. package/core/built/admin/assets/stats/stats.js +24477 -24344
  16. package/core/built/admin/index.html +3 -3
  17. package/core/frontend/helpers/ghost_head.js +9 -9
  18. package/core/frontend/public/ghost-stats.min.js +3 -3
  19. package/core/frontend/public/member-attribution.min.js +1 -1
  20. package/core/frontend/src/ghost-stats/ghost-stats.js +18 -4
  21. package/core/frontend/src/member-attribution/member-attribution.js +28 -18
  22. package/core/frontend/src/utils/url-attribution.js +53 -40
  23. package/core/server/api/endpoints/utils/serializers/input/posts.js +7 -5
  24. package/core/server/services/members/emails/signin.js +3 -2
  25. package/core/server/services/tinybird/TinybirdService.js +6 -1
  26. package/core/server/services/update-check/UpdateCheckService.js +1 -1
  27. package/package.json +5 -5
  28. package/tsconfig.tsbuildinfo +1 -1
  29. package/yarn.lock +331 -49
  30. package/components/tryghost-i18n-6.0.7.tgz +0 -0
@@ -3,63 +3,76 @@
3
3
  */
4
4
 
5
5
  /**
6
- * Parses URL parameters to extract attribution information
7
- *
8
- * @param {string} url - The URL to parse
9
- * @returns {Object} Parsed attribution data
6
+ * @typedef {Object} AttributionData
7
+ * @property {string|null} source - Primary attribution source (ref || source || utm_source)
8
+ * @property {string|null} medium - UTM medium parameter
9
+ * @property {string|null} url - Browser's document.referrer
10
+ * @property {string|null} utmSource - UTM source parameter
11
+ * @property {string|null} utmMedium - UTM medium parameter
12
+ * @property {string|null} utmTerm - UTM term/keyword parameter
13
+ * @property {string|null} utmCampaign - UTM campaign parameter
14
+ * @property {string|null} utmContent - UTM content/variant parameter
10
15
  */
11
- export function parseReferrer(url) {
12
- // Extract current URL parameters
13
- const currentUrl = new URL(url || window.location.href);
14
-
15
- // Parse source parameters
16
- const refParam = currentUrl.searchParams.get('ref');
17
- const sourceParam = currentUrl.searchParams.get('source');
18
- const utmSourceParam = currentUrl.searchParams.get('utm_source');
19
- const utmMediumParam = currentUrl.searchParams.get('utm_medium');
16
+
17
+ /**
18
+ * Extracts attribution parameters from URL search params
19
+ * @private
20
+ * @param {URLSearchParams} searchParams - The search params to parse
21
+ * @returns {AttributionData} Parsed attribution data with all UTM parameters
22
+ */
23
+ function extractParams(searchParams) {
24
+ const refParam = searchParams.get('ref');
25
+ const sourceParam = searchParams.get('source');
26
+ const utmSourceParam = searchParams.get('utm_source');
27
+ const utmMediumParam = searchParams.get('utm_medium');
28
+ const utmTermParam = searchParams.get('utm_term');
29
+ const utmCampaignParam = searchParams.get('utm_campaign');
30
+ const utmContentParam = searchParams.get('utm_content');
20
31
 
21
32
  // Determine primary source
22
33
  const referrerSource = refParam || sourceParam || utmSourceParam || null;
23
34
 
24
- // Check portal hash if needed
25
- if (!referrerSource && currentUrl.hash && currentUrl.hash.includes('#/portal')) {
26
- return parsePortalHash(currentUrl);
27
- }
28
-
29
35
  return {
30
36
  source: referrerSource,
31
37
  medium: utmMediumParam || null,
32
- url: window.document.referrer || null
38
+ url: window.document.referrer || null,
39
+ utmSource: utmSourceParam || null,
40
+ utmMedium: utmMediumParam || null,
41
+ utmTerm: utmTermParam || null,
42
+ utmCampaign: utmCampaignParam || null,
43
+ utmContent: utmContentParam || null
33
44
  };
34
45
  }
35
46
 
36
47
  /**
37
- * Parses attribution data from portal hash URLs
48
+ * Parses URL parameters to extract complete referrer/attribution data
38
49
  *
39
- * @param {URL} url - URL object with a portal hash
40
- * @returns {Object} Parsed attribution data
50
+ * @param {string} url - The URL to parse (defaults to current URL)
51
+ * @returns {AttributionData} Complete attribution data including all UTM parameters
41
52
  */
42
- export function parsePortalHash(url) {
43
- const hashUrl = new URL(url.href.replace('/#/portal', ''));
44
- const refParam = hashUrl.searchParams.get('ref');
45
- const sourceParam = hashUrl.searchParams.get('source');
46
- const utmSourceParam = hashUrl.searchParams.get('utm_source');
47
- const utmMediumParam = hashUrl.searchParams.get('utm_medium');
53
+ export function parseReferrerData(url) {
54
+ // Extract current URL parameters
55
+ const currentUrl = new URL(url || window.location.href);
56
+ let searchParams = currentUrl.searchParams;
48
57
 
49
- return {
50
- source: refParam || sourceParam || utmSourceParam || null,
51
- medium: utmMediumParam || null,
52
- url: window.document.referrer || null
53
- };
58
+ // Handle portal hash URLs - extract params from hash instead
59
+ if (currentUrl.hash && currentUrl.hash.includes('#/portal')) {
60
+ const hashUrl = new URL(currentUrl.href.replace('/#/portal', ''));
61
+ searchParams = hashUrl.searchParams;
62
+ }
63
+
64
+ return extractParams(searchParams);
54
65
  }
55
66
 
56
67
  /**
57
- * Gets the final referrer value based on parsed data
58
- *
59
- * @param {Object} referrerData - Parsed referrer data
60
- * @returns {string|null} Final referrer value or null
68
+ * Selects the primary referrer value from parsed attribution data
69
+ * Prioritizes: source → medium → url
70
+ * Filters out same-domain referrers
71
+ * @private
72
+ * @param {AttributionData} referrerData - Parsed referrer data
73
+ * @returns {string|null} Primary referrer value or null
61
74
  */
62
- export function getFinalReferrer(referrerData) {
75
+ function selectPrimaryReferrer(referrerData) {
63
76
  const { source, medium, url } = referrerData;
64
77
  const finalReferrer = source || medium || url || null;
65
78
 
@@ -87,6 +100,6 @@ export function getFinalReferrer(referrerData) {
87
100
  * @returns {string|null} Final referrer value
88
101
  */
89
102
  export function getReferrer(url) {
90
- const referrerData = parseReferrer(url);
91
- return getFinalReferrer(referrerData);
103
+ const referrerData = parseReferrerData(url);
104
+ return selectPrimaryReferrer(referrerData);
92
105
  }
@@ -19,10 +19,10 @@ const messages = {
19
19
 
20
20
  /**
21
21
  * Selects all allowed columns for the given frame.
22
- *
22
+ *
23
23
  * NOTE: This doesn't stop them from being FETCHED, just returned in the response. This causes
24
24
  * the output serializer to remove them from the data object before returning.
25
- *
25
+ *
26
26
  * NOTE: This is only intended for the Content API. We need these fields within Admin API responses.
27
27
  *
28
28
  * @param {Object} frame - The frame object.
@@ -37,10 +37,10 @@ function removeSourceFormats(frame) {
37
37
 
38
38
  /**
39
39
  * Selects all allowed columns for the given frame.
40
- *
40
+ *
41
41
  * This removes the lexical and mobiledoc columns from the query. This is a performance improvement as we never intend
42
42
  * to expose those columns in the content API and they are very large datasets to be passing around and de/serializing.
43
- *
43
+ *
44
44
  * NOTE: This is only intended for the Content API. We need these fields within Admin API responses.
45
45
  *
46
46
  * @param {Object} frame - The frame object.
@@ -97,7 +97,9 @@ function setDefaultOrder(frame) {
97
97
  }
98
98
 
99
99
  if (!frame.options.order && !frame.options.autoOrder) {
100
- frame.options.order = 'published_at desc';
100
+ // use id as fallback to ensure consistent ordering across pages when posts
101
+ // have the same published_at timestamp
102
+ frame.options.order = 'published_at desc, id desc';
101
103
  }
102
104
  }
103
105
 
@@ -112,8 +112,9 @@ module.exports = ({t, siteTitle, email, url, otc, accentColor = '#15212A', siteD
112
112
 
113
113
  <!-- START CENTERED CONTAINER -->
114
114
  <span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">${otc ? t('Welcome back to {siteTitle}! Your verification code is {otc}.', {siteTitle, otc, interpolation: {escapeValue: false}}) : t('Welcome back to {siteTitle}!', {siteTitle, interpolation: {escapeValue: false}})}</span>
115
+ <!-- SPACING FOR PREVIEW TEXT -->
115
116
  <div style="display:none; max-height:0; overflow:hidden; mso-hide: all;" aria-hidden="true" role="presentation">
116
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
117
+ ${'&zwnj;&nbsp;'.repeat(75)}
117
118
  </div>
118
119
  <table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #ffffff; border-radius: 8px;">
119
120
 
@@ -151,7 +152,7 @@ module.exports = ({t, siteTitle, email, url, otc, accentColor = '#15212A', siteD
151
152
  </tr>
152
153
  </tbody>
153
154
  </table>
154
- <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; color: #3A464C; font-weight: normal; margin: 0; line-height: 24px;">${t('You can also copy & paste this URL into your browser:')}:</p>`
155
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; color: #3A464C; font-weight: normal; margin: 0; line-height: 24px;">${t('You can also copy & paste this URL into your browser:')}</p>`
155
156
  :
156
157
  `<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 24px; margin-bottom: 32px;">${t('Welcome back! Use this link to securely sign in to your {siteTitle} account:', {siteTitle, interpolation: {escapeValue: false}})}</p>
157
158
  <table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
@@ -53,7 +53,12 @@ const TINYBIRD_PIPES = [
53
53
  'api_top_locations',
54
54
  'api_top_os',
55
55
  'api_top_pages',
56
- 'api_top_sources'
56
+ 'api_top_sources',
57
+ 'api_top_utm_sources',
58
+ 'api_top_utm_mediums',
59
+ 'api_top_utm_campaigns',
60
+ 'api_top_utm_contents',
61
+ 'api_top_utm_terms'
57
62
  ];
58
63
 
59
64
  /**
@@ -189,7 +189,7 @@ class UpdateCheckService {
189
189
 
190
190
  try {
191
191
  const response = await this.request(checkEndpoint, reqObj);
192
- return response.body;
192
+ return JSON.parse(response.body);
193
193
  } catch (err) {
194
194
  // CASE: no notifications available, ignore
195
195
  if (err.statusCode === 404) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghost",
3
- "version": "6.0.7",
3
+ "version": "6.0.8",
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.97",
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-6.0.7.tgz",
89
+ "@tryghost/i18n": "file:components/tryghost-i18n-6.0.8.tgz",
90
90
  "@tryghost/image-transform": "1.4.6",
91
91
  "@tryghost/job-manager": "1.0.3",
92
92
  "@tryghost/kg-card-factory": "5.1.2",
@@ -166,7 +166,7 @@
166
166
  "heic-convert": "2.1.0",
167
167
  "html-to-text": "5.1.1",
168
168
  "html5parser": "2.0.2",
169
- "human-number": "2.0.5",
169
+ "human-number": "2.0.6",
170
170
  "iconv-lite": "0.6.3",
171
171
  "image-size": "1.2.1",
172
172
  "intl": "1.2.5",
@@ -232,7 +232,7 @@
232
232
  "@types/bookshelf": "1.2.9",
233
233
  "@types/common-tags": "1.8.4",
234
234
  "@types/jsonwebtoken": "9.0.10",
235
- "@types/node": "22.18.1",
235
+ "@types/node": "22.18.3",
236
236
  "@types/node-jose": "1.1.13",
237
237
  "@types/nodemailer": "6.4.19",
238
238
  "@types/sinon": "17.0.4",
@@ -273,7 +273,7 @@
273
273
  "jackspeak": "2.3.6",
274
274
  "moment": "2.24.0",
275
275
  "moment-timezone": "0.5.45",
276
- "@tryghost/i18n": "file:components/tryghost-i18n-6.0.7.tgz"
276
+ "@tryghost/i18n": "file:components/tryghost-i18n-6.0.8.tgz"
277
277
  },
278
278
  "nx": {
279
279
  "targets": {