ghost 6.1.0 → 6.2.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.
Files changed (32) hide show
  1. package/components/{tryghost-i18n-6.1.0.tgz → tryghost-i18n-6.2.0.tgz} +0 -0
  2. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-Bu9qXr9c.mjs → CodeEditorView-UxqLGRTu.mjs} +3 -3
  3. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
  4. package/core/built/admin/assets/admin-x-settings/{index-o4Q9MNrB.mjs → index-8WxO2QXI.mjs} +3017 -2827
  5. package/core/built/admin/assets/admin-x-settings/{index-qEdfz2hd.mjs → index-B5r0jdJS.mjs} +5 -5
  6. package/core/built/admin/assets/admin-x-settings/{index-BEpRBH9g.mjs → index-Co907MFn.mjs} +2 -2
  7. package/core/built/admin/assets/admin-x-settings/{index-BgCSf8S1.mjs → index-DD3HKlR3.mjs} +306 -315
  8. package/core/built/admin/assets/admin-x-settings/{modals-BtQORnS4.mjs → modals-B7j9sxR4.mjs} +8799 -8807
  9. package/core/built/admin/assets/{chunk.397.e5d027e53a68dff31d76.js → chunk.397.d5e25bb9baf088f52499.js} +2 -2
  10. package/core/built/admin/assets/{chunk.524.2aa0847042f20c9a2a00.js → chunk.524.70595796c7b8c6003a2d.js} +5 -5
  11. package/core/built/admin/assets/{chunk.582.9182c19afab95991771e.js → chunk.582.d9b970b71da671ac1b7b.js} +8 -8
  12. package/core/built/admin/assets/{ghost-9c47d152972b304cab0fb982dc3fccc1.js → ghost-2066304fd0b166e1c16d397dd73ef7b2.js} +4 -4
  13. package/core/built/admin/assets/{ghost-791574a9e2efe65c88412947d2e80170.css → ghost-49475952d56ffe89bd47ab9d9c64ada8.css} +1 -1
  14. package/core/built/admin/assets/{ghost-dark-1a7d101d525c0fdcf406ac0abd98540f.css → ghost-dark-27877727751b91f03261d449d74e33b9.css} +1 -1
  15. package/core/built/admin/assets/posts/posts.js +71 -62
  16. package/core/built/admin/assets/stats/stats.js +2 -2
  17. package/core/built/admin/index.html +5 -5
  18. package/core/server/data/migrations/utils/schema.js +11 -6
  19. package/core/server/data/migrations/versions/6.2/2025-09-30-14-28-09-add-utm-fields.js +24 -0
  20. package/core/server/data/schema/commands.js +21 -6
  21. package/core/server/data/schema/schema.js +24 -0
  22. package/core/server/services/email-service/EmailRenderer.js +1 -1
  23. package/core/server/services/lib/MailgunClient.js +4 -3
  24. package/core/server/services/lib/magic-link/MagicLink.js +9 -9
  25. package/core/server/services/mail/GhostMailer.js +4 -1
  26. package/core/server/services/members/MembersConfigProvider.js +0 -15
  27. package/core/server/services/members/SingleUseTokenProvider.js +8 -8
  28. package/core/shared/config/defaults.json +1 -1
  29. package/package.json +6 -6
  30. package/tsconfig.tsbuildinfo +1 -1
  31. package/yarn.lock +195 -197
  32. /package/core/built/admin/assets/{chunk.397.e5d027e53a68dff31d76.js.LICENSE.txt → chunk.397.d5e25bb9baf088f52499.js.LICENSE.txt} +0 -0
@@ -39837,8 +39837,8 @@ const RY = ({ children: e, className: t, ...a }) => /* @__PURE__ */ A.jsx(
39837
39837
  t === "same" && "text-gray-700 bg-muted"
39838
39838
  );
