ghost 6.60.0 → 6.62.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 (613) hide show
  1. package/components/ghost-storage-base-3.0.0.tgz +0 -0
  2. package/components/tryghost-adapter-base-cache-0.2.0.tgz +0 -0
  3. package/components/tryghost-adapter-base-jobs-0.0.0.tgz +0 -0
  4. package/components/tryghost-adapter-base-redirects-0.0.0.tgz +0 -0
  5. package/components/tryghost-adapter-base-route-settings-0.0.0.tgz +0 -0
  6. package/components/tryghost-adapter-base-scheduling-0.2.3.tgz +0 -0
  7. package/components/tryghost-adapter-base-sso-0.1.3.tgz +0 -0
  8. package/components/tryghost-admin-api-schema-0.0.0.tgz +0 -0
  9. package/components/tryghost-checkout-0.0.0.tgz +0 -0
  10. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  11. package/components/tryghost-i18n-0.0.0.tgz +0 -0
  12. package/components/tryghost-kg-card-factory-5.2.4.tgz +0 -0
  13. package/components/tryghost-kg-clean-basic-html-4.3.4.tgz +0 -0
  14. package/components/tryghost-kg-converters-1.2.5.tgz +0 -0
  15. package/components/tryghost-kg-default-cards-10.3.5.tgz +0 -0
  16. package/components/tryghost-kg-default-nodes-2.2.1.tgz +0 -0
  17. package/components/tryghost-kg-default-transforms-1.3.4.tgz +0 -0
  18. package/components/tryghost-kg-html-to-lexical-1.4.0.tgz +0 -0
  19. package/components/tryghost-kg-lexical-html-renderer-1.5.0.tgz +0 -0
  20. package/components/tryghost-kg-markdown-html-renderer-7.2.4.tgz +0 -0
  21. package/components/tryghost-kg-utils-1.1.4.tgz +0 -0
  22. package/components/tryghost-nql-string-0.0.0.tgz +0 -0
  23. package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
  24. package/content/settings/README.md +1 -1
  25. package/content/themes/casper/.gitignore +28 -0
  26. package/content/themes/casper/assets/built/casper.js +1 -1
  27. package/content/themes/casper/assets/built/screen.css +1 -1
  28. package/content/themes/casper/assets/css/screen.css +2 -1
  29. package/content/themes/casper/assets/js/lightbox.js +63 -46
  30. package/content/themes/casper/package.json +6 -6
  31. package/content/themes/casper/page.hbs +1 -0
  32. package/content/themes/casper/post.hbs +1 -0
  33. package/content/themes/source/.gitignore +28 -0
  34. package/content/themes/source/assets/built/screen.css +1 -1
  35. package/content/themes/source/assets/built/source.js +1 -1
  36. package/content/themes/source/assets/css/screen.css +2 -1
  37. package/content/themes/source/assets/js/lightbox.js +62 -45
  38. package/content/themes/source/assets/js/main.js +2 -2
  39. package/content/themes/source/package.json +4 -4
  40. package/content/themes/source/partials/feature-image.hbs +1 -0
  41. package/core/boot.js +62 -21
  42. package/core/built/admin/assets/Combination-B0l4MKVh.js +41 -0
  43. package/core/built/admin/assets/InterVariable-DiVDrmQJ.woff2 +0 -0
  44. package/core/built/admin/assets/InterVariable-Italic-FCBEiFp6.woff2 +0 -0
  45. package/core/built/admin/assets/{PolarAngleAxis-C4qtHy1B.js → PolarAngleAxis-pkMyqz8g.js} +1 -1
  46. package/core/built/admin/assets/{RadialBarChart-Bh09Stsj.js → RadialBarChart-JAKoS4KT.js} +1 -1
  47. package/core/built/admin/assets/{about-BgpmhCuM.js → about-Dyae_UT6.js} +1 -1
  48. package/core/built/admin/assets/account-migration-CESa0mnX.js +1 -0
  49. package/core/built/admin/assets/{action-list-lGzZLHc8.js → action-list-C4v0-du8.js} +1 -1
  50. package/core/built/admin/assets/activitypub/{account-migration-DMHuPnJn.js → account-migration-By69CgCp.js} +16 -16
  51. package/core/built/admin/assets/activitypub/activitypub.js +9 -10
  52. package/core/built/admin/assets/activitypub/{avatar-DhTo5D1B.js → avatar-DLPL3Abs.js} +59 -68
  53. package/core/built/admin/assets/activitypub/{avatar-flipboard-DnVgbEfa.js → avatar-flipboard-C9iDY-QV.js} +2 -2
  54. package/core/built/admin/assets/activitypub/{bluesky-sharing-BT4zAlW7.js → bluesky-sharing-BJcPMrDd.js} +37 -37
  55. package/core/built/admin/assets/activitypub/{content-formatters-BeDZ-DIa.js → content-formatters-3rTHg3mp.js} +18 -18
  56. package/core/built/admin/assets/activitypub/{copy-8sqxUuNd.js → copy-DIAOQoCZ.js} +2 -2
  57. package/core/built/admin/assets/activitypub/{domain-xQDDu1CS.js → domain-DUR4xSkV.js} +121 -121
  58. package/core/built/admin/assets/activitypub/{edit-profile-BXdF_r2G.js → edit-profile-BqtfzxCp.js} +40 -40
  59. package/core/built/admin/assets/activitypub/{explore-CMt7gKlL.js → explore-BxuJd-J4.js} +27 -27
  60. package/core/built/admin/assets/activitypub/{feed-Bwqdddp_.js → feed-BM1TcksA.js} +51 -51
  61. package/core/built/admin/assets/activitypub/{field-4co_8FVg.js → field-Dn7HHx2-.js} +3 -3
  62. package/core/built/admin/assets/activitypub/{hash-DwakMs4X.js → hash-Bm3WxYN4.js} +2 -2
  63. package/core/built/admin/assets/activitypub/inbox-CCjkuow2.js +1284 -0
  64. package/core/built/admin/assets/activitypub/{inbox-ckD4wo_r.js → inbox-d8oJRTf4.js} +2 -2
  65. package/core/built/admin/assets/activitypub/label-1AA_s9Fa.js +16 -0
  66. package/core/built/admin/assets/activitypub/layout-2-0bw6HN.js +1 -0
  67. package/core/built/admin/assets/activitypub/{moderation-KCCO7L2J.js → moderation-CnmxEB8w.js} +91 -91
  68. package/core/built/admin/assets/activitypub/{note-BX_Bz5A0.js → note-ZKc_Bnba.js} +24 -24
  69. package/core/built/admin/assets/activitypub/{notifications-BQOYGvRv.js → notifications-CB2tJZau.js} +51 -51
  70. package/core/built/admin/assets/activitypub/{onboarding-CNelD_uF.js → onboarding-BsJ9E5qf.js} +2 -2
  71. package/core/built/admin/assets/activitypub/{onboarding-mat5Hrl0.js → onboarding-CSUJJIlh.js} +2 -2
  72. package/core/built/admin/assets/activitypub/{preferences-6h43G9_q.js → preferences-0-JZnUkI.js} +1519 -1519
  73. package/core/built/admin/assets/activitypub/{profile-BMN9VVZ2.js → profile-C35cFIhM.js} +256 -256
  74. package/core/built/admin/assets/activitypub/{reply-ZNo04xsO.js → reply-DYK5vVqb.js} +2 -2
  75. package/core/built/admin/assets/activitypub/{routes-3B9mUQ7i.js → routes-BD4vscyc.js} +796 -790
  76. package/core/built/admin/assets/activitypub/{separator-D9taVCCa.js → separator-BbQqTm-p.js} +3 -3
  77. package/core/built/admin/assets/activitypub/settings-BsMROdqh.js +315 -0
  78. package/core/built/admin/assets/activitypub/{site-DHoMupSB.js → site-O0RL9YCN.js} +2 -2
  79. package/core/built/admin/assets/activitypub/{step-1-BsGGnBuT.js → step-1-BMSx7c_X.js} +6 -6
  80. package/core/built/admin/assets/activitypub/{step-2-teosIS0k.js → step-2-DtPQzlTH.js} +6 -6
  81. package/core/built/admin/assets/activitypub/{step-3-BjD5TokC.js → step-3-CLlOZTyt.js} +11 -11
  82. package/core/built/admin/assets/activitypub/{tabs-Cvwl1wXU.js → tabs-Dxk-b9M-.js} +306 -306
  83. package/core/built/admin/assets/activitypub/{topic-filter-C3LZJc2X.js → topic-filter-5yZH6tId.js} +3 -3
  84. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-Dp9f0gov.js → use-activity-pub-queries-CV50qe03.js} +3 -3
  85. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-C4RI0sa9.js → use-navigate-with-base-path-Bmk5h4lW.js} +220 -220
  86. package/core/built/admin/assets/activitypub/{use-reply-chain-data-C2oNDMUH.js → use-reply-chain-data-Cbvzgjzd.js} +30 -30
  87. package/core/built/admin/assets/activitypub/{users-CoX6FT_K.js → users-BjcoP_HC.js} +7 -5
  88. package/core/built/admin/assets/activitypub/{x-Dxm6pV9X.js → x-DbQZOLiI.js} +2 -2
  89. package/core/built/admin/assets/add-integration-modal-BlF6rs_f.js +1 -0
  90. package/core/built/admin/assets/add-newsletter-modal-CglVQqah.js +1 -0
  91. package/core/built/admin/assets/add-recommendation-modal-wz7BsuaG.js +1 -0
  92. package/core/built/admin/assets/analytics-C4HE1gBg.js +1 -0
  93. package/core/built/admin/assets/{announcement-bar-modal-BTHiy1ZO.js → announcement-bar-modal-Ci4zz8jO.js} +1 -1
  94. package/core/built/admin/assets/{api-keys-Boy5pLbD.js → api-keys-Ck-Yteiv.js} +1 -1
  95. package/core/built/admin/assets/audience-CtbA54Sn.js +1 -0
  96. package/core/built/admin/assets/{automated-emails-te1D5kq-.js → automated-emails-D5VVMND9.js} +1 -1
  97. package/core/built/admin/assets/automations-CzKt5fb8.js +1 -0
  98. package/core/built/admin/assets/bluesky-sharing-BEtJ_gL_.js +1 -0
  99. package/core/built/admin/assets/{box-By_EgGtm.js → box-CdfQAP_F.js} +1 -1
  100. package/core/built/admin/assets/{brand-icon-09YqFZjV.js → brand-icon-CrIGOA3Z.js} +1 -1
  101. package/core/built/admin/assets/{breadcrumb-CcFr_ltt.js → breadcrumb-zUrZrBtW.js} +1 -1
  102. package/core/built/admin/assets/{button-BshWPHvn.js → button-eNMKQWXr.js} +1 -1
  103. package/core/built/admin/assets/{card-CiiE7QfM.js → card--VNbfgo3.js} +1 -1
  104. package/core/built/admin/assets/{chart-BnXyfH11.js → chart-BrhA41Zi.js} +1 -1
  105. package/core/built/admin/assets/chart-helpers-CfBKt0vT.js +1 -0
  106. package/core/built/admin/assets/{checkbox-Dwwr_3Py.js → checkbox-CgOn_tKW.js} +1 -1
  107. package/core/built/admin/assets/{chunk.524.91ecb33e6afc3bd8949b.js → chunk.524.a697dee88db67fdf6021.js} +4 -4
  108. package/core/built/admin/assets/{chunk.582.512549909741883f960c.js → chunk.582.5ed5d124cfca9c259b9c.js} +6 -6
  109. package/core/built/admin/assets/{chunk.16.f76a6314082d813d67e0.js → chunk.598.7e056fabb47ce7dbd73f.js} +14 -14
  110. package/core/built/admin/assets/{code-editor-view-BwIdPy_k.js → code-editor-view-BYNoAoNV.js} +1 -1
  111. package/core/built/admin/assets/{code-editor-view-CtkF82Fv.js → code-editor-view-K2wI-I5f.js} +1 -1
  112. package/core/built/admin/assets/color-picker-Ddw80xX6.js +3 -0
  113. package/core/built/admin/assets/color-picker-field-D9nUhvA7.js +1 -0
  114. package/core/built/admin/assets/combobox-w9j8PPQT.js +1 -0
  115. package/core/built/admin/assets/command-BfpHORBv.js +1 -0
  116. package/core/built/admin/assets/comments-BLmCmyCs.js +1 -0
  117. package/core/built/admin/assets/confirmation-context-CBM8G0Vj.js +1 -0
  118. package/core/built/admin/assets/{container-LiZf3XdY.js → container-BuQG1wuP.js} +1 -1
  119. package/core/built/admin/assets/{content-api-modal-BtpNI92-.js → content-api-modal-TXRz3U7S.js} +1 -1
  120. package/core/built/admin/assets/context-menu-_QMdd3kp.js +1 -0
  121. package/core/built/admin/assets/{copy-field-CglgRZyG.js → copy-field-6N01S07E.js} +1 -1
  122. package/core/built/admin/assets/create-ghost-browse-value-source-qIBymvvf.js +1 -0
  123. package/core/built/admin/assets/custom-field-type-option-BddFlJFa.js +1 -0
  124. package/core/built/admin/assets/custom-integration-modal-DmNquxUV.js +1 -0
  125. package/core/built/admin/assets/{data-list-CupF7bjM.js → data-list-CA2jYGPq.js} +1 -1
  126. package/core/built/admin/assets/{date-range-select-JPwM0yUj.js → date-range-select-uvz_DtPh.js} +1 -1
  127. package/core/built/admin/assets/design-and-theme-modal-BSmo12Op.js +2 -0
  128. package/core/built/admin/assets/{detail-page-D8lx2SdP.js → detail-page-Dnv-CdR1.js} +1 -1
  129. package/core/built/admin/assets/dirty-confirm-dialog-BZ1iytEd.js +1 -0
  130. package/core/built/admin/assets/{disabled-sources-indicator-BTJ2ClQu.js → disabled-sources-indicator-DMRrxsiJ.js} +1 -1
  131. package/core/built/admin/assets/{dist-DzcfoDJ3.js → dist-8YJUlf3B.js} +1 -1
  132. package/core/built/admin/assets/{dist-Cb5RSHSg.js → dist-B3keLvjj.js} +1 -1
  133. package/core/built/admin/assets/dist-Dcsslyo72.js +1 -0
  134. package/core/built/admin/assets/{dist-BT_Z-eCn.js → dist-Dgt2cr6a.js} +1 -1
  135. package/core/built/admin/assets/domain-BNYrt0aD.js +1 -0
  136. package/core/built/admin/assets/dropzone-D7nFdAe5.js +5 -0
  137. package/core/built/admin/assets/edit-profile-C5Asm4LA.js +1 -0
  138. package/core/built/admin/assets/editor-REokrzci.js +8 -0
  139. package/core/built/admin/assets/{embed-signup-form-modal-BFTPf04i.js → embed-signup-form-modal-Ds6mPEi5.js} +1 -1
  140. package/core/built/admin/assets/{empty-indicator-WB1WgT9h.js → empty-indicator-D9QdpuNX.js} +1 -1
  141. package/core/built/admin/assets/en-B1jX3WMs.js +1 -0
  142. package/core/built/admin/assets/explore-UY10Egsp.js +1 -0
  143. package/core/built/admin/assets/feature-image-placeholder-D1iF_8bc.js +1 -0
  144. package/core/built/admin/assets/feed-81C8mJWR.js +1 -0
  145. package/core/built/admin/assets/{field-LmNk7wZh.js → field-Dihp5p18.js} +1 -1
  146. package/core/built/admin/assets/filter-bar-Da1qxybu.js +1 -0
  147. package/core/built/admin/assets/filter-sources-BO9xUPj7.js +1 -0
  148. package/core/built/admin/assets/filters-CRUWecfY.js +1 -0
  149. package/core/built/admin/assets/filters-DirqNXjH.js +1 -0
  150. package/core/built/admin/assets/first-promoter-modal-CT6MxYZb.js +1 -0
  151. package/core/built/admin/assets/{ghost-c8807202a07c20030acb719ee0114815.css → ghost-8eabfc1ece81b03cf7b90780d31487e8.css} +1 -1
  152. package/core/built/admin/assets/{ghost-403cb39badad3df411fc0ba27d220047.js → ghost-c2848508292937415f20f5a830eea59e.js} +31 -28
  153. package/core/built/admin/assets/{ghost-dark-fbf1e384aaef3ae315e540f0eb7373a7.css → ghost-dark-a7bebe2c561f55369dcf29f4c820f7ff.css} +1 -1
  154. package/core/built/admin/assets/gift-link-modal-CQVAye17.js +1 -0
  155. package/core/built/admin/assets/{grid-CUciBfjf.js → grid-Dj29PY05.js} +1 -1
  156. package/core/built/admin/assets/growth-BWIu-6xj.js +3 -0
  157. package/core/built/admin/assets/growth-DU29oxeA.js +1 -0
  158. package/core/built/admin/assets/{heading-CRZZ2FUe.js → heading-Dh5SFVjL.js} +1 -1
  159. package/core/built/admin/assets/history-modal-CFd1Hvx6.js +1 -0
  160. package/core/built/admin/assets/html-field-CJBCLTvv.js +1 -0
  161. package/core/built/admin/assets/{images-C0XvUYMb.js → images-scfkWZiK.js} +1 -1
  162. package/core/built/admin/assets/{inbox-DZXDUt4s.js → inbox-Mcp4DXZN.js} +14 -14
  163. package/core/built/admin/assets/index-6MkPdCtr.js +4 -0
  164. package/core/built/admin/assets/index-Bn7QhcZu.css +2 -0
  165. package/core/built/admin/assets/index.es-CNnuNH0d.js +1 -0
  166. package/core/built/admin/assets/{inline-Cw-gigDW.js → inline-BWSs_VzR.js} +1 -1
  167. package/core/built/admin/assets/{input-group-BPAxETpQ.js → input-group-CMhe2DvY.js} +1 -1
  168. package/core/built/admin/assets/{input-surface-BdOe4KzO.js → input-surface-BsrIZeSF.js} +1 -1
  169. package/core/built/admin/assets/{integration-header-B4DyXV06.js → integration-header-Bm6IDb1A.js} +1 -1
  170. package/core/built/admin/assets/invite-user-modal-DfheMXs0.js +1 -0
  171. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +14 -14
  172. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +3 -3
  173. package/core/built/admin/assets/koenig-lexical/style.css +1 -1
  174. package/core/built/admin/assets/{koenig-lexical-60O_8aiO.js → koenig-lexical-DBSqAuiI.js} +3 -3
  175. package/core/built/admin/assets/koenig-loader-aFQNlJlD.js +2 -0
  176. package/core/built/admin/assets/kpi-card-DBRMfGOW.js +1 -0
  177. package/core/built/admin/assets/{kpi-card-fUHZlzBX.js → kpi-card-DFafD1qq.js} +1 -1
  178. package/core/built/admin/assets/kpi-helpers-Cv8i8hns.js +1 -0
  179. package/core/built/admin/assets/kpi-tabs-BJItvLYm.js +1 -0
  180. package/core/built/admin/assets/{label-VIG969IC.js → label-BnSCvffu.js} +1 -1
  181. package/core/built/admin/assets/label-picker-KVoo91sh.js +1 -0
  182. package/core/built/admin/assets/layout-Bjv2Ll-5.js +1 -0
  183. package/core/built/admin/assets/{link-helpers-Dtq9fxQg.js → link-helpers-MVDdnFCY.js} +1 -1
  184. package/core/built/admin/assets/{list-page-CdsnQR9N.js → list-page-BucXJ0hi.js} +1 -1
  185. package/core/built/admin/assets/member-custom-fields-DDQV6QeG.js +1 -0
  186. package/core/built/admin/assets/member-detail-CdJF6Agw.js +1 -0
  187. package/core/built/admin/assets/members-DjQbq9Z2.js +4 -0
  188. package/core/built/admin/assets/{modal-page-BMTzFHVO.js → modal-page-xCivTMvI.js} +1 -1
  189. package/core/built/admin/assets/moderation-AgCIEso4.js +1 -0
  190. package/core/built/admin/assets/{multi-select-combobox-CGohnsxJ.js → multi-select-combobox-BcITkUVr.js} +1 -1
  191. package/core/built/admin/assets/navigation-modal-BkT7b1M2.js +1 -0
  192. package/core/built/admin/assets/newsletter-BMMAuvgq.js +1 -0
  193. package/core/built/admin/assets/newsletter-detail-modal-0SCJ7Zpm.js +1 -0
  194. package/core/built/admin/assets/{newsletter-emails-D5ql1rud.js → newsletter-emails-DKzwafuh.js} +1 -1
  195. package/core/built/admin/assets/newsletters-BRBb_uQC.js +1 -0
  196. package/core/built/admin/assets/note-DyGHtS9a.js +1 -0
  197. package/core/built/admin/assets/notifications-ZgWniKna.js +1 -0
  198. package/core/built/admin/assets/nql-BLpCn7qX.js +21 -0
  199. package/core/built/admin/assets/offers-container-modal-B-E8TIWr.js +1 -0
  200. package/core/built/admin/assets/{onboarding-B4YhlZ0i.js → onboarding-CzIsre-d.js} +1 -1
  201. package/core/built/admin/assets/onboarding-qg8Akovm.js +1 -0
  202. package/core/built/admin/assets/{onboarding-route-Cu_ROWPn.js → onboarding-route-CaavZulg.js} +1 -1
  203. package/core/built/admin/assets/overview-BW8GLeYi.js +1 -0
  204. package/core/built/admin/assets/overview-BkXZNzaZ.js +1 -0
  205. package/core/built/admin/assets/{page-header-Cfds8pCg.js → page-header-De0VzKBi.js} +1 -1
  206. package/core/built/admin/assets/pagemenu-B5KKCpbA.js +1 -0
  207. package/core/built/admin/assets/pages-jgaFm69d.js +1 -0
  208. package/core/built/admin/assets/pages-route-CIUAQxtY.js +1 -0
  209. package/core/built/admin/assets/papaparse.min-cQwnxGCG.js +10 -0
  210. package/core/built/admin/assets/{pintura-modal-TDTrJ89x.js → pintura-modal-BZ2J9w3n.js} +1 -1
  211. package/core/built/admin/assets/portal-frame-B6Hr9wHc.js +1 -0
  212. package/core/built/admin/assets/portal-modal-D4ceei3t.js +1 -0
  213. package/core/built/admin/assets/post-analytics-header-BYCsT0GP.js +1 -0
  214. package/core/built/admin/assets/post-analytics-ot7f6pih.js +1 -0
  215. package/core/built/admin/assets/{post-analytics-provider-Cu3jNeFh.js → post-analytics-provider-Bo1gmYaP.js} +1 -1
  216. package/core/built/admin/assets/{post-share-modal-P1_M6o-H.js → post-share-modal-C-PaYZ_e.js} +1 -1
  217. package/core/built/admin/assets/posts-DuGazgTb.js +1 -0
  218. package/core/built/admin/assets/posts-list-screen-CBGGgjjj.js +2 -0
  219. package/core/built/admin/assets/posts-route-D6xmS7Vf.js +1 -0
  220. package/core/built/admin/assets/preferences-C5YJLp74.js +5 -0
  221. package/core/built/admin/assets/{preview-chrome-BOVO6_PE.js → preview-chrome-BljUBlOk.js} +1 -1
  222. package/core/built/admin/assets/{preview-modal-MlTjP8UU.js → preview-modal-BfeFqi5y.js} +1 -1
  223. package/core/built/admin/assets/profile-B8mx2yAu.js +1 -0
  224. package/core/built/admin/assets/{radio-group-CjtmsCPl.js → radio-group-DczlLKOn.js} +1 -1
  225. package/core/built/admin/assets/{router-provider-CJC58zTy.js → router-provider-B0gZyiGJ.js} +1 -1
  226. package/core/built/admin/assets/{schemas-CRsC53ls.js → schemas-DppqmpeJ.js} +2 -2
  227. package/core/built/admin/assets/select-cb0t_TAJ.js +1 -0
  228. package/core/built/admin/assets/settings-DZqxR0Da.js +1 -0
  229. package/core/built/admin/assets/settings-DvfTNdHQ.js +4 -0
  230. package/core/built/admin/assets/{settings-breadcrumbs-sLDNedBW.js → settings-breadcrumbs-DLzbRRSt.js} +1 -1
  231. package/core/built/admin/assets/{shade-app-F7WxOyfk-nufqw1hd.js → shade-app-CcS3f8MZ-vwURazk8.js} +2 -2
  232. package/core/built/admin/assets/share-modal-D2-Bz1_I.js +1 -0
  233. package/core/built/admin/assets/{skeleton-D7hwjdry.js → skeleton-Ce9GgUpf.js} +1 -1
  234. package/core/built/admin/assets/slack-modal-DYDTUria.js +1 -0
  235. package/core/built/admin/assets/sort-button-VVeQjjbu.js +1 -0
  236. package/core/built/admin/assets/{source-icon-BPsD5ees.js → source-icon-Ck5_5Kgq.js} +1 -1
  237. package/core/built/admin/assets/{stack-CIfOAAVf.js → stack-B2eszIiq.js} +1 -1
  238. package/core/built/admin/assets/stats-Rjmk5owg.js +1 -0
  239. package/core/built/admin/assets/stats-view-BGGmkmgv.js +1 -0
  240. package/core/built/admin/assets/{step-1-CMta6YFO.js → step-1-CsaBjeN4.js} +1 -1
  241. package/core/built/admin/assets/{step-2-BG04nh5q.js → step-2-D2vnxOOM.js} +1 -1
  242. package/core/built/admin/assets/{step-3-ZnMaVIPj.js → step-3-wBoLdGEb.js} +2 -2
  243. package/core/built/admin/assets/{stripe-button-DDoUKN5a.js → stripe-button-CDgLz0S0.js} +1 -1
  244. package/core/built/admin/assets/stripe-connect-modal-CSjMJetP.js +1 -0
  245. package/core/built/admin/assets/{switch-C9djUIMO.js → switch-CI4gLWqb.js} +1 -1
  246. package/core/built/admin/assets/{table-CrqQtkR2.js → table-Do6iQ83Q.js} +1 -1
  247. package/core/built/admin/assets/{tabs-CGisSbWq.js → tabs-D2yWLMp_.js} +1 -1
  248. package/core/built/admin/assets/tag-detail-BhXlOsCa.js +2 -0
  249. package/core/built/admin/assets/tags-ldukRApe.js +1 -0
  250. package/core/built/admin/assets/testimonials-modal-CtnZBJKl.js +1 -0
  251. package/core/built/admin/assets/{text-DU9Hv33_.js → text-BQDSDsKs.js} +1 -1
  252. package/core/built/admin/assets/{textarea-d9qEkMOP.js → textarea-DJ1Q5V39.js} +1 -1
  253. package/core/built/admin/assets/tier-detail-modal-BOs8e_G8.js +1 -0
  254. package/core/built/admin/assets/{toggle-group-CtKZURJd.js → toggle-group-ByUJsxwD.js} +1 -1
  255. package/core/built/admin/assets/{topic-filter-D8JT8Tq1.js → topic-filter-BgC3g5_v.js} +1 -1
  256. package/core/built/admin/assets/{transistor-modal-6PUf0idx.js → transistor-modal-Drr0SNfo.js} +1 -1
  257. package/core/built/admin/assets/trend-badge-6ye93YAW.js +1 -0
  258. package/core/built/admin/assets/{unsplash-modal-BPuDBPja.js → unsplash-modal-p6AoQoWp.js} +1 -1
  259. package/core/built/admin/assets/{use-analytics-data--opuzwcz.js → use-analytics-data-CWCMAESo.js} +1 -1
  260. package/core/built/admin/assets/use-check-theme-limit-error-B8bjF-gO.css +1 -0
  261. package/core/built/admin/assets/use-check-theme-limit-error-Cw3WQHy8.js +2 -0
  262. package/core/built/admin/assets/use-filter-params-BIni9taJ.js +1 -0
  263. package/core/built/admin/assets/use-growth-stats-DCVG3s9w.js +1 -0
  264. package/core/built/admin/assets/{use-limiter-TDSNiHPB.js → use-limiter-D2WRRx0H.js} +1 -1
  265. package/core/built/admin/assets/{use-navigate-with-base-path-CK989MlC.js → use-navigate-with-base-path-BHPA228f.js} +1 -1
  266. package/core/built/admin/assets/{use-pintura-config-CnCGrDFk.js → use-pintura-config-h-J0Lvta.js} +1 -1
  267. package/core/built/admin/assets/{use-pintura-editor-ntphvtUd.js → use-pintura-editor-BfN1UwzH.js} +1 -1
  268. package/core/built/admin/assets/use-post-referrers-Czg47m8s.js +1 -0
  269. package/core/built/admin/assets/use-reply-chain-data-ns0caK8k.js +1 -0
  270. package/core/built/admin/assets/{use-router-history-entry-BwfQS2mq.js → use-router-history-entry-uldn7zJO.js} +1 -1
  271. package/core/built/admin/assets/{use-setting-group-DQxV42-O.js → use-setting-group-DpcuCUDg.js} +1 -1
  272. package/core/built/admin/assets/use-settings-navigation-CZjGrhsp.js +1 -0
  273. package/core/built/admin/assets/{use-simple-pagination-B2TdNX67.js → use-simple-pagination-DzJWobPR.js} +1 -1
  274. package/core/built/admin/assets/{use-staff-users-QrR9voeC.js → use-staff-users-Csipb-O0.js} +1 -1
  275. package/core/built/admin/assets/use-tinybird-query-CSnGXzZh.js +1 -0
  276. package/core/built/admin/assets/use-tinybird-token-DTOKRR7e.js +1 -0
  277. package/core/built/admin/assets/{use-unsaved-changes-guard-DpYJcj1d.js → use-unsaved-changes-guard-CBzs-KPi.js} +1 -1
  278. package/core/built/admin/assets/{use-url-input-WnIqckzS.js → use-url-input-DFr8lCbS.js} +1 -1
  279. package/core/built/admin/assets/user-detail-modal-B_iUClI6.js +1 -0
  280. package/core/built/admin/assets/users-Cg-OeGzC.js +1 -0
  281. package/core/built/admin/assets/{virtual-list-D3MfPNOh.js → virtual-list-BW-padMe.js} +1 -1
  282. package/core/built/admin/assets/web-C1v33ehA.js +2 -0
  283. package/core/built/admin/assets/web-C4BKzn2T.js +1 -0
  284. package/core/built/admin/assets/{zapier-modal-BO6PS_Vd.js → zapier-modal-BBFfI6Ml.js} +1 -1
  285. package/core/built/admin/index.html +24 -30
  286. package/core/frontend/apps/private-blogging/lib/views/private.hbs +44 -43
  287. package/core/frontend/helpers/cancel_link.js +2 -2
  288. package/core/frontend/helpers/pagination.js +2 -2
  289. package/core/frontend/helpers/social_accounts.js +2 -2
  290. package/core/frontend/public/ghost.css +1 -1
  291. package/core/frontend/public/ghost.min.css +1 -1
  292. package/core/frontend/services/rendering/renderer.js +15 -0
  293. package/core/frontend/services/sitemap/user-map-generator.js +9 -2
  294. package/core/server/adapters/storage/LocalStorageBase.js +8 -5
  295. package/core/server/api/endpoints/db.js +19 -5
  296. package/core/server/api/endpoints/index.js +6 -2
  297. package/core/server/api/endpoints/mail.js +1 -1
  298. package/core/server/api/endpoints/member-metafields.js +89 -0
  299. package/core/server/api/endpoints/members.js +4 -4
  300. package/core/server/api/endpoints/posts.js +4 -3
  301. package/core/server/api/endpoints/tiers-checkout-config.js +59 -0
  302. package/core/server/api/endpoints/utils/serializers/input/members.js +4 -7
  303. package/core/server/api/endpoints/utils/serializers/output/config.js +1 -0
  304. package/core/server/api/endpoints/utils/serializers/output/index.js +6 -2
  305. package/core/server/api/endpoints/utils/serializers/output/{member-custom-fields.js → member-metafields.js} +2 -4
  306. package/core/server/api/endpoints/utils/serializers/output/members.js +2 -4
  307. package/core/server/api/endpoints/utils/serializers/output/tiers-checkout-config.js +12 -0
  308. package/core/server/api/endpoints/utils/validators/input/invitations.js +0 -5
  309. package/core/server/api/endpoints/utils/validators/input/pages.js +1 -1
  310. package/core/server/api/endpoints/utils/validators/input/posts.js +1 -1
  311. package/core/server/data/exporter/table-lists.js +3 -0
  312. package/core/server/data/importer/content-files.js +57 -0
  313. package/core/server/data/importer/handlers/image.js +2 -2
  314. package/core/server/data/importer/import-archive.js +131 -0
  315. package/core/server/data/importer/import-manager.js +18 -180
  316. package/core/server/data/importer/importers/content-file-importer.js +66 -4
  317. package/core/server/data/importer/importers/data/data-importer.js +3 -3
  318. package/core/server/data/migrations/utils/migrations.js +1 -1
  319. package/core/server/data/migrations/versions/6.61/2026-08-19-14-30-20-add-written-by-to-members-custom-field-values.js +42 -0
  320. package/core/server/data/migrations/versions/6.61/2026-08-19-14-38-53-add-members-custom-field-bindings-table.js +26 -0
  321. package/core/server/data/migrations/versions/6.61/2026-08-19-14-48-06-add-tier-checkout-field-tables.js +35 -0
  322. package/core/server/data/schema/schema.js +90 -0
  323. package/core/server/data/seeders/importers/automation-action-revisions-importer.js +3 -3
  324. package/core/server/data/seeders/importers/automation-actions-importer.js +3 -3
  325. package/core/server/data/seeders/importers/automation-run-steps-importer.js +3 -3
  326. package/core/server/data/seeders/importers/automation-runs-importer.js +5 -5
  327. package/core/server/data/seeders/importers/automations-importer.js +3 -3
  328. package/core/server/data/seeders/importers/comment-reports-importer.js +1 -1
  329. package/core/server/data/seeders/importers/comments-importer.js +1 -1
  330. package/core/server/data/seeders/importers/email-batches-importer.js +1 -1
  331. package/core/server/data/seeders/importers/email-recipients-importer.js +1 -1
  332. package/core/server/data/seeders/importers/emails-importer.js +1 -1
  333. package/core/server/data/seeders/importers/labels-importer.js +1 -1
  334. package/core/server/data/seeders/importers/members-click-events-importer.js +1 -1
  335. package/core/server/data/seeders/importers/members-created-events-importer.js +1 -1
  336. package/core/server/data/seeders/importers/members-feedback-importer.js +1 -1
  337. package/core/server/data/seeders/importers/members-importer.js +1 -1
  338. package/core/server/data/seeders/importers/members-login-events-importer.js +1 -1
  339. package/core/server/data/seeders/importers/members-products-importer.js +1 -1
  340. package/core/server/data/seeders/importers/members-status-events-importer.js +1 -1
  341. package/core/server/data/seeders/importers/members-stripe-customers-importer.js +1 -1
  342. package/core/server/data/seeders/importers/members-stripe-customers-subscriptions-importer.js +1 -1
  343. package/core/server/data/seeders/importers/members-subscribe-events-importer.js +1 -1
  344. package/core/server/data/seeders/importers/members-subscription-created-events-importer.js +1 -1
  345. package/core/server/data/seeders/importers/offer-redemptions-importer.js +1 -1
  346. package/core/server/data/seeders/importers/offers-importer.js +1 -1
  347. package/core/server/data/seeders/importers/posts-importer.js +1 -1
  348. package/core/server/data/seeders/importers/table-importer.js +2 -2
  349. package/core/server/data/seeders/importers/tags-importer.js +1 -1
  350. package/core/server/data/seeders/importers/users-importer.js +1 -1
  351. package/core/server/data/seeders/importers/web-mentions-importer.js +1 -1
  352. package/core/server/data/seeders/utils/random.js +3 -3
  353. package/core/server/ghost-server.js +3 -2
  354. package/core/server/lib/db-types/boolean.js +18 -0
  355. package/core/server/lib/image/image-size.js +24 -0
  356. package/core/server/lib/package-json/parse.js +2 -2
  357. package/core/server/lib/promise-pool.js +31 -0
  358. package/core/server/models/member.js +6 -17
  359. package/core/server/models/post.js +2 -2
  360. package/core/server/models/relations/authors.js +2 -16
  361. package/core/server/models/user.js +9 -2
  362. package/core/server/services/auth/passwordreset.js +13 -0
  363. package/core/server/services/automations/database-automations-repository.js +26 -26
  364. package/core/server/services/automations/poll.js +1 -0
  365. package/core/server/services/comments/comments-controller.js +30 -9
  366. package/core/server/services/comments/comments-service.js +55 -5
  367. package/core/server/services/content-import/csv/formula.js +12 -0
  368. package/core/server/services/content-import/csv/index.js +6 -1
  369. package/core/server/services/content-import/csv/parse.js +27 -6
  370. package/core/server/services/content-import/csv/serialize.js +13 -0
  371. package/core/server/services/content-import/import/assets.js +74 -0
  372. package/core/server/services/content-import/import/completion-email.js +164 -0
  373. package/core/server/services/content-import/import/errors-file.js +36 -0
  374. package/core/server/services/content-import/import/importer.js +204 -54
  375. package/core/server/services/content-import/import/local-media-url.js +63 -0
  376. package/core/server/services/content-import/import/media.js +254 -0
  377. package/core/server/services/content-import/import/post-data.js +1 -1
  378. package/core/server/services/content-import/import/post-link.js +11 -0
  379. package/core/server/services/content-import/import/post-repository.js +100 -0
  380. package/core/server/services/content-import/import/reader.js +9 -2
  381. package/core/server/services/content-import/import/relations.js +149 -0
  382. package/core/server/services/content-import/import/report.js +34 -0
  383. package/core/server/services/content-import/import/row.js +11 -2
  384. package/core/server/services/content-import/import/schema.js +30 -0
  385. package/core/server/services/content-import/import/source.js +125 -0
  386. package/core/server/services/content-import/import/staged-file.js +29 -0
  387. package/core/server/services/content-import/import/store.js +25 -2
  388. package/core/server/services/content-import/index.js +58 -34
  389. package/core/server/services/content-import/jobs/content-csv-import-job.js +20 -0
  390. package/core/server/services/custom-redirects/redirect-config-parser.js +2 -2
  391. package/core/server/services/custom-redirects/redirects-service.js +1 -1
  392. package/core/server/services/custom-redirects/validation.js +1 -1
  393. package/core/server/services/email-address/email-address-parser.js +30 -38
  394. package/core/server/services/email-address/email-address-service.js +37 -4
  395. package/core/server/services/email-analytics/email-analytics-service.js +8 -1
  396. package/core/server/services/email-analytics/index.js +2 -7
  397. package/core/server/services/email-analytics/lib/queries.js +7 -1
  398. package/core/server/services/email-service/batch-sending-service.js +0 -4
  399. package/core/server/services/email-service/email-renderer.js +14 -4
  400. package/core/server/services/email-service/email-service-wrapper.js +0 -9
  401. package/core/server/services/email-service/mailgun-email-provider.js +20 -9
  402. package/core/server/services/gift-links/schema.js +3 -3
  403. package/core/server/services/gifts/CONTEXT.md +1 -1
  404. package/core/server/services/gifts/constants.js +1 -1
  405. package/core/server/services/gifts/email-templates/gift-delivery.hbs +3 -3
  406. package/core/server/services/gifts/gift-bookshelf-repository.js +2 -2
  407. package/core/server/services/gifts/gift-delivery-bookshelf-repository.js +18 -9
  408. package/core/server/services/gifts/gift-delivery-schema.js +5 -5
  409. package/core/server/services/gifts/gift-delivery-service.js +4 -0
  410. package/core/server/services/gifts/gift-email-service.js +0 -1
  411. package/core/server/services/gifts/gift-schema.js +10 -10
  412. package/core/server/services/gifts/gift-service.js +4 -2
  413. package/core/server/services/gifts/index.js +3 -3
  414. package/core/server/services/gifts/jobs/index.js +4 -4
  415. package/core/server/services/indexnow-ping/indexnow-ping-service.js +2 -2
  416. package/core/server/services/invitations/accept.js +4 -2
  417. package/core/server/services/jobs/job-service.js +0 -9
  418. package/core/server/services/jobs-service/register-job-handlers.js +25 -15
  419. package/core/server/services/lib/mailgun-client.js +44 -8
  420. package/core/server/services/mail/ghost-mailer.js +4 -4
  421. package/core/server/services/media-inliner/external-media-inliner-job.js +12 -0
  422. package/core/server/services/media-inliner/external-media-inliner.js +103 -29
  423. package/core/server/services/media-inliner/service.js +13 -41
  424. package/core/server/services/media-inliner/types.js +2 -0
  425. package/core/server/services/member-welcome-emails/service.js +7 -7
  426. package/core/server/services/members/api.js +1 -0
  427. package/core/server/services/members/import-export/csv/parse.js +0 -7
  428. package/core/server/services/members/import-export/export/exporter.js +4 -6
  429. package/core/server/services/members/import-export/import/completion-email.js +2 -5
  430. package/core/server/services/members/import-export/import/importer.js +21 -5
  431. package/core/server/services/members/import-export/import/row.js +0 -3
  432. package/core/server/services/members/import-export/import/stripe-utils.js +6 -1
  433. package/core/server/services/members/import-export/index.js +10 -12
  434. package/core/server/services/members/jobs/clean-expired-comped-job.js +7 -0
  435. package/core/server/services/members/jobs/clean-expired-comped-task.js +145 -0
  436. package/core/server/services/members/jobs/index.js +48 -24
  437. package/core/server/services/members/members-api/controllers/member-controller.js +11 -0
  438. package/core/server/services/members/members-api/controllers/router-controller.js +3 -6
  439. package/core/server/services/members/members-api/members-api.js +9 -6
  440. package/core/server/services/members/members-api/repositories/event-repository.js +0 -4
  441. package/core/server/services/members/members-api/services/member-bread-service.js +53 -63
  442. package/core/server/services/members/members-api/services/payments-service.js +47 -1
  443. package/core/server/services/members/service.js +1 -5
  444. package/core/server/services/members-custom-fields/actions.js +27 -1
  445. package/core/server/services/members-custom-fields/bindings-service.js +120 -0
  446. package/core/server/services/members-custom-fields/codec.js +3 -4
  447. package/core/server/services/members-custom-fields/definitions-service.js +165 -22
  448. package/core/server/services/members-custom-fields/filter.js +75 -93
  449. package/core/server/services/members-custom-fields/index.js +11 -4
  450. package/core/server/services/members-custom-fields/key.js +2 -1
  451. package/core/server/services/members-custom-fields/models.js +1 -1
  452. package/core/server/services/members-custom-fields/queries.js +5 -0
  453. package/core/server/services/members-custom-fields/schema.js +37 -6
  454. package/core/server/services/members-custom-fields/serializers.js +3 -5
  455. package/core/server/services/members-custom-fields/values-service.js +65 -31
  456. package/core/server/services/mentions/mention-controller.js +23 -21
  457. package/core/server/services/mentions/process-webmention-job.js +16 -0
  458. package/core/server/services/mentions/service.js +8 -4
  459. package/core/server/services/public-config/config.js +7 -0
  460. package/core/server/services/route-settings/validation-errors.js +1 -1
  461. package/core/server/services/route-settings/yaml-parser.js +1 -1
  462. package/core/server/services/settings/settings-service.js +1 -8
  463. package/core/server/services/settings-helpers/settings-helpers.js +4 -6
  464. package/core/server/services/slack-ping/slack-ping-service.js +1 -1
  465. package/core/server/services/staff/staff-service-emails.js +2 -2
  466. package/core/server/services/stripe/config.js +14 -8
  467. package/core/server/services/stripe/service.js +10 -1
  468. package/core/server/services/stripe/services/checkout/completed-session.js +68 -0
  469. package/core/server/services/stripe/services/checkout/session-options.js +100 -0
  470. package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +71 -1
  471. package/core/server/services/stripe/stripe-api.js +31 -0
  472. package/core/server/services/stripe/stripe-service.js +26 -9
  473. package/core/server/services/stripe/webhook-controller.js +10 -0
  474. package/core/server/services/tier-checkout-config/codec.js +99 -0
  475. package/core/server/services/tier-checkout-config/index.js +26 -0
  476. package/core/server/services/tier-checkout-config/models.js +63 -0
  477. package/core/server/services/tier-checkout-config/queries.js +80 -0
  478. package/core/server/services/tier-checkout-config/schema.js +33 -0
  479. package/core/server/services/tier-checkout-config/serializers.js +128 -0
  480. package/core/server/services/tier-checkout-config/service.js +304 -0
  481. package/core/server/services/tiers/service.js +21 -0
  482. package/core/server/services/update-check/index.js +13 -13
  483. package/core/server/services/update-check/jobs/update-check-job.js +7 -0
  484. package/core/server/services/update-check/update-check-service.js +2 -2
  485. package/core/server/web/admin/controller.js +1 -1
  486. package/core/server/web/api/endpoints/admin/routes.js +34 -20
  487. package/core/server/web/api/middleware/upload.js +2 -2
  488. package/core/server/web/parent/app.js +5 -0
  489. package/core/server/web/parent/middleware/filter-query-parameters.js +94 -0
  490. package/core/server/web/parent/middleware/index.js +1 -0
  491. package/core/server/web/query-parameter-policy/index.js +9 -0
  492. package/core/server/web/query-parameter-policy/policy.json +58 -0
  493. package/core/server/web/query-parameter-policy/schema.js +38 -0
  494. package/core/shared/config/defaults.json +3 -0
  495. package/core/shared/config/env/config.testing-mysql.json +4 -1
  496. package/core/shared/config/env/config.testing.json +3 -0
  497. package/core/shared/config/overrides.json +4 -2
  498. package/core/shared/labs.js +9 -15
  499. package/package.json +25 -25
  500. package/pnpm-lock.yaml +741 -108
  501. package/pnpm-workspace.yaml +12 -9
  502. package/tsconfig.json +2 -3
  503. package/components/tryghost-kg-default-nodes-2.2.0.tgz +0 -0
  504. package/core/built/admin/assets/Combination-gnJO3vIw.js +0 -41
  505. package/core/built/admin/assets/account-migration-8qTEI1i1.js +0 -1
  506. package/core/built/admin/assets/activitypub/inbox-DQ5CwLma.js +0 -1284
  507. package/core/built/admin/assets/activitypub/label-B72sfvEU.js +0 -16
  508. package/core/built/admin/assets/activitypub/layout-CXajhRsL.js +0 -1
  509. package/core/built/admin/assets/activitypub/settings-71gCm3SO.js +0 -315
  510. package/core/built/admin/assets/add-integration-modal--YpTq3c0.js +0 -1
  511. package/core/built/admin/assets/add-newsletter-modal-C8Ix2hol.js +0 -1
  512. package/core/built/admin/assets/add-recommendation-modal-CScuNFz4.js +0 -1
  513. package/core/built/admin/assets/alert-dialog-wEbVxANM.js +0 -1
  514. package/core/built/admin/assets/analytics-Cnji45H5.js +0 -1
  515. package/core/built/admin/assets/app-utils-D2Wp7ZSS.js +0 -1
  516. package/core/built/admin/assets/audience-DTLMXAFH.js +0 -1
  517. package/core/built/admin/assets/automations-BWpRCZ_f.js +0 -1
  518. package/core/built/admin/assets/bluesky-sharing-DHOpQxff.js +0 -1
  519. package/core/built/admin/assets/chart-helpers-B3OqQ6Ez.js +0 -1
  520. package/core/built/admin/assets/color-picker-Bmo1r9Fc.js +0 -3
  521. package/core/built/admin/assets/color-picker-field-DywowPhF.js +0 -1
  522. package/core/built/admin/assets/combobox-C4QCh6Yw.js +0 -1
  523. package/core/built/admin/assets/command-DcLbSifC.js +0 -1
  524. package/core/built/admin/assets/comments-Bxy_3WyE.js +0 -1
  525. package/core/built/admin/assets/confirmation-context-CLFHhyyH.js +0 -1
  526. package/core/built/admin/assets/custom-field-type-option-D0PXoSnP.js +0 -10
  527. package/core/built/admin/assets/custom-integration-modal-DCIHKN33.js +0 -1
  528. package/core/built/admin/assets/design-and-theme-modal-CZ-PkW7o.js +0 -23
  529. package/core/built/admin/assets/dialog-Db7c164G.js +0 -1
  530. package/core/built/admin/assets/dirty-confirm-dialog-CmARpZQS.js +0 -1
  531. package/core/built/admin/assets/dist-QLZuGq7o2.js +0 -1
  532. package/core/built/admin/assets/domain-D0SAtc-T.js +0 -1
  533. package/core/built/admin/assets/dropzone-CaPOeDRZ.js +0 -5
  534. package/core/built/admin/assets/edit-profile-By7v5XW5.js +0 -1
  535. package/core/built/admin/assets/editor-COXn4Atu.js +0 -8
  536. package/core/built/admin/assets/explore-BsNYrubV.js +0 -1
  537. package/core/built/admin/assets/feed-8RR1O1bK.js +0 -1
  538. package/core/built/admin/assets/filter-sources-4C0kF1NU.js +0 -1
  539. package/core/built/admin/assets/filters-CV5jCb9n.js +0 -1
  540. package/core/built/admin/assets/filters-Dmr0PoqU.js +0 -1
  541. package/core/built/admin/assets/first-promoter-modal-OCzSaL3B.js +0 -1
  542. package/core/built/admin/assets/gift-link-modal-0mzjn5oF.js +0 -1
  543. package/core/built/admin/assets/growth-55vWlGT_.js +0 -1
  544. package/core/built/admin/assets/growth-DA5AluYw.js +0 -3
  545. package/core/built/admin/assets/history-modal-G1s5hHz7.js +0 -1
  546. package/core/built/admin/assets/html-field-BegxsWpw.js +0 -1
  547. package/core/built/admin/assets/index-CDP9tIOk.js +0 -4
  548. package/core/built/admin/assets/index-Dqf2x-ry.css +0 -1
  549. package/core/built/admin/assets/index.es-BMUCj9TR.js +0 -1
  550. package/core/built/admin/assets/input-UHrKuvBT.js +0 -1
  551. package/core/built/admin/assets/invite-user-modal-BrAkxEO3.js +0 -1
  552. package/core/built/admin/assets/koenig-loader-DwVY_V-H.js +0 -1
  553. package/core/built/admin/assets/kpi-card-LERqWBoY.js +0 -1
  554. package/core/built/admin/assets/kpi-helpers-DB3ouhYF.js +0 -1
  555. package/core/built/admin/assets/kpi-tabs-BGvtMD8S.js +0 -1
  556. package/core/built/admin/assets/label-picker-BBWykUeA.js +0 -1
  557. package/core/built/admin/assets/layout-Di5K7doM.js +0 -1
  558. package/core/built/admin/assets/loading-indicator-SFTqyYVE.js +0 -1
  559. package/core/built/admin/assets/member-custom-fields-gOv6Hoh3.js +0 -1
  560. package/core/built/admin/assets/member-detail-D7mZDDp3.js +0 -1
  561. package/core/built/admin/assets/member-route-0Aa52Ntz.js +0 -1
  562. package/core/built/admin/assets/members-DvZG9t69.js +0 -2
  563. package/core/built/admin/assets/moderation-DzzPLDyl.js +0 -1
  564. package/core/built/admin/assets/navigation-modal-CI8Bx5vn.js +0 -1
  565. package/core/built/admin/assets/newsletter-Bg3cq5U-.js +0 -1
  566. package/core/built/admin/assets/newsletter-detail-modal-DQU3r4g2.js +0 -1
  567. package/core/built/admin/assets/newsletters-DMDfZbMS.js +0 -1
  568. package/core/built/admin/assets/note-i8k6pjaw.js +0 -1
  569. package/core/built/admin/assets/notifications-psA_2Ph4.js +0 -1
  570. package/core/built/admin/assets/offers-container-modal-BWfjSdyg.js +0 -1
  571. package/core/built/admin/assets/onboarding-DGQuV7Ew.js +0 -1
  572. package/core/built/admin/assets/overview-C-6xOLaf.js +0 -1
  573. package/core/built/admin/assets/overview-DaLhpRrC.js +0 -1
  574. package/core/built/admin/assets/pages-B6vr9F_D.js +0 -1
  575. package/core/built/admin/assets/popover-CFgqFDvC.js +0 -1
  576. package/core/built/admin/assets/portal-frame-DjBF-uy2.js +0 -1
  577. package/core/built/admin/assets/portal-modal-Cf0pBAk8.js +0 -1
  578. package/core/built/admin/assets/post-analytics-Buz1COoY.js +0 -1
  579. package/core/built/admin/assets/post-analytics-header-B-P0eFnT.js +0 -1
  580. package/core/built/admin/assets/posts-KY3aUhVV.js +0 -1
  581. package/core/built/admin/assets/preferences-Cq1aOiLy.js +0 -5
  582. package/core/built/admin/assets/profile-D6dHYf2o.js +0 -1
  583. package/core/built/admin/assets/select-D_RQaXgt.js +0 -1
  584. package/core/built/admin/assets/settings-B5hJKZ5C.js +0 -1
  585. package/core/built/admin/assets/settings-DIo_F-MF.js +0 -4
  586. package/core/built/admin/assets/share-modal-CLBfj377.js +0 -1
  587. package/core/built/admin/assets/shim-ClF_m5ux.js +0 -1
  588. package/core/built/admin/assets/slack-modal-Bt9I_tEo.js +0 -1
  589. package/core/built/admin/assets/sort-button-DMBXF7tP.js +0 -1
  590. package/core/built/admin/assets/stats-B_YkF7bp.js +0 -1
  591. package/core/built/admin/assets/stats-view-BOCp9RG8.js +0 -1
  592. package/core/built/admin/assets/stripe-connect-modal-BeSukI8q.js +0 -1
  593. package/core/built/admin/assets/tag-detail-2a3BfzTW.js +0 -2
  594. package/core/built/admin/assets/tags-Dp2_8UzL.js +0 -1
  595. package/core/built/admin/assets/testimonials-modal-C9nrypNo.js +0 -1
  596. package/core/built/admin/assets/tier-detail-modal-CxRwgTPR.js +0 -1
  597. package/core/built/admin/assets/use-check-theme-limit-error-C9-QhGdV.js +0 -1
  598. package/core/built/admin/assets/use-filter-params-CSpAvgD2.js +0 -1
  599. package/core/built/admin/assets/use-growth-stats-BOmoPDHJ.js +0 -1
  600. package/core/built/admin/assets/use-post-referrers-CbXLFIex.js +0 -1
  601. package/core/built/admin/assets/use-reply-chain-data-JYFKSO83.js +0 -1
  602. package/core/built/admin/assets/use-settings-navigation-BBzro2-V.js +0 -1
  603. package/core/built/admin/assets/use-tinybird-query-D2Aeoty5.js +0 -39
  604. package/core/built/admin/assets/use-tinybird-token-DBuwMmln.js +0 -1
  605. package/core/built/admin/assets/user-detail-modal-59mBJqxN.js +0 -1
  606. package/core/built/admin/assets/users-5msE1WMs.js +0 -1
  607. package/core/built/admin/assets/web-DUuX7nE2.js +0 -2
  608. package/core/built/admin/assets/web-N8ucs2aG.js +0 -1
  609. package/core/server/api/endpoints/member-custom-fields.js +0 -100
  610. package/core/server/services/jobs/worker-model-event-bridge.js +0 -149
  611. package/core/server/services/members/jobs/clean-expired-comped.js +0 -132
  612. package/core/server/services/update-check/run-update-check.js +0 -63
  613. /package/core/server/lib/{db-date.js → db-types/date.js} +0 -0
@@ -8,6 +8,7 @@ const bson_objectid_1 = __importDefault(require("bson-objectid"));
8
8
  const errors_1 = __importDefault(require("@tryghost/errors"));
9
9
  const zod_1 = require("zod");
10
10
  const custom_field_types_1 = require("@tryghost/custom-field-types");
11
+ const identity_1 = require("@tryghost/custom-field-types/identity");
11
12
  const codec_1 = require("./codec");
12
13
  const schema_1 = require("./schema");
13
14
  const queries_1 = require("./queries");
@@ -33,10 +34,12 @@ const MAX_KEY_BASE_LENGTH = MAX_KEY_LENGTH - (String(MAX_KEY_ITERATIONS).length
33
34
  // object from JSON.parse. A key naming a member of Object.prototype reads back as
34
35
  // inherited rather than absent wherever one of those objects is indexed.
35
36
  //
36
- // Derived rather than listed, because the set is a consequence of how keys are
37
- // minted: minting lowercases and trims leading underscores, so a prototype name
38
- // survives only if it has neither. `constructor` is the only one.
39
- const RESERVED_KEYS = Object.getOwnPropertyNames(Object.prototype).filter((name) => (0, key_1.mintableKey)(name) === name);
37
+ // Derived rather than listed, from the format a key may take rather than from how one
38
+ // is minted: a caller can state a key instead of deriving it from a name, so what has
39
+ // to be reserved is every prototype name a well-formed key could spell. The ones that
40
+ // carry a capital cannot be spelled at all and need no reserving; `constructor` and
41
+ // `__proto__` can.
42
+ const RESERVED_KEYS = Object.getOwnPropertyNames(Object.prototype).filter((name) => key_1.KEY_CHARACTERS.test(name));
40
43
  const FieldName = zod_1.z
41
44
  .string()
42
45
  .trim()
@@ -90,7 +93,29 @@ class CustomFieldDefinitionsService {
90
93
  // restart. Where the number comes from is the caller's business.
91
94
  this.getMaxDefinitions = getMaxDefinitions;
92
95
  }
96
+ async hasAnyActive() {
97
+ const [field] = await this.list((0, queries_1.activeFields)(this.knex).limit(1));
98
+ return Boolean(field);
99
+ }
100
+ /**
101
+ * The fields table has no namespace column: every row in it belongs to the publisher's
102
+ * `custom` namespace, so no other namespace can have a stored field.
103
+ */
104
+ isStored(namespace) {
105
+ return namespace === identity_1.CUSTOM_NAMESPACE;
106
+ }
107
+ assertDefinable(namespace) {
108
+ if (!this.isStored(namespace)) {
109
+ throw new errors_1.default.ValidationError({
110
+ message: `Fields cannot be defined in the "${namespace}" namespace.`,
111
+ property: 'namespace',
112
+ });
113
+ }
114
+ }
93
115
  async browse(options = {}) {
116
+ if (options.namespace !== undefined && !this.isStored(options.namespace)) {
117
+ return [];
118
+ }
94
119
  // Archived fields are hidden by default: most surfaces (member details, the
95
120
  // filter picker, the importer) only ever want active fields. A caller-
96
121
  // supplied `filter` can widen that — Settings pulls active and archived
@@ -113,12 +138,12 @@ class CustomFieldDefinitionsService {
113
138
  const rows = await (0, queries_1.inFieldOrder)(query).select('*');
114
139
  return rows.map((row) => zod_1.z.decode(codec_1.customFieldCodec, row));
115
140
  }
116
- async read(key) {
117
- const row = await this.knex(TABLE).where('key', key).first();
118
- if (!row) {
141
+ async read(namespace, key) {
142
+ const [field] = this.isStored(namespace) ? await this.list((0, queries_1.fieldByKey)(this.knex, key)) : [];
143
+ if (!field) {
119
144
  throw new errors_1.default.NotFoundError({ message: 'Custom field not found.' });
120
145
  }
121
- return zod_1.z.decode(codec_1.customFieldCodec, row);
146
+ return field;
122
147
  }
123
148
  /**
124
149
  * Create one or more field definitions. All-or-nothing: the batch runs in a
@@ -130,7 +155,8 @@ class CustomFieldDefinitionsService {
130
155
  * batch, so two items sharing a name are caught and two items deriving the same
131
156
  * key get distinct ones, exactly as if they had arrived as separate requests.
132
157
  */
133
- async add(context, input) {
158
+ async add(context, namespace, input) {
159
+ this.assertDefinable(namespace);
134
160
  const requestedCount = Array.isArray(input) ? input.length : 0;
135
161
  const parsed = AddFieldsInput.safeParse(input);
136
162
  if (!parsed.success) {
@@ -167,13 +193,11 @@ class CustomFieldDefinitionsService {
167
193
  for (const [index, field] of fields.entries()) {
168
194
  await this.assertNameAvailable(trx, field.name);
169
195
  const key = await this.mintKey(trx, bases[index]);
170
- await trx(TABLE).insert({
171
- id: new bson_objectid_1.default().toHexString(),
196
+ await this.insertField(trx, {
172
197
  key,
173
198
  name: field.name,
174
199
  type: field.type,
175
- sort_order: firstSortOrder + index,
176
- created_at: new Date(),
200
+ sortOrder: firstSortOrder + index,
177
201
  });
178
202
  keys.push(key);
179
203
  }
@@ -194,7 +218,69 @@ class CustomFieldDefinitionsService {
194
218
  // Logged after the commit: the action log is a separate Bookshelf write
195
219
  // outside this transaction, so recording inside it would leave orphaned
196
220
  // "added" entries for fields a rollback never created.
197
- for (const field of created) {
221
+ await this.recordCreated(context, created);
222
+ return created;
223
+ }
224
+ /**
225
+ * Given an executor it joins that transaction; given none it opens its own. Unlike
226
+ * `add`, the key is stated rather than minted from the name, and nothing about it is
227
+ * worked around: a caller states a key because something else already names that key,
228
+ * so a variant of it would be a field nothing points at.
229
+ *
230
+ * The field is returned rather than logged: the history writes on its own connection,
231
+ * which a single-connection pool would deadlock against an open transaction.
232
+ */
233
+ async addOne(wanted, { executor = this.knex } = {}) {
234
+ // Before any database access, the way `add` mints before opening its transaction:
235
+ // an unusable key is a payload problem worth reporting on its own terms.
236
+ assertKeyUsable(wanted.key);
237
+ const write = async (db) => {
238
+ await this.assertWithinLimit(db, 1);
239
+ await this.assertKeyAvailable(db, wanted.key);
240
+ await this.assertNameAvailable(db, wanted.name);
241
+ await this.insertField(db, {
242
+ key: wanted.key,
243
+ name: wanted.name,
244
+ type: wanted.type,
245
+ sortOrder: await this.nextSortOrder(db),
246
+ });
247
+ const [created] = await this.readMany(db, [wanted.key]);
248
+ return created;
249
+ };
250
+ // knex's marker for a transactor: join it rather than nesting a savepoint under it.
251
+ return executor.isTransaction ? write(executor) : executor.transaction(write);
252
+ }
253
+ /**
254
+ * Only for a stated key. Minting picks a free one instead, so this reads as a clean 422
255
+ * where the unique index would read as a 500.
256
+ */
257
+ async assertKeyAvailable(db, key) {
258
+ const taken = await db(TABLE).where('key', key).first();
259
+ if (taken) {
260
+ throw new errors_1.default.ValidationError({
261
+ message: 'A custom field with this key already exists.',
262
+ property: 'key',
263
+ });
264
+ }
265
+ }
266
+ async insertField(db, field) {
267
+ await db(TABLE).insert({
268
+ id: new bson_objectid_1.default().toHexString(),
269
+ key: field.key,
270
+ name: field.name,
271
+ type: field.type,
272
+ sort_order: field.sortOrder,
273
+ created_at: new Date(),
274
+ });
275
+ }
276
+ /** `read` for a caller that is deciding rather than serving: absent is an answer. */
277
+ async findByKey(key, { executor = this.knex } = {}) {
278
+ const row = await executor(TABLE).where('key', key).first();
279
+ return row ? zod_1.z.decode(codec_1.customFieldCodec, row) : null;
280
+ }
281
+ /** The same entry `add` writes. Only the caller knows its transaction committed. */
282
+ async recordCreated(context, fields) {
283
+ for (const field of fields) {
198
284
  await this.recordAction({
199
285
  context,
200
286
  verb: 'create',
@@ -202,7 +288,6 @@ class CustomFieldDefinitionsService {
202
288
  details: { primary_name: field.name, key: field.key },
203
289
  });
204
290
  }
205
- return created;
206
291
  }
207
292
  /**
208
293
  * The operational ceiling on how many definitions a site can hold. This is a
@@ -249,7 +334,8 @@ class CustomFieldDefinitionsService {
249
334
  * fresh rank, so a move is well-defined even where every rank is still the default.
250
335
  * Returns every definition, archived included, matching what the request named.
251
336
  */
252
- async reorder(context, input) {
337
+ async reorder(context, namespace, input) {
338
+ this.assertDefinable(namespace);
253
339
  const parsed = ReorderInput.safeParse(input);
254
340
  if (!parsed.success) {
255
341
  const issue = parsed.error.issues[0];
@@ -368,7 +454,10 @@ class CustomFieldDefinitionsService {
368
454
  });
369
455
  }
370
456
  }
371
- async edit(context, key, input) {
457
+ async edit(context, namespace, key, input) {
458
+ if (!this.isStored(namespace)) {
459
+ throw new errors_1.default.NotFoundError({ message: 'Custom field not found.' });
460
+ }
372
461
  const parsed = EditFieldInput.safeParse(input);
373
462
  if (!parsed.success) {
374
463
  throw new errors_1.default.ValidationError({
@@ -377,7 +466,7 @@ class CustomFieldDefinitionsService {
377
466
  });
378
467
  }
379
468
  const patch = parsed.data;
380
- const existing = await this.read(key);
469
+ const existing = await this.read(identity_1.CUSTOM_NAMESPACE, key);
381
470
  // Key and type are immutable after creation: values are addressed by key
382
471
  // and interpreted by type, so changing either would silently orphan or
383
472
  // corrupt stored values. Reject a differing value rather than ignore it.
@@ -431,7 +520,7 @@ class CustomFieldDefinitionsService {
431
520
  details: { primary_name: patch.name ?? existing.name, key },
432
521
  });
433
522
  }
434
- return this.read(key);
523
+ return this.read(identity_1.CUSTOM_NAMESPACE, key);
435
524
  }
436
525
  /**
437
526
  * Permanently delete a field and every member value attached to it (the FK
@@ -440,7 +529,10 @@ class CustomFieldDefinitionsService {
440
529
  * which makes accidental data loss a deliberate two-step. Archiving is the
441
530
  * reversible soft state (see `edit` with a status change).
442
531
  */
443
- async destroy(context, key) {
532
+ async destroy(context, namespace, key) {
533
+ if (!this.isStored(namespace)) {
534
+ throw new errors_1.default.NotFoundError({ message: 'Custom field not found.' });
535
+ }
444
536
  const field = await this.knex(TABLE).where('key', key).first();
445
537
  if (!field) {
446
538
  throw new errors_1.default.NotFoundError({ message: 'Custom field not found.' });
@@ -460,6 +552,36 @@ class CustomFieldDefinitionsService {
460
552
  }
461
553
  }
462
554
  exports.CustomFieldDefinitionsService = CustomFieldDefinitionsService;
555
+ /**
556
+ * The shape a stated key has to have. Minting derives one that is usable by
557
+ * construction, so this is the check that path never needed: a caller stating its own
558
+ * key has said nothing about the format, and guarding here covers every route in
559
+ * rather than whichever one arrived first.
560
+ *
561
+ * The length bound is the column's own, not `mintKey`'s: that one holds back room for a
562
+ * `_<n>` collision suffix, and a stated key is written exactly as given and never
563
+ * suffixed, so the whole column is available to it.
564
+ */
565
+ function assertKeyUsable(key) {
566
+ if (!key_1.KEY_CHARACTERS.test(key)) {
567
+ throw new errors_1.default.ValidationError({
568
+ message: 'A custom field key can only contain lowercase letters, numbers and underscores.',
569
+ property: 'key',
570
+ });
571
+ }
572
+ if (key.length > MAX_KEY_LENGTH) {
573
+ throw new errors_1.default.ValidationError({
574
+ message: `A custom field key can be at most ${MAX_KEY_LENGTH} characters.`,
575
+ property: 'key',
576
+ });
577
+ }
578
+ if (RESERVED_KEYS.includes(key)) {
579
+ throw new errors_1.default.ValidationError({
580
+ message: `${key} cannot be used as a custom field key.`,
581
+ property: 'key',
582
+ });
583
+ }
584
+ }
463
585
  // The field a zod issue points at. Create validates an array, so an issue's path
464
586
  // is prefixed with the item's index (`[0, 'name']`); `property` names the field
465
587
  // that is wrong, so the numeric prefix is dropped. Which item it was is reported
@@ -490,6 +612,24 @@ function applyFilter(query, filter) {
490
612
  let mongoQuery;
491
613
  try {
492
614
  mongoQuery = nql(filter).toJSON();
615
+ }
616
+ catch (err) {
617
+ throw new errors_1.default.BadRequestError({
618
+ message: 'Could not parse the filter parameter.',
619
+ property: 'filter',
620
+ err: err,
621
+ });
622
+ }
623
+ // The table has no `namespace` column, so mongo-knex would compile this into SQL against a
624
+ // column that does not exist. Refuse it at the edge, naming the URL as the way to pick a
625
+ // namespace, rather than surface a database error.
626
+ if (filterReferencesAttribute(mongoQuery, 'namespace')) {
627
+ throw new errors_1.default.BadRequestError({
628
+ message: 'Filtering on namespace is not supported. Scope by the namespace route instead.',
629
+ property: 'filter',
630
+ });
631
+ }
632
+ try {
493
633
  knexify(query, mongoQuery, { tableName: TABLE });
494
634
  }
495
635
  catch (err) {
@@ -508,12 +648,15 @@ function applyFilter(query, filter) {
508
648
  // $and/$or/$nor combinators — so a status filter at any nesting counts as the
509
649
  // caller opting in to (or out of) archived fields.
510
650
  function filterReferencesStatus(query) {
651
+ return filterReferencesAttribute(query, 'status');
652
+ }
653
+ function filterReferencesAttribute(query, attribute) {
511
654
  return Object.entries(query).some(([key, value]) => {
512
- if (key === 'status') {
655
+ if (key === attribute) {
513
656
  return true;
514
657
  }
515
658
  if ((key === '$and' || key === '$or' || key === '$nor') && Array.isArray(value)) {
516
- return value.some((sub) => filterReferencesStatus(sub));
659
+ return value.some((sub) => filterReferencesAttribute(sub, attribute));
517
660
  }
518
661
  return false;
519
662
  });
@@ -5,34 +5,27 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.CUSTOM_FIELDS_RELATION = void 0;
7
7
  exports.createCustomFieldsFilterTransformer = createCustomFieldsFilterTransformer;
8
- // The members filter reaches custom field values through a `custom_fields` relation
9
- // on the Member model (the values table joined on member_id). A field is named by its
10
- // `key` the stable public slug and matched on its `value`; a composite field's
11
- // part is named as `value.<part>`, and a part's presence as `path.<part>`:
8
+ // Translates the metafield clauses of a members filter into a query over the
9
+ // values table. A metafield is a publisher-defined field on a member; it is
10
+ // addressed as `namespace.key`, and a composite value such as an address names
11
+ // one of its parts by appending it: `namespace.key.part`.
12
12
  //
13
- // (custom_fields.key:'company'+custom_fields.value:'Ghost') // value
14
- // (custom_fields.key:'shipping_address'+custom_fields.value.country:'GB')
15
- // custom_fields.key:'phone' / custom_fields.key:-'phone' // field set / not set
16
- // (custom_fields.key:'shipping_address'+custom_fields.path:'country') // part set
17
- // (custom_fields.key:'shipping_address'+custom_fields.path:-'country') // part not set
13
+ // (metafields.key:'custom.company'+metafields.value:'Ghost')
14
+ // (metafields.key:'custom.shipping_address.country'+metafields.value:'GB')
15
+ // metafields.key:'custom.company' // has a value
16
+ // metafields.key:-'custom.shipping_address.country' // that part is not set
18
17
  //
19
- // The values table stores one row per leaf: `custom_field_key` (the field's stable key),
20
- // `path` (empty for a scalar, the part's key for a composite), and `value_text`. A scalar
21
- // field is just a leaf at path ''; "the field", "a part", and "the whole field" are the
22
- // same thing a set of leaf rows pinned by (custom_field_key, [path], [value]). So every
23
- // filter is one `$elemMatch` over those columns: positive asserts a matching leaf exists,
24
- // `$not` asserts none does. The public grammar is the storage vocabulary here — the key
25
- // sits on the row — so the transformer emits it straight, with no id lookup and no field
26
- // catalog read. That is what lets the Member model chain it inline on every members query,
27
- // rather than each call site remembering to wire it. A key that names no current field
28
- // matches no leaf; the admin never offers an archived or deleted field in the filter UI,
29
- // though the API leaves them queryable.
18
+ // The values table holds one row per stored value: `custom_field_key`, `path`
19
+ // (empty for a plain field, the part's name for one part of a composite) and
20
+ // `value_text`. A filter addresses those columns directly, so each one becomes a
21
+ // single $elemMatch over them positive asserts a matching row exists, $not
22
+ // asserts none does with no lookup against the field definitions.
30
23
  const errors_1 = __importDefault(require("@tryghost/errors"));
31
- const RELATION = 'custom_fields';
24
+ const identity_1 = require("@tryghost/custom-field-types/identity");
25
+ const RELATION = 'metafields';
32
26
  const PREFIX = `${RELATION}.`;
33
27
  const KEY_ATTRIBUTE = `${PREFIX}key`;
34
28
  const VALUE_ATTRIBUTE = `${PREFIX}value`;
35
- const PATH_ATTRIBUTE = `${PREFIX}path`;
36
29
  const ROOT_PATH = '';
37
30
  // Only recurse into plain objects/arrays; a RegExp, Date, etc. is a leaf value that
38
31
  // must pass through by reference (recursing would strip a regex to `{}`).
@@ -42,8 +35,7 @@ function isPlainObject(value) {
42
35
  !Array.isArray(value) &&
43
36
  (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null));
44
37
  }
45
- // A single-clause object whose one key targets the custom_fields relation.
46
- function isCustomFieldClause(node) {
38
+ function isMetafieldClause(node) {
47
39
  if (!isPlainObject(node)) {
48
40
  return false;
49
41
  }
@@ -57,100 +49,94 @@ function isCustomFieldClause(node) {
57
49
  // `key:'a'+key:'b'`, since a bare key clause needs no group of its own. That is two
58
50
  // filters — a member with a value for both — not one leaf that is somehow both fields, so
59
51
  // it has to fall through and be transformed a clause at a time.
60
- function isCustomFieldCompound(clauses) {
61
- if (!Array.isArray(clauses) || clauses.length < 2 || !clauses.every(isCustomFieldClause)) {
52
+ function isMetafieldCompound(clauses) {
53
+ if (!Array.isArray(clauses) || clauses.length < 2 || !clauses.every(isMetafieldClause)) {
62
54
  return false;
63
55
  }
64
56
  return clauses.filter((clause) => Object.keys(clause)[0] === KEY_ATTRIBUTE).length === 1;
65
57
  }
66
- // `custom_fields.value` addresses the scalar (root) leaf; `custom_fields.value.<part>`
67
- // a composite's part. Returns the row `path` the attribute selects, or null if the
68
- // attribute isn't a value clause.
69
- function pathForValueAttribute(attribute) {
70
- if (attribute === VALUE_ATTRIBUTE) {
71
- return ROOT_PATH;
72
- }
73
- if (attribute.startsWith(`${VALUE_ATTRIBUTE}.`)) {
74
- return attribute.slice(`${VALUE_ATTRIBUTE}.`.length);
75
- }
76
- return null;
77
- }
78
- // The single `{$ne}` shape all negations arrive as (is-not-set on a key or part).
58
+ // NQL compiles every negation in this grammar to `{$ne: <string>}`, so a negated clause
59
+ // can arrive in no other shape.
79
60
  function negatedString(value) {
80
61
  return isPlainObject(value) && typeof value.$ne === 'string' ? value.$ne : null;
81
62
  }
63
+ const ANY_PATH = null;
64
+ // A stored key never contains a dot, so a full dotted identity is a key no stored
65
+ // row can equal.
66
+ function unmatchableLeaf(identity) {
67
+ return { key: (0, identity_1.formatIdentity)(identity), path: identity.partPath };
68
+ }
69
+ function parseLeafAddress(raw) {
70
+ const identity = typeof raw === 'string' ? (0, identity_1.parseIdentity)(raw) : null;
71
+ if (!identity) {
72
+ throw new errors_1.default.BadRequestError({
73
+ message: `A metafield filter names a field as namespace.key, for example (${KEY_ATTRIBUTE}:'custom.company'+${VALUE_ATTRIBUTE}:'Ghost').`,
74
+ });
75
+ }
76
+ if (identity.namespace !== identity_1.CUSTOM_NAMESPACE) {
77
+ return unmatchableLeaf(identity);
78
+ }
79
+ return { key: identity.key, path: identity.partPath };
80
+ }
82
81
  function createCustomFieldsFilterTransformer() {
83
- // The grammar carries the field key in the clause value; take it as a string,
84
- // defaulting to one no field can hold so a malformed clause matches nothing.
85
- const keyOf = (value) => (typeof value === 'string' ? value : '');
86
82
  // One (maybe-negated) $elemMatch over the leaf columns: positive is "a leaf pinned
87
83
  // by these matches", `$not` is "no leaf does".
88
- function buildElemMatch(fieldKey, conditions, negate) {
89
- const match = { custom_field_key: fieldKey, ...conditions };
84
+ function buildElemMatch(target, value, negate) {
85
+ const match = { custom_field_key: target.key };
86
+ if (value !== undefined) {
87
+ match.path = target.path ?? ROOT_PATH;
88
+ match.value_text = value;
89
+ }
90
+ else if (target.path !== ANY_PATH) {
91
+ match.path = target.path;
92
+ }
90
93
  if (negate) {
91
94
  return { [RELATION]: { $not: { $elemMatch: match } } };
92
95
  }
93
96
  return { [RELATION]: { $elemMatch: match } };
94
97
  }
95
- // A `(key + value[.part] | path)` compound. A `value` clause pins the part and its
96
- // value; a `path` clause pins the part's presence (no value), and a negated path is
97
- // is-not-set — no such leaf — which negates the whole match.
98
98
  function toElemMatch(clauses) {
99
- let fieldKey = '';
100
- let hasKey = false;
101
- let hasLeaf = false;
102
- const conditions = {};
103
- let negate = false;
104
- const oneLeafOnly = () => {
105
- if (hasLeaf) {
106
- throw new errors_1.default.BadRequestError({
107
- message: 'A custom field filter takes one value or path clause.',
108
- });
109
- }
110
- hasLeaf = true;
111
- };
99
+ let target;
100
+ let value;
101
+ let hasValue = false;
112
102
  for (const clause of clauses) {
113
- const [attribute, value] = Object.entries(clause)[0];
103
+ const [attribute, clauseValue] = Object.entries(clause)[0];
114
104
  if (attribute === KEY_ATTRIBUTE) {
115
- if (hasKey) {
105
+ if (negatedString(clauseValue) !== null) {
116
106
  throw new errors_1.default.BadRequestError({
117
- message: `A custom field filter takes one "${KEY_ATTRIBUTE}" clause.`,
107
+ message: `A negated "${KEY_ATTRIBUTE}" stands alone; it cannot be grouped with a value.`,
118
108
  });
119
109
  }
120
- hasKey = true;
121
- fieldKey = keyOf(value);
122
- }
123
- else if (attribute === PATH_ATTRIBUTE) {
124
- oneLeafOnly();
125
- const negatedPath = negatedString(value);
126
- conditions.path = negatedPath ?? value;
127
- negate = negatedPath !== null;
110
+ target = parseLeafAddress(clauseValue);
128
111
  }
129
- else {
130
- const path = pathForValueAttribute(attribute);
131
- if (path === null) {
132
- // Not the key, a value/value.<part>, or a path: it names no leaf
133
- // column, so dropping it would silently widen the match to the key
134
- // alone. Fail closed instead.
112
+ else if (attribute === VALUE_ATTRIBUTE) {
113
+ if (hasValue) {
135
114
  throw new errors_1.default.BadRequestError({
136
- message: `Unsupported custom field filter clause "${attribute}".`,
115
+ message: `A metafield filter takes one "${VALUE_ATTRIBUTE}" clause.`,
137
116
  });
138
117
  }
139
- oneLeafOnly();
140
- conditions.path = path;
141
- conditions.value_text = value;
118
+ hasValue = true;
119
+ value = clauseValue;
142
120
  }
121
+ else {
122
+ throw new errors_1.default.BadRequestError({
123
+ message: `Unsupported metafield filter clause "${attribute}".`,
124
+ });
125
+ }
126
+ }
127
+ if (!target) {
128
+ throw new errors_1.default.BadRequestError({
129
+ message: `A metafield filter needs a "${KEY_ATTRIBUTE}" clause.`,
130
+ });
143
131
  }
144
- return buildElemMatch(fieldKey, conditions, negate);
132
+ return buildElemMatch(target, hasValue ? value : undefined, false);
145
133
  }
146
- // A standalone `key` clause is whole-field is-set / is-not-set: a leaf for this
147
- // field exists (any part), or none does.
148
134
  function fromKeyClause(value) {
149
135
  const negatedKey = negatedString(value);
150
136
  if (negatedKey !== null) {
151
- return buildElemMatch(keyOf(negatedKey), {}, true);
137
+ return buildElemMatch(parseLeafAddress(negatedKey), undefined, true);
152
138
  }
153
- return buildElemMatch(keyOf(value), {}, false);
139
+ return buildElemMatch(parseLeafAddress(value), undefined, false);
154
140
  }
155
141
  function transform(node) {
156
142
  if (Array.isArray(node)) {
@@ -159,7 +145,7 @@ function createCustomFieldsFilterTransformer() {
159
145
  if (!isPlainObject(node)) {
160
146
  return node;
161
147
  }
162
- if (isCustomFieldCompound(node.$and)) {
148
+ if (isMetafieldCompound(node.$and)) {
163
149
  return toElemMatch(node.$and);
164
150
  }
165
151
  const out = {};
@@ -168,12 +154,8 @@ function createCustomFieldsFilterTransformer() {
168
154
  Object.assign(out, fromKeyClause(value));
169
155
  }
170
156
  else if (key.startsWith(PREFIX)) {
171
- // A value or path clause only means something paired with its key in one
172
- // leaf, which arrives as the compound handled above. A `custom_fields.*`
173
- // clause reaching here is unpaired (or its pair was not grouped), so fail
174
- // closed rather than pass it to mongo-knex as a column that does not exist.
175
157
  throw new errors_1.default.BadRequestError({
176
- message: `A "${key}" filter must be grouped with its "${KEY_ATTRIBUTE}" clause, for example (${KEY_ATTRIBUTE}:'a_field'+${VALUE_ATTRIBUTE}:'a value').`,
158
+ message: `A "${key}" filter must be grouped with its "${KEY_ATTRIBUTE}" clause, for example (${KEY_ATTRIBUTE}:'custom.a_field'+${VALUE_ATTRIBUTE}:'a value').`,
177
159
  });
178
160
  }
179
161
  else {
@@ -189,7 +171,7 @@ function createCustomFieldsFilterTransformer() {
189
171
  * rows (one per field, or per part of a composite field), and a predicate asks whether
190
172
  * one of them matches, so this joins the values table on member_id and mongo-knex emits
191
173
  * it as a correlated `members.id IN (…)` subquery — composing with every other member
192
- * filter. Registered only behind the `membersCustomFields` flag.
174
+ * filter.
193
175
  */
194
176
  exports.CUSTOM_FIELDS_RELATION = {
195
177
  tableName: 'members_custom_field_values',
@@ -1,15 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.values = exports.definitions = void 0;
3
+ exports.bindings = exports.values = exports.definitions = exports.actingContext = void 0;
4
4
  exports.init = init;
5
5
  const definitions_service_1 = require("./definitions-service");
6
6
  const values_service_1 = require("./values-service");
7
+ const bindings_service_1 = require("./bindings-service");
7
8
  const actions_1 = require("./actions");
8
9
  const config_1 = require("./config");
10
+ var actions_2 = require("./actions");
11
+ Object.defineProperty(exports, "actingContext", { enumerable: true, get: function () { return actions_2.actingContext; } });
9
12
  function init() {
10
- // The two are constructed together below, so checking both keeps the "both or
11
- // neither" invariant explicit rather than trusting one to stand in for the pair.
12
- if (exports.definitions && exports.values) {
13
+ // The three are constructed together below, so checking all of them keeps the "all or
14
+ // none" invariant explicit rather than trusting one to stand in for the rest.
15
+ if (exports.definitions && exports.values && exports.bindings) {
13
16
  return;
14
17
  }
15
18
  const { knex } = require('../../data/db');
@@ -31,4 +34,8 @@ function init() {
31
34
  knex,
32
35
  getMaxDefinitions: () => (0, config_1.resolveMaxDefinitions)(config.get('members:customFields:maxDefinitions')),
33
36
  });
37
+ // Built after the values, which is what a binding routes into. It has no handle on the
38
+ // definitions: making a field is not part of binding to one, so a caller that needs both
39
+ // asks for both.
40
+ exports.bindings = new bindings_service_1.CustomFieldBindingsService({ knex, values: exports.values });
34
41
  }
@@ -2,11 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KEY_CHARACTERS = void 0;
4
4
  exports.mintableKey = mintableKey;
5
+ const identity_1 = require("@tryghost/custom-field-types/identity");
5
6
  // Neither package ships types.
6
7
  const { stripInvisibleChars } = require('@tryghost/string');
7
8
  const unidecode = require('unidecode');
8
9
  /** Every character a key may contain. Nothing else survives minting. */
9
- exports.KEY_CHARACTERS = /^[a-z0-9_]+$/;
10
+ exports.KEY_CHARACTERS = identity_1.IDENTITY_SEGMENT;
10
11
  /**
11
12
  * Mint the key a field is addressed by, from the name a publisher chose.
12
13
  *
@@ -4,9 +4,9 @@ exports.CustomField = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const custom_field_types_1 = require("@tryghost/custom-field-types");
6
6
  const schema_1 = require("./schema");
7
- // The domain shape of a field definition (camelCase; distinct from the DB row).
8
7
  exports.CustomField = zod_1.z.object({
9
8
  id: zod_1.z.string(),
9
+ namespace: zod_1.z.string(),
10
10
  key: zod_1.z.string(),
11
11
  name: zod_1.z.string(),
12
12
  type: custom_field_types_1.FieldTypeSchema,
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.activeFields = activeFields;
4
+ exports.fieldByKey = fieldByKey;
4
5
  exports.inFieldOrder = inFieldOrder;
5
6
  const schema_1 = require("./schema");
6
7
  const FIELDS_TABLE = 'members_custom_fields';
@@ -11,6 +12,10 @@ const FIELDS_TABLE = 'members_custom_fields';
11
12
  function activeFields(db) {
12
13
  return db(FIELDS_TABLE).where('status', schema_1.FIELD_STATUS.active);
13
14
  }
15
+ /** One field by key, as the same kind of query the rest of this reads through. */
16
+ function fieldByKey(db, key) {
17
+ return db(FIELDS_TABLE).where(`${FIELDS_TABLE}.key`, key);
18
+ }
14
19
  /**
15
20
  * The publisher's order, applied to every read of the list. Here for the same reason the
16
21
  * status filter is: a read that forgets it comes back in whatever order the engine chose.