39839
39839
  return /* @__PURE__ */ A.jsxs("div", { className: "relative flex flex-col items-start gap-2 lg:flex-row lg:gap-3", children: [
39840
- /* @__PURE__ */ A.jsx("div", { className: "text-[2.2rem] font-semibold leading-none tracking-tighter", children: e }),
39841
- t && t !== "hidden" && /* @__PURE__ */ A.jsx(A.Fragment, { children: /* @__PURE__ */ A.jsxs("div", { className: n, children: [
39840
+ /* @__PURE__ */ A.jsx("div", { className: "text-[2.2rem] font-semibold leading-none tracking-tighter", "data-testid": "kpi-card-header-value", children: e }),
39841
+ t && t !== "hidden" && /* @__PURE__ */ A.jsx(A.Fragment, { children: /* @__PURE__ */ A.jsxs("div", { className: n, "data-testid": "kpi-card-header-diff", children: [
39842
39842
  /* @__PURE__ */ A.jsx("span", { className: "font-medium leading-none", children: a }),
39843
39843
  t === "up" && /* @__PURE__ */ A.jsx(ni, { className: "!size-[12px]", size: 14, strokeWidth: 2 }),
39844
39844
  t === "down" && /* @__PURE__ */ A.jsx(oi, { className: "!size-[12px]", size: 14, strokeWidth: 2 }),
@@ -119676,12 +119676,12 @@ const p_1 = "https://www.google.com/s2/favicons?domain=ghost.org&sz=64", dp1 = (
119676
119676
  ] });
119677
119677
  }, eM2 = ({ children: e, className: t, ...a }) => /* @__PURE__ */ A.jsx("section", { className: d1("flex gap-8 flex-col p-4 lg:p-8 size-full grow", t), ...a, children: e }), tM2 = ({ currentTab: e }) => /* @__PURE__ */ A.jsxs(W6, { variant: "inline-nav", children: [
119678
119678
  /* @__PURE__ */ A.jsx(W6.Title, { children: "Tags" }),
119679
- /* @__PURE__ */ A.jsx(W6.Nav, { children: /* @__PURE__ */ A.jsxs(ne1, { defaultValue: e, children: [
119679
+ /* @__PURE__ */ A.jsx(W6.Nav, { children: /* @__PURE__ */ A.jsxs(ne1, { "data-testid": "tags-header-tabs", defaultValue: e, children: [
119680
119680
  /* @__PURE__ */ A.jsx(Dr, { value: "public", asChild: !0, children: /* @__PURE__ */ A.jsx(GH, { to: "/tags", children: "Public tags" }) }),
119681
119681
  /* @__PURE__ */ A.jsx(Dr, { value: "internal", asChild: !0, children: /* @__PURE__ */ A.jsx(GH, { to: "/tags?type=internal", children: "Internal tags" }) })
119682
119682
  ] }) }),
119683
119683
  /* @__PURE__ */ A.jsx(W6.Actions, { children: /* @__PURE__ */ A.jsx(J0, { asChild: !0, children: /* @__PURE__ */ A.jsx("a", { className: "font-bold", href: "#/tags/new", children: "New tag" }) }) })
119684
- ] }), aM2 = ({ children: e }) => /* @__PURE__ */ A.jsx(k_1, { children: /* @__PURE__ */ A.jsx("div", { className: "grid w-full grow", children: /* @__PURE__ */ A.jsx("div", { className: "flex h-full flex-col", children: e }) }) });
119684
+ ] }), aM2 = ({ children: e }) => /* @__PURE__ */ A.jsx(k_1, { children: /* @__PURE__ */ A.jsx("div", { className: "grid w-full grow", children: /* @__PURE__ */ A.jsx("div", { className: "flex h-full flex-col", "data-testid": "tags-page", children: e }) }) });
119685
119685
  function A_1(e) {
119686
119686
  const a = e instanceof HTMLElement && window.getComputedStyle(e).overflowY, o = a !== "visible" && a !== "hidden";
119687
119687
  if (e) {
@@ -120273,14 +120273,15 @@ function mM2({
120273
120273
  spaceAfter: d
120274
120274
  };
120275
120275
  }
120276
- const gp1 = ({ height: e }) => /* @__PURE__ */ A.jsx("tr", { className: "flex lg:table-row", children: /* @__PURE__ */ A.jsx("td", { className: "flex lg:table-cell", style: { height: e } }) }), MM2 = Q(function(t, a) {
120276
+ const gp1 = ({ height: e }) => /* @__PURE__ */ A.jsx("tr", { "aria-hidden": "true", className: "flex lg:table-row", children: /* @__PURE__ */ A.jsx("td", { className: "flex lg:table-cell", style: { height: e } }) }), MM2 = Q(function(t, a) {
120277
120277
  return /* @__PURE__ */ A.jsx(
120278
120278
  bl,
120279
120279
  {
120280
120280
  ref: a,
120281
120281
  ...t,
120282
+ "aria-hidden": "true",
120282
120283
  className: "relative flex flex-col lg:table-row",
120283
- children: /* @__PURE__ */ A.jsx(A5, { className: "relative z-10 h-24 animate-pulse", children: /* @__PURE__ */ A.jsx("div", { className: "h-full rounded-md bg-muted" }) })
120284
+ children: /* @__PURE__ */ A.jsx(A5, { className: "relative z-10 h-24 animate-pulse", children: /* @__PURE__ */ A.jsx("div", { className: "h-full rounded-md bg-muted", "data-testid": "loading-placeholder" }) })
120284
120285
  }
120285
120286
  );
120286
120287
  });
@@ -120299,62 +120300,70 @@ function bM2({
120299
120300
  fetchNextPage: n,
120300
120301
  parentRef: r
120301
120302
  });
120302
- return /* @__PURE__ */ A.jsx("div", { ref: r, children: /* @__PURE__ */ A.jsxs(pe1, { className: "flex table-fixed flex-col lg:table", children: [
120303
- /* @__PURE__ */ A.jsx(de1, { className: "hidden lg:!visible lg:!table-header-group", children: /* @__PURE__ */ A.jsxs(bl, { children: [
120304
- /* @__PURE__ */ A.jsx(R6, { className: "w-auto px-4", children: "Tag" }),
120305
- /* @__PURE__ */ A.jsx(R6, { className: "w-1/5 px-4", children: "Slug" }),
120306
- /* @__PURE__ */ A.jsx(R6, { className: "w-1/5 px-4", children: "No. of posts" }),
120307
- /* @__PURE__ */ A.jsx(R6, { className: "w-20 px-4" })
120308
- ] }) }),
120309
- /* @__PURE__ */ A.jsxs(fe1, { className: "flex flex-col lg:table-row-group", children: [
120310
- /* @__PURE__ */ A.jsx(gp1, { height: c }),
120311
- i.map(({ key: s, virtualItem: p, item: d, props: f }) => {
120312
- var h, m, M;
120313
- return p.index > e.length - 1 ? /* @__PURE__ */ A.jsx(MM2, { ...f }, s) : /* @__PURE__ */ A.jsxs(
120314
- bl,
120315
- {
120316
- ...f,
120317
- className: "relative grid w-full grid-cols-[1fr_5rem] items-center gap-x-4 p-2 md:grid-cols-[1fr_auto_5rem] lg:table-row lg:p-0",
120318
- children: [
120319
- /* @__PURE__ */ A.jsxs(A5, { className: "static col-start-1 col-end-1 row-start-1 row-end-1 flex min-w-0 flex-col p-0 lg:table-cell lg:w-1/2 lg:p-4 xl:w-3/5", children: [
120320
- /* @__PURE__ */ A.jsx(
120321
- "a",
120322
- {
120323
- className: "block truncate pb-1 text-lg font-medium before:absolute before:inset-0 before:z-10",
120324
- href: `#/tags/${d.slug}`,
120325
- children: d.name
120326
- }
120327
- ),
120328
- /* @__PURE__ */ A.jsx("span", { className: "block truncate text-muted-foreground", children: d.description })
120329
- ] }),
120330
- /* @__PURE__ */ A.jsx(A5, { className: "col-start-1 col-end-1 row-start-2 row-end-2 flex p-0 lg:table-cell lg:p-4", children: /* @__PURE__ */ A.jsx("span", { className: "block truncate", children: d.slug }) }),
120331
- /* @__PURE__ */ A.jsx(A5, { className: "col-start-1 col-end-1 row-start-3 row-end-3 flex p-0 md:col-start-2 md:col-end-2 md:row-start-1 md:row-end-3 lg:table-cell lg:p-4", children: (h = d.count) != null && h.posts ? /* @__PURE__ */ A.jsx(
120332
- "a",
120333
- {
120334
- className: "relative z-10 -m-4 inline-block p-4 hover:underline",
120335
- href: `#/posts?tag=${d.slug}`,
120336
- children: `${R2((m = d.count) == null ? void 0 : m.posts)} ${((M = d.count) == null ? void 0 : M.posts) === 1 ? "post" : "posts"}`
120337
- }
120338
- ) : /* @__PURE__ */ A.jsx("span", { className: "text-muted-foreground", children: "0 posts" }) }),
120339
- /* @__PURE__ */ A.jsx(A5, { className: "col-start-2 col-end-2 row-start-1 row-end-3 p-0 md:col-start-3 md:col-end-3 lg:table-cell lg:p-4", children: /* @__PURE__ */ A.jsx(
120340
- J0,
120341
- {
120342
- "aria-hidden": "true",
120343
- className: "w-12",
120344
- size: "icon",
120345
- tabIndex: -1,
120346
- variant: "outline",
120347
- children: /* @__PURE__ */ A.jsx(Dp, {})
120348
- }
120349
- ) })
120350
- ]
120351
- },
120352
- s
120353
- );
120354
- }),
120355
- /* @__PURE__ */ A.jsx(gp1, { height: l })
120356
- ] })
120357
- ] }) });
120303
+ return /* @__PURE__ */ A.jsx("div", { ref: r, children: /* @__PURE__ */ A.jsxs(
120304
+ pe1,
120305
+ {
120306
+ className: "flex table-fixed flex-col lg:table",
120307
+ "data-testid": "tags-list",
120308
+ children: [
120309
+ /* @__PURE__ */ A.jsx(de1, { className: "hidden lg:!visible lg:!table-header-group", children: /* @__PURE__ */ A.jsxs(bl, { children: [
120310
+ /* @__PURE__ */ A.jsx(R6, { className: "w-auto px-4", children: "Tag" }),
120311
+ /* @__PURE__ */ A.jsx(R6, { className: "w-1/5 px-4", children: "Slug" }),
120312
+ /* @__PURE__ */ A.jsx(R6, { className: "w-1/5 px-4", children: "No. of posts" }),
120313
+ /* @__PURE__ */ A.jsx(R6, { className: "w-20 px-4" })
120314
+ ] }) }),
120315
+ /* @__PURE__ */ A.jsxs(fe1, { className: "flex flex-col lg:table-row-group", children: [
120316
+ /* @__PURE__ */ A.jsx(gp1, { height: c }),
120317
+ i.map(({ key: s, virtualItem: p, item: d, props: f }) => {
120318
+ var h, m, M;
120319
+ return p.index > e.length - 1 ? /* @__PURE__ */ A.jsx(MM2, { ...f }, s) : /* @__PURE__ */ A.jsxs(
120320
+ bl,
120321
+ {
120322
+ ...f,
120323
+ className: "relative grid w-full grid-cols-[1fr_5rem] items-center gap-x-4 p-2 md:grid-cols-[1fr_auto_5rem] lg:table-row lg:p-0",
120324
+ "data-testid": "tag-list-row",
120325
+ children: [
120326
+ /* @__PURE__ */ A.jsxs(A5, { className: "static col-start-1 col-end-1 row-start-1 row-end-1 flex min-w-0 flex-col p-0 lg:table-cell lg:w-1/2 lg:p-4 xl:w-3/5", children: [
120327
+ /* @__PURE__ */ A.jsx(
120328
+ "a",
120329
+ {
120330
+ className: "block truncate pb-1 text-lg font-medium before:absolute before:inset-0 before:z-10",
120331
+ href: `#/tags/${d.slug}`,
120332
+ children: d.name
120333
+ }
120334
+ ),
120335
+ /* @__PURE__ */ A.jsx("span", { className: "block truncate text-muted-foreground", children: d.description })
120336
+ ] }),
120337
+ /* @__PURE__ */ A.jsx(A5, { className: "col-start-1 col-end-1 row-start-2 row-end-2 flex p-0 lg:table-cell lg:p-4", children: /* @__PURE__ */ A.jsx("span", { className: "block truncate", children: d.slug }) }),
120338
+ /* @__PURE__ */ A.jsx(A5, { className: "col-start-1 col-end-1 row-start-3 row-end-3 flex p-0 md:col-start-2 md:col-end-2 md:row-start-1 md:row-end-3 lg:table-cell lg:p-4", children: (h = d.count) != null && h.posts ? /* @__PURE__ */ A.jsx(
120339
+ "a",
120340
+ {
120341
+ className: "relative z-10 -m-4 inline-block p-4 hover:underline",
120342
+ href: `#/posts?tag=${d.slug}`,
120343
+ children: `${R2((m = d.count) == null ? void 0 : m.posts)} ${((M = d.count) == null ? void 0 : M.posts) === 1 ? "post" : "posts"}`
120344
+ }
120345
+ ) : /* @__PURE__ */ A.jsx("span", { className: "text-muted-foreground", children: "0 posts" }) }),
120346
+ /* @__PURE__ */ A.jsx(A5, { className: "col-start-2 col-end-2 row-start-1 row-end-3 p-0 md:col-start-3 md:col-end-3 lg:table-cell lg:p-4", children: /* @__PURE__ */ A.jsx(
120347
+ J0,
120348
+ {
120349
+ "aria-hidden": "true",
120350
+ className: "w-12",
120351
+ size: "icon",
120352
+ tabIndex: -1,
120353
+ variant: "outline",
120354
+ children: /* @__PURE__ */ A.jsx(Dp, {})
120355
+ }
120356
+ ) })
120357
+ ]
120358
+ },
120359
+ s
120360
+ );
120361
+ }),
120362
+ /* @__PURE__ */ A.jsx(gp1, { height: l })
120363
+ ] })
120364
+ ]
120365
+ }
120366
+ ) });
120358
120367
  }
120359
120368
  const zM2 = "TagsResponseType", vM2 = Hf2({
120360
120369
  dataType: zM2,
@@ -96030,8 +96030,8 @@ const EV1 = ({ children: e, className: t, ...a }) => /* @__PURE__ */ A.jsx(
96030
96030
  t === "same" && "text-gray-700 bg-muted"
96031
96031
  );
96032
96032
  return /* @__PURE__ */ A.jsxs("div", { className: "relative flex flex-col items-start gap-2 lg:flex-row lg:gap-3", children: [
96033
- /* @__PURE__ */ A.jsx("div", { className: "text-[2.2rem] font-semibold leading-none tracking-tighter", children: e }),
96034
- t && t !== "hidden" && /* @__PURE__ */ A.jsx(A.Fragment, { children: /* @__PURE__ */ A.jsxs("div", { className: n, children: [
96033
+ /* @__PURE__ */ A.jsx("div", { className: "text-[2.2rem] font-semibold leading-none tracking-tighter", "data-testid": "kpi-card-header-value", children: e }),
96034
+ t && t !== "hidden" && /* @__PURE__ */ A.jsx(A.Fragment, { children: /* @__PURE__ */ A.jsxs("div", { className: n, "data-testid": "kpi-card-header-diff", children: [
96035
96035
  /* @__PURE__ */ A.jsx("span", { className: "font-medium leading-none", children: a }),
96036
96036
  t === "up" && /* @__PURE__ */ A.jsx(Fc, { className: "!size-[12px]", size: 14, strokeWidth: 2 }),
96037
96037
  t === "down" && /* @__PURE__ */ A.jsx(jc, { className: "!size-[12px]", size: 14, strokeWidth: 2 }),
@@ -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%226.1%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%229c120d864e%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%22d668621a75%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%2285275dce41%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%2231e7d6e61e%22%2C%22adminXActivitypubRemoteConfigUrl%22%3A%22%2F.ghost%2Factivitypub%2Fstable%2Fclient-config%22%7D" />
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%226.2%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%2213355f26c2%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%22d668621a75%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%22cf5b4df358%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%22f5dea8dc06%22%2C%22adminXActivitypubRemoteConfigUrl%22%3A%22%2F.ghost%2Factivitypub%2Fstable%2Fclient-config%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" />
@@ -28,7 +28,7 @@
28
28
  </style>
29
29
 
30
30
  <link integrity="" rel="stylesheet" href="assets/vendor-0ede59da8efb5e28fa929557f7ff7154.css">
31
- <link integrity="" rel="stylesheet" href="assets/ghost-791574a9e2efe65c88412947d2e80170.css" title="light">
31
+ <link integrity="" rel="stylesheet" href="assets/ghost-49475952d56ffe89bd47ab9d9c64ada8.css" title="light">
32
32
 
33
33
 
34
34
  </head>
@@ -48,8 +48,8 @@
48
48
  <div id="ember-basic-dropdown-wormhole"></div>
49
49
 
50
50
  <script src="assets/vendor-aed0068cf9b67d042dd23a6343545b7b.js"></script>
51
- <script src="assets/chunk.397.e5d027e53a68dff31d76.js"></script>
52
- <script src="assets/chunk.524.2aa0847042f20c9a2a00.js"></script>
53
- <script src="assets/ghost-9c47d152972b304cab0fb982dc3fccc1.js"></script>
51
+ <script src="assets/chunk.397.d5e25bb9baf088f52499.js"></script>
52
+ <script src="assets/chunk.524.70595796c7b8c6003a2d.js"></script>
53
+ <script src="assets/ghost-2066304fd0b166e1c16d397dd73ef7b2.js"></script>
54
54
  </body>
55
55
  </html>
@@ -11,7 +11,7 @@ const {createNonTransactionalMigration, createTransactionalMigration} = require(
11
11
  *
12
12
  * @returns {Migration}
13
13
  */
14
- function createAddColumnMigration(table, column, columnDefinition) {
14
+ function createAddColumnMigration(table, column, columnDefinition, options = {}) {
15
15
  return createNonTransactionalMigration(
16
16
  // up
17
17
  commands.createColumnMigration({
@@ -20,7 +20,8 @@ function createAddColumnMigration(table, column, columnDefinition) {
20
20
  dbIsInCorrectState: hasColumn => hasColumn === true,
21
21
  operation: commands.addColumn,
22
22
  operationVerb: 'Adding',
23
- columnDefinition
23
+ columnDefinition,
24
+ options
24
25
  }),
25
26
  // down
26
27
  commands.createColumnMigration({
@@ -29,7 +30,8 @@ function createAddColumnMigration(table, column, columnDefinition) {
29
30
  dbIsInCorrectState: hasColumn => hasColumn === false,
30
31
  operation: commands.dropColumn,
31
32
  operationVerb: 'Removing',
32
- columnDefinition
33
+ columnDefinition,
34
+ options
33
35
  })
34
36
  );
35
37
  }
@@ -41,7 +43,7 @@ function createAddColumnMigration(table, column, columnDefinition) {
41
43
  *
42
44
  * @returns {Migration}
43
45
  */
44
- function createDropColumnMigration(table, column, columnDefinition) {
46
+ function createDropColumnMigration(table, column, columnDefinition, options = {}) {
45
47
  return createNonTransactionalMigration(
46
48
  // up
47
49
  commands.createColumnMigration({
@@ -49,7 +51,9 @@ function createDropColumnMigration(table, column, columnDefinition) {
49
51
  column,
50
52
  dbIsInCorrectState: hasColumn => hasColumn === false,
51
53
  operation: commands.dropColumn,
52
- operationVerb: 'Removing'
54
+ operationVerb: 'Removing',
55
+ columnDefinition,
56
+ options
53
57
  }),
54
58
  // down
55
59
  commands.createColumnMigration({
@@ -58,7 +62,8 @@ function createDropColumnMigration(table, column, columnDefinition) {
58
62
  dbIsInCorrectState: hasColumn => hasColumn === true,
59
63
  operation: commands.addColumn,
60
64
  operationVerb: 'Adding',
61
- columnDefinition
65
+ columnDefinition,
66
+ options
62
67
  })
63
68
  );
64
69
  }
@@ -0,0 +1,24 @@
1
+ const {combineNonTransactionalMigrations, createAddColumnMigration} = require('../../utils');
2
+
3
+ module.exports = combineNonTransactionalMigrations(
4
+ // members_created_events
5
+ createAddColumnMigration('members_created_events', 'utm_source', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
6
+ createAddColumnMigration('members_created_events', 'utm_medium', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
7
+ createAddColumnMigration('members_created_events', 'utm_campaign', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
8
+ createAddColumnMigration('members_created_events', 'utm_term', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
9
+ createAddColumnMigration('members_created_events', 'utm_content', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
10
+
11
+ // members_subscription_created_events
12
+ createAddColumnMigration('members_subscription_created_events', 'utm_source', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
13
+ createAddColumnMigration('members_subscription_created_events', 'utm_medium', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
14
+ createAddColumnMigration('members_subscription_created_events', 'utm_campaign', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
15
+ createAddColumnMigration('members_subscription_created_events', 'utm_term', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
16
+ createAddColumnMigration('members_subscription_created_events', 'utm_content', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
17
+
18
+ // donation_payment_events
19
+ createAddColumnMigration('donation_payment_events', 'utm_source', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
20
+ createAddColumnMigration('donation_payment_events', 'utm_medium', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
21
+ createAddColumnMigration('donation_payment_events', 'utm_campaign', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
22
+ createAddColumnMigration('donation_payment_events', 'utm_term', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
23
+ createAddColumnMigration('donation_payment_events', 'utm_content', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'})
24
+ );
@@ -96,8 +96,10 @@ function dropNullable(tableName, column, transaction = db.knex) {
96
96
  * @param {string} column
97
97
  * @param {import('knex').Knex.Transaction} [transaction]
98
98
  * @param {object} columnSpec
99
+ * @param {object} [options]
100
+ * @param {'inplace'|'copy'|'auto'} [options.algorithm] - MySQL only
99
101
  */
100
- async function addColumn(tableName, column, transaction = db.knex, columnSpec) {
102
+ async function addColumn(tableName, column, transaction = db.knex, columnSpec, options = {}) {
101
103
  const addColumnBuilder = transaction.schema.table(tableName, function (table) {
102
104
  addTableColumn(tableName, table, column, columnSpec);
103
105
  });
@@ -114,7 +116,12 @@ async function addColumn(tableName, column, transaction = db.knex, columnSpec) {
114
116
 
115
117
  if (DatabaseInfo.isMySQL(transaction)) {
116
118
  // Guard against an ending semicolon
117
- sql = sql.replace(/;\s*$/, '') + ', algorithm=copy';
119
+ sql = sql.replace(/;\s*$/, '');
120
+ if (options?.algorithm !== 'auto') {
121
+ // default to copy if not specified
122
+ const algorithm = options?.algorithm || 'copy';
123
+ sql += `, algorithm=${algorithm}`;
124
+ }
118
125
  }
119
126
 
120
127
  await transaction.raw(sql);
@@ -126,8 +133,10 @@ async function addColumn(tableName, column, transaction = db.knex, columnSpec) {
126
133
  * @param {string} column
127
134
  * @param {import('knex').Knex} [transaction]
128
135
  * @param {object} [columnSpec]
136
+ * @param {object} [options]
137
+ * @param {'inplace'|'copy'|'auto'} [options.algorithm] - MySQL only
129
138
  */
130
- async function dropColumn(tableName, column, transaction = db.knex, columnSpec = {}) {
139
+ async function dropColumn(tableName, column, transaction = db.knex, columnSpec = {}, options = {}) {
131
140
  if (Object.prototype.hasOwnProperty.call(columnSpec, 'references')) {
132
141
  const [toTable, toColumn] = columnSpec.references.split('.');
133
142
  await dropForeign({fromTable: tableName, fromColumn: column, toTable, toColumn, constraintName: columnSpec.constraintName, transaction});
@@ -149,7 +158,12 @@ async function dropColumn(tableName, column, transaction = db.knex, columnSpec =
149
158
 
150
159
  if (DatabaseInfo.isMySQL(transaction)) {
151
160
  // Guard against an ending semicolon
152
- sql = sql.replace(/;\s*$/, '') + ', algorithm=copy';
161
+ sql = sql.replace(/;\s*$/, '');
162
+ if (options?.algorithm !== 'auto') {
163
+ // default to copy if not specified
164
+ const algorithm = options?.algorithm || 'copy';
165
+ sql += `, algorithm=${algorithm}`;
166
+ }
153
167
  }
154
168
 
155
169
  await transaction.raw(sql);
@@ -561,7 +575,8 @@ function createColumnMigration(...migrations) {
561
575
  dbIsInCorrectState,
562
576
  operation,
563
577
  operationVerb,
564
- columnDefinition
578
+ columnDefinition,
579
+ options
565
580
  } = migration;
566
581
 
567
582
  const hasColumn = await conn.schema.hasColumn(table, column);
@@ -571,7 +586,7 @@ function createColumnMigration(...migrations) {
571
586
  logging.warn(`${operationVerb} ${table}.${column} column - skipping as table is correct`);
572
587
  } else {
573
588
  logging.info(`${operationVerb} ${table}.${column} column`);
574
- await operation(table, column, conn, columnDefinition);
589
+ await operation(table, column, conn, columnDefinition, options);
575
590
  }
576
591
  }
577
592
 
@@ -525,6 +525,7 @@ module.exports = {
525
525
  id: {type: 'string', maxlength: 24, nullable: false, primary: true},
526
526
  created_at: {type: 'dateTime', nullable: false},
527
527
  member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
528
+ // attribution values from ghost-history (member attribution tracking script)
528
529
  attribution_id: {type: 'string', maxlength: 24, nullable: true, index: true},
529
530
  attribution_type: {
530
531
  type: 'string', maxlength: 50, nullable: true, validations: {
@@ -532,9 +533,16 @@ module.exports = {
532
533
  }
533
534
  },
534
535
  attribution_url: {type: 'string', maxlength: 2000, nullable: true},
536
+ // referrer values from browser, processed by our referrerParser library
535
537
  referrer_source: {type: 'string', maxlength: 191, nullable: true},
536
538
  referrer_medium: {type: 'string', maxlength: 191, nullable: true},
537
539
  referrer_url: {type: 'string', maxlength: 2000, nullable: true},
540
+ // raw values from URL query parameters
541
+ utm_source: {type: 'string', maxlength: 191, nullable: true},
542
+ utm_medium: {type: 'string', maxlength: 191, nullable: true},
543
+ utm_campaign: {type: 'string', maxlength: 191, nullable: true},
544
+ utm_term: {type: 'string', maxlength: 191, nullable: true},
545
+ utm_content: {type: 'string', maxlength: 191, nullable: true},
538
546
  source: {
539
547
  type: 'string', maxlength: 50, nullable: false, validations: {
540
548
  isIn: [['member', 'import', 'system', 'api', 'admin']]
@@ -705,6 +713,7 @@ module.exports = {
705
713
  created_at: {type: 'dateTime', nullable: false},
706
714
  member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
707
715
  subscription_id: {type: 'string', maxlength: 24, nullable: false, references: 'members_stripe_customers_subscriptions.id', cascadeDelete: true},
716
+ // attribution values from ghost-history (member attribution tracking script)
708
717
  attribution_id: {type: 'string', maxlength: 24, nullable: true, index: true},
709
718
  attribution_type: {
710
719
  type: 'string', maxlength: 50, nullable: true, validations: {
@@ -712,9 +721,16 @@ module.exports = {
712
721
  }
713
722
  },
714
723
  attribution_url: {type: 'string', maxlength: 2000, nullable: true},
724
+ // referrer values from browser, processed by our referrerParser library
715
725
  referrer_source: {type: 'string', maxlength: 191, nullable: true},
716
726
  referrer_medium: {type: 'string', maxlength: 191, nullable: true},
717
727
  referrer_url: {type: 'string', maxlength: 2000, nullable: true},
728
+ // raw values from URL query parameters
729
+ utm_source: {type: 'string', maxlength: 191, nullable: true},
730
+ utm_medium: {type: 'string', maxlength: 191, nullable: true},
731
+ utm_campaign: {type: 'string', maxlength: 191, nullable: true},
732
+ utm_term: {type: 'string', maxlength: 191, nullable: true},
733
+ utm_content: {type: 'string', maxlength: 191, nullable: true},
718
734
  batch_id: {type: 'string', maxlength: 24, nullable: true}
719
735
  },
720
736
  offer_redemptions: {
@@ -746,6 +762,7 @@ module.exports = {
746
762
  member_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'members.id', setNullDelete: true},
747
763
  amount: {type: 'integer', nullable: false},
748
764
  currency: {type: 'string', maxlength: 50, nullable: false},
765
+ // attribution values from ghost-history (member attribution tracking script)
749
766
  attribution_id: {type: 'string', maxlength: 24, nullable: true},
750
767
  attribution_type: {
751
768
  type: 'string', maxlength: 50, nullable: true, validations: {
@@ -753,9 +770,16 @@ module.exports = {
753
770
  }
754
771
  },
755
772
  attribution_url: {type: 'string', maxlength: 2000, nullable: true},
773
+ // referrer values from browser, processed by our referrerParser library
756
774
  referrer_source: {type: 'string', maxlength: 191, nullable: true},
757
775
  referrer_medium: {type: 'string', maxlength: 191, nullable: true},
758
776
  referrer_url: {type: 'string', maxlength: 2000, nullable: true},
777
+ // raw values from URL query parameters
778
+ utm_source: {type: 'string', maxlength: 191, nullable: true},
779
+ utm_medium: {type: 'string', maxlength: 191, nullable: true},
780
+ utm_campaign: {type: 'string', maxlength: 191, nullable: true},
781
+ utm_term: {type: 'string', maxlength: 191, nullable: true},
782
+ utm_content: {type: 'string', maxlength: 191, nullable: true},
759
783
  created_at: {type: 'dateTime', nullable: false},
760
784
  donation_message: {type: 'string', maxlength: 255, nullable: true} // https://docs.stripe.com/payments/checkout/custom-fields
761
785
  },
@@ -1241,7 +1241,7 @@ class EmailRenderer {
1241
1241
  }, true) : null
1242
1242
  },
1243
1243
  preheader: this.#getEmailPreheader(post, segment, html),
1244
- preheaderSpacing: '&zwnj;&nbsp;'.repeat(75),
1244
+ preheaderSpacing: `${'&#8199;&#847; '.repeat(150)}${'&shy; '.repeat(200)} &nbsp;`,
1245
1245
  html,
1246
1246
 
1247
1247
  post: {
@@ -67,7 +67,8 @@ module.exports = class MailgunClient {
67
67
  subject: messageContent.subject,
68
68
  html: messageContent.html,
69
69
  text: messageContent.plaintext,
70
- 'recipient-variables': JSON.stringify(recipientData)
70
+ 'recipient-variables': JSON.stringify(recipientData),
71
+ 'h:Sender': message.from
71
72
  };
72
73
 
73
74
  // Do we have a custom List-Unsubscribe header set?
@@ -348,9 +349,9 @@ module.exports = class MailgunClient {
348
349
  /**
349
350
  * Returns the configured target delivery window in seconds
350
351
  * Ghost will attempt to deliver emails evenly distributed over this window
351
- *
352
+ *
352
353
  * Defaults to 0 (no delay) if not set
353
- *
354
+ *
354
355
  * @returns {number}
355
356
  */
356
357
  getTargetDeliveryWindow() {
@@ -23,7 +23,7 @@ const messages = {
23
23
  * @typedef {Object} TokenProvider<T, D>
24
24
  * @prop {(data: D) => Promise<T>} create
25
25
  * @prop {(token: T, options?: TokenValidateOptions) => Promise<D>} validate
26
- * @prop {(token: T) => Promise<string | null>} [getIdByToken]
26
+ * @prop {(token: T) => Promise<string | null>} [getRefByToken]
27
27
  * @prop {(otcRef: string, tokenValue: T) => string} [deriveOTC]
28
28
  */
29
29
 
@@ -108,7 +108,7 @@ class MagicLink {
108
108
  let otcRef = null;
109
109
  if (this.labsService?.isSet('membersSigninOTC') && otc) {
110
110
  try {
111
- otcRef = await this.getIdFromToken(token);
111
+ otcRef = await this.getRefFromToken(token);
112
112
  } catch (err) {
113
113
  this.sentry?.captureException?.(err);
114
114
  otcRef = null;
@@ -135,17 +135,17 @@ class MagicLink {
135
135
  }
136
136
 
137
137
  /**
138
- * getIdFromToken
138
+ * getRefFromToken
139
139
  *
140
- * @param {Token} token - The token to get the id from
141
- * @returns {Promise<string|null>} id - The id of the token
140
+ * @param {Token} token - The token to get the ref from
141
+ * @returns {Promise<string|null>} ref - The ref of the token
142
142
  */
143
- async getIdFromToken(token) {
144
- if (typeof this.tokenProvider.getIdByToken !== 'function') {
143
+ async getRefFromToken(token) {
144
+ if (typeof this.tokenProvider.getRefByToken !== 'function') {
145
145
  return null;
146
146
  }
147
147
 
148
- const id = await this.tokenProvider.getIdByToken(token);
148
+ const id = await this.tokenProvider.getRefByToken(token);
149
149
  return id;
150
150
  }
151
151
 
@@ -156,7 +156,7 @@ class MagicLink {
156
156
  * @returns {Promise<string|null>} otc - The otc of the token
157
157
  */
158
158
  async getOTCFromToken(token) {
159
- const tokenId = await this.getIdFromToken(token);
159
+ const tokenId = await this.getRefFromToken(token);
160
160
 
161
161
  if (!tokenId || typeof this.tokenProvider.deriveOTC !== 'function') {
162
162
  return null;
@@ -68,7 +68,10 @@ function createMessage(message) {
68
68
  ...message,
69
69
  ...addresses,
70
70
  generateTextFromHTML,
71
- encoding
71
+ encoding,
72
+ headers: {
73
+ Sender: addresses.from
74
+ }
72
75
  };
73
76
  }
74
77
 
@@ -1,6 +1,5 @@
1
1
  const logging = require('@tryghost/logging');
2
2
  const {URL} = require('url');
3
- const crypto = require('crypto');
4
3
  const createKeypair = require('keypair');
5
4
 
6
5
  class MembersConfigProvider {
@@ -42,20 +41,6 @@ class MembersConfigProvider {
42
41
  return this._settingsHelpers.isStripeConnected();
43
42
  }
44
43
 
45
- getAuthSecret() {
46
- const hexSecret = this._settingsCache.get('members_email_auth_secret');
47
- if (!hexSecret) {
48
- logging.warn('Could not find members_email_auth_secret, using dynamically generated secret');
49
- return crypto.randomBytes(64);
50
- }
51
- const secret = Buffer.from(hexSecret, 'hex');
52
- if (secret.length < 64) {
53
- logging.warn('members_email_auth_secret not large enough (64 bytes), using dynamically generated secret');
54
- return crypto.randomBytes(64);
55
- }
56
- return secret;
57
- }
58
-
59
44
  getAllowSelfSignup() {
60
45
  // Free signups are allowed only if the site subscription is set to "Full-access"
61
46
  // It is blocked for "Invite-only", "Paid-members-only" and "None" accesses
@@ -193,7 +193,7 @@ class SingleUseTokenProvider {
193
193
  }
194
194
 
195
195
  try {
196
- const model = await this.model.findOne({id: otcRef});
196
+ const model = await this.model.findOne({uuid: otcRef});
197
197
 
198
198
  if (!model) {
199
199
  return false;
@@ -208,16 +208,16 @@ class SingleUseTokenProvider {
208
208
  }
209
209
 
210
210
  /**
211
- * @method getIdByToken
212
- * Retrieves the ID associated with a given token.
211
+ * @method getRefByToken
212
+ * Retrieves the ref associated with a given token.
213
213
  *
214
214
  * @param {string} token - The token to look up.
215
- * @returns {Promise<string|null>} The ID if found, or null if not found or on error.
215
+ * @returns {Promise<string|null>} The ref if found, or null if not found or on error.
216
216
  */
217
- async getIdByToken(token) {
217
+ async getRefByToken(token) {
218
218
  try {
219
219
  const model = await this.model.findOne({token});
220
- return model ? model.get('id') : null;
220
+ return model ? model.get('uuid') : null;
221
221
  } catch (err) {
222
222
  return null;
223
223
  }
@@ -232,7 +232,7 @@ class SingleUseTokenProvider {
232
232
  */
233
233
  async getTokenByRef(ref) {
234
234
  try {
235
- const model = await this.model.findOne({id: ref});
235
+ const model = await this.model.findOne({uuid: ref});
236
236
  return model ? model.get('token') : null;
237
237
  } catch (err) {
238
238
  return null;
@@ -300,7 +300,7 @@ class SingleUseTokenProvider {
300
300
  return false;
301
301
  }
302
302
 
303
- const tokenId = await this.getIdByToken(token);
303
+ const tokenId = await this.getRefByToken(token);
304
304
  if (!tokenId) {
305
305
  return false;
306
306
  }