ghost 6.44.0 → 6.44.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/core/boot.js +9 -13
  2. package/core/bridge.js +6 -10
  3. package/core/built/admin/assets/{PolarAngleAxis-eFuSrR7S.js → PolarAngleAxis-DWP6qq-Z.js} +2 -2
  4. package/core/built/admin/assets/{_baseAssignValue-XVHfKew7.js → _baseAssignValue-N9f4o_XK.js} +1 -1
  5. package/core/built/admin/assets/{a-large-small-DOzPDzKp.js → a-large-small-BOAtcFiE.js} +1 -1
  6. package/core/built/admin/assets/{account-migration-Cx45M5qm.js → account-migration-CtZx_5ib.js} +1 -1
  7. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
  8. package/core/built/admin/assets/admin-x-settings/{code-editor-view-Bi8Orhyj.mjs → code-editor-view-BMovCjer.mjs} +2 -2
  9. package/core/built/admin/assets/admin-x-settings/{index-1_V9g9Pt.mjs → index-BT1n-g2b.mjs} +3 -3
  10. package/core/built/admin/assets/admin-x-settings/{index-e9saYQJa.mjs → index-BnowNBGd.mjs} +5 -5
  11. package/core/built/admin/assets/admin-x-settings/{index-CnvQi01U.mjs → index-CRpK6gF8.mjs} +3 -3
  12. package/core/built/admin/assets/admin-x-settings/{index-BmU6BOfy.mjs → index-CfXS3AZ8.mjs} +3 -3
  13. package/core/built/admin/assets/admin-x-settings/{index-EuMiBt6I.mjs → index-Cs8zML6u.mjs} +3 -3
  14. package/core/built/admin/assets/admin-x-settings/{index-B43MPc5u.mjs → index-DmpwO1w3.mjs} +2 -2
  15. package/core/built/admin/assets/admin-x-settings/{index-DOgG0XZa.mjs → index-DwFIdhcR.mjs} +3 -3
  16. package/core/built/admin/assets/admin-x-settings/{index-CWdxMIcZ.mjs → index-sNyPiIJA.mjs} +2 -2
  17. package/core/built/admin/assets/admin-x-settings/{index-Ojg_pBvq.mjs → index-xTILut54.mjs} +1259 -1259
  18. package/core/built/admin/assets/admin-x-settings/{modals-Ckbx9UQF.mjs → modals-Cm5t13os.mjs} +9 -9
  19. package/core/built/admin/assets/{arrow-right-DckEQtp9.js → arrow-right-BxdHN_En.js} +1 -1
  20. package/core/built/admin/assets/{at-sign-BChf68aS.js → at-sign-Cajf-dOf.js} +1 -1
  21. package/core/built/admin/assets/{audience-BYU5Bd7D.js → audience-CHNb9mCT.js} +1 -1
  22. package/core/built/admin/assets/{automated-email-design-Z26Qit0_.js → automated-email-design-B9C9mKUS.js} +2 -2
  23. package/core/built/admin/assets/{automated-emails-aBicreLv.js → automated-emails-DhLhkq8L.js} +1 -1
  24. package/core/built/admin/assets/{automations-vpH-d72N.js → automations-6-cgZovk.js} +1 -1
  25. package/core/built/admin/assets/{automations-Ddo22yPk.js → automations-CxJ-N_YK.js} +1 -1
  26. package/core/built/admin/assets/{avatar-flipboard-CoI3LQ-o.js → avatar-flipboard-f5nSMmek.js} +1 -1
  27. package/core/built/admin/assets/{bluesky-sharing-iVDkCMKN.js → bluesky-sharing-Dm2k8qZH.js} +1 -1
  28. package/core/built/admin/assets/{chart-CjM1VQqH.js → chart-BbJnjWey.js} +23 -23
  29. package/core/built/admin/assets/{checkbox-CC5kd_Kf.js → checkbox-CT7DNFAI.js} +1 -1
  30. package/core/built/admin/assets/{chevron-up-D__wzyXT.js → chevron-up-BxijmTT9.js} +1 -1
  31. package/core/built/admin/assets/{chunk.696.79505fa556d329d6e824.js → chunk.167.37bb0d7f6ac67fefa679.js} +3184 -3523
  32. package/core/built/admin/assets/{chunk.696.79505fa556d329d6e824.js.LICENSE.txt → chunk.167.37bb0d7f6ac67fefa679.js.LICENSE.txt} +1 -1
  33. package/core/built/admin/assets/{chunk.524.1ff649a6cc62dc38f538.js → chunk.524.ed812b62217210a913d4.js} +5 -5
  34. package/core/built/admin/assets/{chunk.582.408a40e0095e479e3d11.js → chunk.582.7db5c9f17bca5a484049.js} +6 -6
  35. package/core/built/admin/assets/{chunk.945.835812fcb16029abd7f9.js → chunk.834.f9db7c62b9457db93e8c.js} +3 -3
  36. package/core/built/admin/assets/{code-editor-view-C735d5EA.js → code-editor-view-D8slE0IA.js} +1 -1
  37. package/core/built/admin/assets/{comments-DgFPax3Y.js → comments-BvfR-fA8.js} +1 -1
  38. package/core/built/admin/assets/{content-helpers-DjugSfvm.js → content-helpers-DUWGLvgL.js} +1 -1
  39. package/core/built/admin/assets/{copy-D9JONGcK.js → copy-ht18t5gu.js} +1 -1
  40. package/core/built/admin/assets/{data-list-QWLEHG4y.js → data-list-Daha2dIs.js} +1 -1
  41. package/core/built/admin/assets/{deleted-feed-item-CwSCflTW.js → deleted-feed-item-ChP5bwgi.js} +1 -1
  42. package/core/built/admin/assets/{dropzone-lK_4vpWS.js → dropzone-h_qyk514.js} +1 -1
  43. package/core/built/admin/assets/{edit-profile-BmioJZHv.js → edit-profile-BT8RQpAG.js} +1 -1
  44. package/core/built/admin/assets/editor-s1mqd9_o.js +8 -0
  45. package/core/built/admin/assets/{empty-indicator-Dg64lT_g.js → empty-indicator-BIUvv6JE.js} +1 -1
  46. package/core/built/admin/assets/{en-DCwBNJ98.js → en-BEcDl42w.js} +1 -1
  47. package/core/built/admin/assets/{eye-psLxWnjO.js → eye-TPnIdamu.js} +1 -1
  48. package/core/built/admin/assets/{feed-TUcCSZHL.js → feed-BK3qvLWi.js} +1 -1
  49. package/core/built/admin/assets/{field-Uk8NOvtc.js → field-CSFsrvVy.js} +1 -1
  50. package/core/built/admin/assets/{filter-query-core-CbH_VKcG.js → filter-query-core-R8cQCO2w.js} +1 -1
  51. package/core/built/admin/assets/{filters-DmawZ5Uu.js → filters-Cp1voB1e.js} +1 -1
  52. package/core/built/admin/assets/gh-chart-BFsyOJD_.js +1 -0
  53. package/core/built/admin/assets/{ghost-2baa7ddc3630015f41c0efd13bd586b8.js → ghost-1881362e8a92dae1e3333a0484c173a3.js} +7 -6
  54. package/core/built/admin/assets/{growth-BAl-TyMy.js → growth-DIigOxF1.js} +1 -1
  55. package/core/built/admin/assets/{hash-Q1ou0OMZ.js → hash-B3iFVeCl.js} +1 -1
  56. package/core/built/admin/assets/{inbox-BAOtR6tN.js → inbox-2bPMLikt.js} +1 -1
  57. package/core/built/admin/assets/{index-BnJp51QE.js → index-52qUC8yv.js} +5 -5
  58. package/core/built/admin/assets/{index-BDIwushG.js → index-9j01Aj_X.js} +1 -1
  59. package/core/built/admin/assets/index-B9UVpmey.js +1 -0
  60. package/core/built/admin/assets/{index-DTeOW3Se.js → index-BXc5OF6x.js} +1 -1
  61. package/core/built/admin/assets/{index-Duc21aJ7.js → index-BaGL3IFe.js} +1 -1
  62. package/core/built/admin/assets/{index-DKC8uXHk.js → index-BdozZbt2.js} +2 -2
  63. package/core/built/admin/assets/{index-z951kI4K.js → index-Bgpz7M7i.js} +1 -1
  64. package/core/built/admin/assets/{index-BfSvX9is.js → index-Bp4O7TVf.js} +1 -1
  65. package/core/built/admin/assets/{index-C5S08C6P.js → index-BuqUmpe9.js} +1 -1
  66. package/core/built/admin/assets/{index-D7OtVnbo.js → index-CEF_Ofn0.js} +1 -1
  67. package/core/built/admin/assets/{index-Bs6rUYFS.js → index-CgSVv5Up.js} +58 -58
  68. package/core/built/admin/assets/{index-Ro9qXetv.js → index-Cr6x2czK.js} +1 -1
  69. package/core/built/admin/assets/{index-DJbgQjV6.js → index-CrbZj3Ju.js} +1 -1
  70. package/core/built/admin/assets/{index-BTBcWw_z.js → index-CrlAPDdD.js} +2 -2
  71. package/core/built/admin/assets/{index-B6SD2Qs-.js → index-CtrmMGWT.js} +1 -1
  72. package/core/built/admin/assets/{index-Bw0oyOFd.js → index-D-V_DAdt.js} +1 -1
  73. package/core/built/admin/assets/{index-DAJduTBl.js → index-D0poOSjw.js} +4 -4
  74. package/core/built/admin/assets/{index-CGT3W8fi.js → index-DWVGCPEC.js} +3 -3
  75. package/core/built/admin/assets/index-Dozbr_4s.css +1 -0
  76. package/core/built/admin/assets/{index-FyplpnYQ.js → index-S3Ai3lDy.js} +1 -1
  77. package/core/built/admin/assets/{index-DLr8D_Ks.js → index-VDhA5kgU.js} +1 -1
  78. package/core/built/admin/assets/{index-DRA1fORQ.js → index-fnIFqFMt.js} +1 -1
  79. package/core/built/admin/assets/{index-BhWQZObu.js → index-kA3UcdW7.js} +1 -1
  80. package/core/built/admin/assets/{input-group-DH32syTz.js → input-group-Fdl0S6pw.js} +1 -1
  81. package/core/built/admin/assets/{koenig-lexical-CxVcbgsH.js → koenig-lexical-REiYtzGD.js} +1 -1
  82. package/core/built/admin/assets/{kpi-card-DBWHDi3l.js → kpi-card-BS8vEj4c.js} +1 -1
  83. package/core/built/admin/assets/{kpi-card-BYhzWV5F.js → kpi-card-D3HEfOCP.js} +1 -1
  84. package/core/built/admin/assets/{kpi-tabs-Kxc-utyg.js → kpi-tabs-8vaI7yWq.js} +1 -1
  85. package/core/built/admin/assets/kpis-CkD0Xteg.js +1 -0
  86. package/core/built/admin/assets/label-DT38Gd2T.js +1 -0
  87. package/core/built/admin/assets/{links-Cg8O8ZGP.js → links-BK-azjCL.js} +1 -1
  88. package/core/built/admin/assets/{list-page-D2KCBsiy.js → list-page-DVpac3lc.js} +1 -1
  89. package/core/built/admin/assets/{loader-circle-nmLqhMZL.js → loader-circle-CIq5Cv-9.js} +1 -1
  90. package/core/built/admin/assets/{mail-D7cxYMd5.js → mail-gdeIU06V.js} +1 -1
  91. package/core/built/admin/assets/{main-layout-BXdUkeCo.js → main-layout-B2t-RiNE.js} +1 -1
  92. package/core/built/admin/assets/members-7z-tESWd.js +10 -0
  93. package/core/built/admin/assets/minus-BbGfVkll.js +1 -0
  94. package/core/built/admin/assets/{modals-VRvX8mK0.js → modals-DVIfijEz.js} +3 -3
  95. package/core/built/admin/assets/{moderation-DkT8qVJs.js → moderation-DwzDOhih.js} +1 -1
  96. package/core/built/admin/assets/{newsletter-sUT4ciRJ.js → newsletter-D1N8bUfp.js} +1 -1
  97. package/core/built/admin/assets/{newsletters-BNvAF2Oi.js → newsletters-7GSeQdQe.js} +1 -1
  98. package/core/built/admin/assets/{note-B0JhJv7s.js → note-Bn1-ZWDE.js} +1 -1
  99. package/core/built/admin/assets/offers-BsuLKYy9.js +1 -0
  100. package/core/built/admin/assets/{onboarding-route-IrO_SCb3.js → onboarding-route-C73r9kyO.js} +1 -1
  101. package/core/built/admin/assets/{overview-C2nk_B6i.js → overview-6U5p38zx.js} +1 -1
  102. package/core/built/admin/assets/{pagemenu-BGhty1iq.js → pagemenu-DrueoEX9.js} +1 -1
  103. package/core/built/admin/assets/{pencil-DiA3J4m5.js → pencil-Csnv8udS.js} +1 -1
  104. package/core/built/admin/assets/{pin-off-HWos3YHh.js → pin-off-BDeHGsSQ.js} +1 -1
  105. package/core/built/admin/assets/{post-analytics-context-BJwP-XAB.js → post-analytics-context-jKeUh2Ka.js} +1 -1
  106. package/core/built/admin/assets/{post-analytics-header-CZF5QmAD.js → post-analytics-header-C5QLn6Lm.js} +1 -1
  107. package/core/built/admin/assets/{post-analytics-DBUHbt1b.js → post-analytics-ngMqqbt9.js} +1 -1
  108. package/core/built/admin/assets/{post-share-modal-Bb8R05dH.js → post-share-modal-04B7JT4R.js} +1 -1
  109. package/core/built/admin/assets/posts/{alert-dialog-mmQvJUHQ.mjs → alert-dialog-CYvHtOQ5.mjs} +4 -4
  110. package/core/built/admin/assets/posts/{app-utils-CD0bhOLs.mjs → app-utils-Cs-8qje1.mjs} +3 -3
  111. package/core/built/admin/assets/posts/{automations-BL0WaXHA.mjs → automations-DE2rwx-Z.mjs} +25 -25
  112. package/core/built/admin/assets/posts/{automations-BmGKngyG.mjs → automations-Dq0_cprS.mjs} +4 -4
  113. package/core/built/admin/assets/posts/{avatar-CeJ0Zq_A.mjs → avatar-Dt_bGEgl.mjs} +7 -7
  114. package/core/built/admin/assets/posts/{button-CQCNdxY6.mjs → button-DO7JrPsL.mjs} +2 -2
  115. package/core/built/admin/assets/posts/{chevron-up-DaikhWC2.mjs → chevron-up-B8WyJjCr.mjs} +3 -3
  116. package/core/built/admin/assets/posts/{comments-DUSJ25vf.mjs → comments-B3-RpqJU.mjs} +1933 -1905
  117. package/core/built/admin/assets/posts/{data-list-BiiYXQes.mjs → data-list-BiZFP6Co.mjs} +5 -5
  118. package/core/built/admin/assets/posts/{dialog-DRN6olky.mjs → dialog-Bvl2PD8s.mjs} +3 -3
  119. package/core/built/admin/assets/posts/{editor-DN_Zz0m2.mjs → editor-BCtdAD74.mjs} +3230 -3130
  120. package/core/built/admin/assets/posts/{ellipsis-BIs4PL5-.mjs → ellipsis-B5H7Hudx.mjs} +2 -2
  121. package/core/built/admin/assets/posts/{empty-indicator-Wiukrx4T.mjs → empty-indicator-BRoAf6Jv.mjs} +2 -2
  122. package/core/built/admin/assets/posts/{filters-CJl5ebXd.mjs → filters-CffYQY_8.mjs} +12 -12
  123. package/core/built/admin/assets/posts/{get-site-timezone-JepxGwVr.mjs → get-site-timezone-CSrPVUfX.mjs} +2 -2
  124. package/core/built/admin/assets/posts/{growth-cNlqbz-x.mjs → growth--LVGRPtS.mjs} +13 -13
  125. package/core/built/admin/assets/posts/{heading-C_V6Ckxx.mjs → heading-BRQEP7IA.mjs} +2 -2
  126. package/core/built/admin/assets/posts/{heart-BMBWsLs2.mjs → heart-CC2f1qWb.mjs} +2 -2
  127. package/core/built/admin/assets/posts/{hooks-D4pJ_p_P.mjs → hooks-J2WpDj1d.mjs} +2 -2
  128. package/core/built/admin/assets/posts/{index-DzWTmJMy.mjs → index-D-vhKzUJ.mjs} +3 -3
  129. package/core/built/admin/assets/posts/{index-CDwlVw9E.mjs → index-LnPyGojr.mjs} +2 -2
  130. package/core/built/admin/assets/posts/{index-BuIZkUhD.mjs → index-nxfIbREV.mjs} +11 -11
  131. package/core/built/admin/assets/posts/{input-DUvSHOFz.mjs → input-DTtfruk_.mjs} +3 -3
  132. package/core/built/admin/assets/posts/{input-surface-CO8-j6V2.mjs → input-surface-DXTGCuo2.mjs} +2 -2
  133. package/core/built/admin/assets/posts/{koenig-lexical-cRm7bY-6.mjs → koenig-lexical-CNcifuT7.mjs} +2 -2
  134. package/core/built/admin/assets/posts/{kpis-BXukzbg6.mjs → kpis-n8L4BlMi.mjs} +5843 -5854
  135. package/core/built/admin/assets/posts/{label-TTt0QyDb.mjs → label-yl8B8ADj.mjs} +6 -6
  136. package/core/built/admin/assets/posts/{links-BAXELE82.mjs → links-DDApMJfk.mjs} +4 -4
  137. package/core/built/admin/assets/posts/{list-page-DQIiFvFP.mjs → list-page-BJJ9amzJ.mjs} +4 -4
  138. package/core/built/admin/assets/posts/{loading-indicator-BsY7eGY6.mjs → loading-indicator-CQBDNvdo.mjs} +3 -3
  139. package/core/built/admin/assets/posts/{mail-Bi1gt3MU.mjs → mail-BHg0SvNu.mjs} +2 -2
  140. package/core/built/admin/assets/posts/{main-layout-DejRuxP8.mjs → main-layout-DXveCJ5W.mjs} +2 -2
  141. package/core/built/admin/assets/posts/{newsletter-PCUtHW6Z.mjs → newsletter-DUpVmnKN.mjs} +19 -19
  142. package/core/built/admin/assets/posts/{overview-f4eDWazr.mjs → overview-sv6i-2h9.mjs} +18 -18
  143. package/core/built/admin/assets/posts/{pencil-DDnfXQqn.mjs → pencil-DsGDLJp6.mjs} +2 -2
  144. package/core/built/admin/assets/posts/{plus-DW3bG6ui.mjs → plus-DjZ_REvJ.mjs} +2 -2
  145. package/core/built/admin/assets/posts/{popover-Du2NcggM.mjs → popover-2PA9zdiD.mjs} +4 -4
  146. package/core/built/admin/assets/posts/{post-analytics-BmnHyWJq.mjs → post-analytics-CKHoiJFY.mjs} +6 -6
  147. package/core/built/admin/assets/posts/{post-analytics-context-0fieM91Y.mjs → post-analytics-context-D17CB7nm.mjs} +6 -6
  148. package/core/built/admin/assets/posts/{post-analytics-header-Bxi56AWV.mjs → post-analytics-header-aQLnrdWi.mjs} +12 -12
  149. package/core/built/admin/assets/posts/{post-share-modal-DiuT4GFO.mjs → post-share-modal-C8-8CZw8.mjs} +6 -6
  150. package/core/built/admin/assets/posts/{posts-CYjfxmgX.mjs → posts-BA2vu_T2.mjs} +2 -2
  151. package/core/built/admin/assets/posts/posts.js +1 -1
  152. package/core/built/admin/assets/posts/{search-Dz7F6ifc.mjs → search-Bx6BvCRk.mjs} +2 -2
  153. package/core/built/admin/assets/posts/{select-FX6QYAFU.mjs → select-QOJ2OvL0.mjs} +7 -7
  154. package/core/built/admin/assets/posts/{separator-C7QPCSjM.mjs → separator-CHtBa4OX.mjs} +3 -3
  155. package/core/built/admin/assets/posts/{settings-DJqx19W1.mjs → settings-CxhTCxXg.mjs} +2 -2
  156. package/core/built/admin/assets/posts/{sheet-wg_8-w45.mjs → sheet-DO5nhiru.mjs} +4 -4
  157. package/core/built/admin/assets/posts/{site-Bxv-L5Dn.mjs → site-6g4pKVnR.mjs} +2 -2
  158. package/core/built/admin/assets/posts/{skeleton-DrFcKwP3.mjs → skeleton-DgWx-52p.mjs} +3 -3
  159. package/core/built/admin/assets/posts/{source-icon-DD_bmIqg.mjs → source-icon-_zLbYeab.mjs} +4 -4
  160. package/core/built/admin/assets/posts/{stats-DkOg80Vt.mjs → stats-BGJFxx5f.mjs} +4 -4
  161. package/core/built/admin/assets/posts/{switch-DZFGAF_Y.mjs → switch-Djipx7gc.mjs} +5 -5
  162. package/core/built/admin/assets/posts/{table-CZCVviNy.mjs → table-DpDCE80J.mjs} +2 -2
  163. package/core/built/admin/assets/posts/{tabs-1fkhM4p8.mjs → tabs-CLWGiyQ4.mjs} +4 -4
  164. package/core/built/admin/assets/posts/{tags-CkyGqwqi.mjs → tags-B4KIn7er.mjs} +2 -2
  165. package/core/built/admin/assets/posts/{tags-BAmugIR1.mjs → tags-Dn1L_Z_4.mjs} +16 -16
  166. package/core/built/admin/assets/posts/{toggle-group-B_ubMZvo.mjs → toggle-group-DMMjXGB3.mjs} +4 -4
  167. package/core/built/admin/assets/posts/{tooltip-D1X0uVas.mjs → tooltip-DlCjZwmt.mjs} +5 -5
  168. package/core/built/admin/assets/posts/{underline-CV8OJ7qc.mjs → underline-WN3ZSFyI.mjs} +2 -2
  169. package/core/built/admin/assets/posts/{value-DhEK7_uT.mjs → value-BWiEmo45.mjs} +2 -2
  170. package/core/built/admin/assets/posts/{virtual-list-window-DqbQUkIo.mjs → virtual-list-window-F3Tvi9H5.mjs} +3 -3
  171. package/core/built/admin/assets/posts/{web-5jcYGeBz.mjs → web-C8PxkHvH.mjs} +18 -18
  172. package/core/built/admin/assets/posts/{x-Cl72IwQm.mjs → x-BdlZ3sG3.mjs} +2 -2
  173. package/core/built/admin/assets/posts/{zap-BPDyHF1u.mjs → zap-BuAk2C7n.mjs} +33 -21
  174. package/core/built/admin/assets/{posts-D6K0FKB9.js → posts-bRma2Aji.js} +1 -1
  175. package/core/built/admin/assets/power-B4VCYMPn.js +1 -0
  176. package/core/built/admin/assets/{referrers-C9UK_aF_.js → referrers-BgXy4ZDF.js} +1 -1
  177. package/core/built/admin/assets/{repeat-BwgUDBQn.js → repeat-BElPLZnh.js} +1 -1
  178. package/core/built/admin/assets/{reply-DbknkQhJ.js → reply-qlUFZ_G8.js} +1 -1
  179. package/core/built/admin/assets/{rocket-1EqU2MzS.js → rocket-CXYckFbm.js} +1 -1
  180. package/core/built/admin/assets/{select-BGJXWb8i.js → select-MGAxyhXP.js} +1 -1
  181. package/core/built/admin/assets/{send-_CqvCQBY.js → send-DcU5LcPI.js} +1 -1
  182. package/core/built/admin/assets/{settings-Bnep890F.js → settings-DGSAz4oa.js} +4 -4
  183. package/core/built/admin/assets/{settings-Ct244PHl.js → settings-DI95fXs5.js} +1 -1
  184. package/core/built/admin/assets/{share-modal-B8pN_3tk.js → share-modal-CsNAIPeN.js} +1 -1
  185. package/core/built/admin/assets/{sort-button-EXsVVSdw.js → sort-button-wV9PBd2P.js} +1 -1
  186. package/core/built/admin/assets/{source-icon-XMs0toB0.js → source-icon-CYt4X1-M.js} +1 -1
  187. package/core/built/admin/assets/{sprout-uA_pVmDr.js → sprout-5QqpAoO8.js} +1 -1
  188. package/core/built/admin/assets/{square-atKnWQyw.js → square-YHeYyKG9.js} +1 -1
  189. package/core/built/admin/assets/{stats-CPNnZGD9.js → stats-PD64opSw.js} +1 -1
  190. package/core/built/admin/assets/{stats-view-BP8LU5rf.js → stats-view-C4fnD3uG.js} +1 -1
  191. package/core/built/admin/assets/{step-1-FHeGC4GW.js → step-1-BqeM_LxC.js} +1 -1
  192. package/core/built/admin/assets/{step-2-BFhgD2N8.js → step-2-C7AcokE9.js} +1 -1
  193. package/core/built/admin/assets/{step-3-BZMKjY5n.js → step-3-9Z0qkfpM.js} +2 -2
  194. package/core/built/admin/assets/{table-kGA62bBZ.js → table-C8Mxw8fs.js} +1 -1
  195. package/core/built/admin/assets/{tabs-Dl3BXr4Z.js → tabs-2KXMNRG-.js} +1 -1
  196. package/core/built/admin/assets/{tags-CLhASxwZ.js → tags-Cx-Jx8Yb.js} +1 -1
  197. package/core/built/admin/assets/{tags-D-W0kJMe.js → tags-cn7wgGit.js} +1 -1
  198. package/core/built/admin/assets/{textarea-DMub6CsU.js → textarea-dKwIcAK5.js} +1 -1
  199. package/core/built/admin/assets/{thumbs-up-DZu4SX5N.js → thumbs-up-gzGza05L.js} +1 -1
  200. package/core/built/admin/assets/{tiers-2bg8BCFj.js → tiers-D--LgD9v.js} +1 -1
  201. package/core/built/admin/assets/{toggle-group-ByS0KZ3G.js → toggle-group-C8KbiBQS.js} +1 -1
  202. package/core/built/admin/assets/{topic-filter-oRqwlePZ.js → topic-filter--VQn4A7D.js} +1 -1
  203. package/core/built/admin/assets/{trash-BHoSD8mS.js → trash-CnBwlhjN.js} +1 -1
  204. package/core/built/admin/assets/{underline-1YSgOpPm.js → underline-B2FjHOE0.js} +1 -1
  205. package/core/built/admin/assets/{undo-2-BwYMlB8G.js → undo-2-fUPuO2Zt.js} +1 -1
  206. package/core/built/admin/assets/{upload-BDz0_o9b.js → upload-BPOrFdtp.js} +1 -1
  207. package/core/built/admin/assets/{use-growth-stats-PRVcCUT7.js → use-growth-stats-By0ZDqFi.js} +1 -1
  208. package/core/built/admin/assets/{use-pintura-config-CROsgSEd.js → use-pintura-config-0ykvCF7U.js} +1 -1
  209. package/core/built/admin/assets/{use-simple-pagination-CBjKBNNI.js → use-simple-pagination-TruuCHV6.js} +1 -1
  210. package/core/built/admin/assets/{user-round-check-DKooQiV7.js → user-round-check-C1Vahz0b.js} +1 -1
  211. package/core/built/admin/assets/{user-round-x-B94nAYJb.js → user-round-x-CzKFo5GY.js} +1 -1
  212. package/core/built/admin/assets/{virtual-list-window-Cf_vCQFb.js → virtual-list-window-BWEknk_A.js} +1 -1
  213. package/core/built/admin/assets/{wallet-cards-BorRQari.js → wallet-cards-CRHrrjiy.js} +1 -1
  214. package/core/built/admin/assets/{web-BVHy-80k.js → web-C2H0oAut.js} +1 -1
  215. package/core/built/admin/index.html +7 -7
  216. package/core/frontend/services/llms/service.js +37 -10
  217. package/core/frontend/services/routing/config.js +5 -3
  218. package/core/frontend/services/routing/config.ts +59 -0
  219. package/core/frontend/services/sitemap/handler.js +2 -14
  220. package/core/frontend/services/sitemap/site-map-manager.js +9 -214
  221. package/core/server/api/endpoints/comment-replies.js +1 -2
  222. package/core/server/api/endpoints/members.js +4 -1
  223. package/core/server/api/endpoints/search-index-public.js +12 -1
  224. package/core/server/api/endpoints/search-index.js +13 -2
  225. package/core/server/lib/bootstrap-socket.js +31 -36
  226. package/core/server/lib/bootstrap-socket.ts +114 -0
  227. package/core/server/lib/common/to-plain.js +9 -21
  228. package/core/server/lib/common/to-plain.ts +25 -0
  229. package/core/server/models/role-utils.js +5 -7
  230. package/core/server/models/role-utils.ts +56 -0
  231. package/core/server/services/adapter-manager/adapter-manager.js +5 -1
  232. package/core/server/services/audience-feedback/audience-feedback-service.js +1 -1
  233. package/core/server/services/automations/automations-api.js +37 -2
  234. package/core/server/services/automations/automations-api.ts +43 -2
  235. package/core/server/services/comments/comments-controller.js +87 -55
  236. package/core/server/services/comments/comments-service-emails.js +1 -1
  237. package/core/server/services/comments/comments-service.js +249 -79
  238. package/core/server/services/mail/templates/notification.html +157 -0
  239. package/core/server/services/member-attribution/url-translator.js +1 -1
  240. package/core/server/services/members/members-api/services/token-service.js +21 -2
  241. package/core/server/services/notifications/notification-email.js +38 -0
  242. package/core/server/services/notifications/notification-email.ts +66 -0
  243. package/core/server/services/notifications/sanitize-email-html.js +37 -0
  244. package/core/server/services/notifications/sanitize-email-html.ts +35 -0
  245. package/core/server/services/route-settings/route-settings.js +2 -18
  246. package/core/server/services/route-settings/validate.js +2 -1
  247. package/core/server/services/update-check/index.js +10 -3
  248. package/core/server/services/update-check/update-check-service.js +18 -25
  249. package/core/server/services/url/index.js +1 -16
  250. package/core/shared/config/defaults.json +0 -1
  251. package/package.json +2 -1
  252. package/pnpm-lock.yaml +11 -1
  253. package/pnpm-workspace.yaml +2 -0
  254. package/core/built/admin/assets/editor-G3BxjP48.js +0 -8
  255. package/core/built/admin/assets/gh-chart-vpcpdiS9.js +0 -1
  256. package/core/built/admin/assets/index-BP5ial3p.js +0 -1
  257. package/core/built/admin/assets/index-Bd9VZ28k.css +0 -1
  258. package/core/built/admin/assets/kpis-B8pv3wne.js +0 -1
  259. package/core/built/admin/assets/label-BjHICZC1.js +0 -1
  260. package/core/built/admin/assets/members-DcuuSs_v.js +0 -10
  261. package/core/built/admin/assets/minus-Dv51QEND.js +0 -1
  262. package/core/built/admin/assets/offers-DKZ0qsmb.js +0 -1
  263. package/core/built/admin/assets/power-D5QO6MjY.js +0 -1
  264. package/core/server/services/url/lazy-find-resource.js +0 -49
  265. package/core/server/services/url/lazy-find-resource.ts +0 -62
  266. package/core/server/services/url/lazy-url-service.js +0 -262
  267. package/core/server/services/url/lazy-url-service.ts +0 -308
@@ -1,262 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LazyUrlService = void 0;
4
- /* eslint-disable @typescript-eslint/no-require-imports */
5
- const nql = require('@tryghost/nql');
6
- const debug = require('@tryghost/debug')('services:url:lazy');
7
- const logging = require('@tryghost/logging');
8
- const errors = require('@tryghost/errors');
9
- const localUtils = require('../../../shared/url-utils');
10
- // The same expansions used by UrlGenerator so users can write `tag:foo`,
11
- // `author:jane`, etc. and have them rewritten to the underlying field paths.
12
- const EXPANSIONS = [
13
- { key: 'author', replacement: 'authors.slug' },
14
- { key: 'tags', replacement: 'tags.slug' },
15
- { key: 'tag', replacement: 'tags.slug' },
16
- { key: 'authors', replacement: 'authors.slug' },
17
- { key: 'primary_tag', replacement: 'primary_tag.slug' },
18
- { key: 'primary_author', replacement: 'primary_author.slug' }
19
- ];
20
- // Same `page:true/false` legacy transformer the UrlGenerator uses, so old
21
- // routes.yaml configs continue to work.
22
- const PAGE_TRANSFORMER = nql.utils.mapKeyValues({
23
- key: { from: 'page', to: 'type' },
24
- values: [
25
- { from: false, to: 'post' },
26
- { from: true, to: 'page' }
27
- ]
28
- });
29
- // Map a resource's `type` field (whatever the caller passed) onto the plural
30
- // router resource type. We accept the singular DB column values ('post',
31
- // 'page') as well as the plural router keys, because the migrated callers
32
- // are inconsistent: API responses spread `attrs` (singular), but some
33
- // helpers explicitly tag with the plural router key.
34
- const TYPE_TO_ROUTER_TYPE = {
35
- post: 'posts',
36
- posts: 'posts',
37
- page: 'pages',
38
- pages: 'pages',
39
- tag: 'tags',
40
- tags: 'tags',
41
- author: 'authors',
42
- authors: 'authors'
43
- };
44
- /**
45
- * On-demand URL service. Computes URLs and ownership per-call from the
46
- * registered router configs instead of holding a precomputed map of every
47
- * resource. Pure for forward lookups; resolveUrl() takes an optional DB
48
- * lookup function so reverse lookups stay testable.
49
- */
50
- class LazyUrlService {
51
- urlUtils;
52
- findResource;
53
- routerConfigs;
54
- constructor({ urlUtils = localUtils, findResource = null } = {}) {
55
- this.urlUtils = urlUtils;
56
- this.findResource = findResource;
57
- // Router configs in priority order. Position is the registration order.
58
- this.routerConfigs = [];
59
- }
60
- onRouterAddedType(identifier, filter, resourceType, permalink) {
61
- debug('onRouterAddedType', identifier, resourceType, permalink, filter);
62
- const config = { identifier, filter, resourceType, permalink, nql: null };
63
- if (filter) {
64
- config.nql = nql(filter, { expansions: EXPANSIONS, transformer: PAGE_TRANSFORMER });
65
- }
66
- this.routerConfigs.push(config);
67
- }
68
- onRouterUpdated() {
69
- // No state to regenerate. The next URL request reads the (possibly new)
70
- // router config; in-flight requests that already snapshotted the old
71
- // config keep using it, which matches the documented atomic-reload
72
- // behaviour.
73
- }
74
- /**
75
- * Drop all registered routers. Called when routes.yaml is reloaded.
76
- */
77
- reset() {
78
- this.routerConfigs = [];
79
- }
80
- hasFinished() {
81
- return true;
82
- }
83
- getUrlForResource(resource, options = {}) {
84
- const routerType = this._routerTypeOf(resource);
85
- if (!routerType) {
86
- return this._formatPath('/404/', options);
87
- }
88
- const candidates = this.routerConfigs.filter(c => c.resourceType === routerType);
89
- for (const config of candidates) {
90
- // Warn loudly when a caller passes a resource that's missing
91
- // the relations a tag- or author-filtered router needs. The
92
- // URL silently falls through to /404/ on those routes; the
93
- // log lets operators alert on any caller that hasn't been
94
- // inflated with withRelated: ['tags', 'authors'].
95
- this._warnIfThin(config, resource, routerType);
96
- // NQL filters are evaluated against the original resource (with
97
- // its singular DB `type` field intact) because the page:true/false
98
- // transformer rewrites to type:'post'/'page'.
99
- if (this._matchesFilter(config, resource)) {
100
- const path = this.urlUtils.replacePermalink(config.permalink, resource);
101
- return this._formatPath(path, options);
102
- }
103
- }
104
- return this._formatPath('/404/', options);
105
- }
106
- /**
107
- * Warn when a caller passes a thin resource (missing tags/authors)
108
- * against a router whose filter references those relations. The
109
- * filter eval will fail silently and the URL will resolve to /404/.
110
- * Detection is conservative: only fires when the router filter
111
- * actually references the relation, so a tag-less router (`filter:
112
- * featured:true`) doesn't log for a tag-less resource.
113
- */
114
- _warnIfThin(config, resource, routerType) {
115
- if (!config.filter) {
116
- return;
117
- }
118
- const needsTags = /\btags?\b|\bprimary_tag\b/.test(config.filter);
119
- const needsAuthors = /\bauthors?\b|\bprimary_author\b/.test(config.filter);
120
- const r = resource;
121
- const missingTags = needsTags && !Array.isArray(r.tags);
122
- const missingAuthors = needsAuthors && !Array.isArray(r.authors);
123
- if (!missingTags && !missingAuthors) {
124
- return;
125
- }
126
- const missing = [missingTags && 'tags', missingAuthors && 'authors']
127
- .filter(Boolean);
128
- logging.error(new errors.InternalServerError({
129
- message: 'Thin resource passed to LazyUrlService.getUrlForResource',
130
- code: 'LAZY_URL_THIN_RESOURCE',
131
- errorDetails: {
132
- resourceType: routerType,
133
- resourceId: resource.id,
134
- routerIdentifier: config.identifier,
135
- filter: config.filter,
136
- missing
137
- }
138
- }));
139
- }
140
- ownsResource(routerIdentifier, resource) {
141
- const config = this.routerConfigs.find(c => c.identifier === routerIdentifier);
142
- if (!config || !resource) {
143
- return false;
144
- }
145
- const routerType = this._routerTypeOf(resource);
146
- if (config.resourceType !== routerType) {
147
- return false;
148
- }
149
- return this._matchesFilter(config, resource);
150
- }
151
- /**
152
- * Translate the resource's `type` field into the plural router resource
153
- * type (`'posts'` / `'pages'` / `'tags'` / `'authors'`). Returns `null`
154
- * when the type is missing or unrecognised.
155
- */
156
- _routerTypeOf(resource) {
157
- if (!resource || !resource.type) {
158
- return null;
159
- }
160
- return TYPE_TO_ROUTER_TYPE[resource.type] || null;
161
- }
162
- /**
163
- * Resolve a URL path to a resource record. Iterates router configs in
164
- * priority order, pattern-matching the path against each permalink
165
- * template, querying the database for a matching resource, and verifying
166
- * any NQL filter still matches for posts.
167
- */
168
- async resolveUrl(urlPath) {
169
- if (!this.findResource) {
170
- return null;
171
- }
172
- for (const config of this.routerConfigs) {
173
- const params = this._matchPermalink(config.permalink, urlPath);
174
- if (!params) {
175
- continue;
176
- }
177
- const resource = await this.findResource(config.resourceType, params);
178
- if (!resource) {
179
- continue;
180
- }
181
- // For posts/pages with NQL filters, confirm the DB record still
182
- // satisfies the filter. The match runs against the raw record
183
- // (with its singular `type` column) because the page:true/false
184
- // transformer rewrites to type:'post'/'page'.
185
- if (config.nql && !this._matchesFilter(config, resource)) {
186
- continue;
187
- }
188
- return Object.assign({}, resource, { type: config.resourceType });
189
- }
190
- return null;
191
- }
192
- _matchesFilter(config, resource) {
193
- if (!config.nql) {
194
- return true;
195
- }
196
- try {
197
- return !!config.nql.queryJSON(resource);
198
- }
199
- catch (err) {
200
- const message = err instanceof Error ? err.message : String(err);
201
- debug('NQL match failed', config.filter, message);
202
- return false;
203
- }
204
- }
205
- _formatPath(path, options) {
206
- if (options.absolute) {
207
- return this.urlUtils.createUrl(path, options.absolute);
208
- }
209
- if (options.withSubdirectory) {
210
- return this.urlUtils.createUrl(path, false, true);
211
- }
212
- return path;
213
- }
214
- /**
215
- * Match a Ghost permalink template (e.g. `/:slug/`,
216
- * `/:year/:month/:slug/`) against a URL path and extract the named
217
- * fields. Ghost's RouteSettings validator rewrites `{field}` placeholders
218
- * into `:field` form before they reach the URL service, so this parser
219
- * works on the `:field` syntax.
220
- *
221
- * Implementation: walk the permalink and path one segment at a time.
222
- * Each segment must be either a literal match or a single placeholder.
223
- * Mixing literals and placeholders inside a segment is unsupported and
224
- * not a documented Ghost feature; rejecting it here also keeps us out of
225
- * regex-backtracking territory entirely.
226
- */
227
- _matchPermalink(permalink, urlPath) {
228
- if (typeof permalink !== 'string' || typeof urlPath !== 'string') {
229
- return null;
230
- }
231
- const permalinkSegments = permalink.split('/');
232
- const pathSegments = urlPath.split('/');
233
- if (permalinkSegments.length !== pathSegments.length) {
234
- return null;
235
- }
236
- const params = {};
237
- for (let i = 0; i < permalinkSegments.length; i += 1) {
238
- const templateSegment = permalinkSegments[i];
239
- const pathSegment = pathSegments[i];
240
- if (templateSegment.startsWith(':')) {
241
- const fieldName = templateSegment.slice(1);
242
- if (!/^\w+$/.test(fieldName) || pathSegment.length === 0) {
243
- return null;
244
- }
245
- try {
246
- params[fieldName] = decodeURIComponent(pathSegment);
247
- }
248
- catch {
249
- // Malformed %-escapes (e.g. "/foo%ZZ/") throw URIError;
250
- // treat as a non-match rather than crash the request.
251
- return null;
252
- }
253
- }
254
- else if (templateSegment !== pathSegment) {
255
- return null;
256
- }
257
- }
258
- return params;
259
- }
260
- }
261
- exports.LazyUrlService = LazyUrlService;
262
- module.exports = LazyUrlService;
@@ -1,308 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-require-imports */
2
- const nql = require('@tryghost/nql');
3
- const debug = require('@tryghost/debug')('services:url:lazy');
4
- const logging = require('@tryghost/logging');
5
- const errors = require('@tryghost/errors');
6
- const localUtils = require('../../../shared/url-utils');
7
- /* eslint-enable @typescript-eslint/no-require-imports */
8
-
9
- import type {Resource, UrlOptions, LazyUrlServiceBackend} from './url-service-facade';
10
-
11
- // The same expansions used by UrlGenerator so users can write `tag:foo`,
12
- // `author:jane`, etc. and have them rewritten to the underlying field paths.
13
- const EXPANSIONS = [
14
- {key: 'author', replacement: 'authors.slug'},
15
- {key: 'tags', replacement: 'tags.slug'},
16
- {key: 'tag', replacement: 'tags.slug'},
17
- {key: 'authors', replacement: 'authors.slug'},
18
- {key: 'primary_tag', replacement: 'primary_tag.slug'},
19
- {key: 'primary_author', replacement: 'primary_author.slug'}
20
- ];
21
-
22
- // Same `page:true/false` legacy transformer the UrlGenerator uses, so old
23
- // routes.yaml configs continue to work.
24
- const PAGE_TRANSFORMER = nql.utils.mapKeyValues({
25
- key: {from: 'page', to: 'type'},
26
- values: [
27
- {from: false, to: 'post'},
28
- {from: true, to: 'page'}
29
- ]
30
- });
31
-
32
- // Map a resource's `type` field (whatever the caller passed) onto the plural
33
- // router resource type. We accept the singular DB column values ('post',
34
- // 'page') as well as the plural router keys, because the migrated callers
35
- // are inconsistent: API responses spread `attrs` (singular), but some
36
- // helpers explicitly tag with the plural router key.
37
- const TYPE_TO_ROUTER_TYPE: Record<string, string> = {
38
- post: 'posts',
39
- posts: 'posts',
40
- page: 'pages',
41
- pages: 'pages',
42
- tag: 'tags',
43
- tags: 'tags',
44
- author: 'authors',
45
- authors: 'authors'
46
- };
47
-
48
- interface NqlInstance {
49
- queryJSON(record: Record<string, unknown>): unknown;
50
- }
51
-
52
- interface RouterConfig {
53
- identifier: string;
54
- filter: string | null;
55
- resourceType: string;
56
- permalink: string;
57
- nql: NqlInstance | null;
58
- }
59
-
60
- /**
61
- * Async function that resolves a slug (or other params) to a database
62
- * record. Injected so the URL service stays pure for forward lookups —
63
- * only `resolveUrl` actually hits the DB, and only via this hook.
64
- */
65
- export type FindResource = (
66
- routerType: string,
67
- params: Record<string, string>
68
- ) => Promise<Record<string, unknown> | null>;
69
-
70
- interface LazyUrlServiceDeps {
71
- urlUtils?: typeof localUtils;
72
- findResource?: FindResource | null;
73
- }
74
-
75
- /**
76
- * On-demand URL service. Computes URLs and ownership per-call from the
77
- * registered router configs instead of holding a precomputed map of every
78
- * resource. Pure for forward lookups; resolveUrl() takes an optional DB
79
- * lookup function so reverse lookups stay testable.
80
- */
81
- export class LazyUrlService implements LazyUrlServiceBackend {
82
- private urlUtils: typeof localUtils;
83
- private findResource: FindResource | null;
84
- private routerConfigs: RouterConfig[];
85
-
86
- constructor({urlUtils = localUtils, findResource = null}: LazyUrlServiceDeps = {}) {
87
- this.urlUtils = urlUtils;
88
- this.findResource = findResource;
89
- // Router configs in priority order. Position is the registration order.
90
- this.routerConfigs = [];
91
- }
92
-
93
- onRouterAddedType(
94
- identifier: string,
95
- filter: string | null,
96
- resourceType: string,
97
- permalink: string
98
- ): void {
99
- debug('onRouterAddedType', identifier, resourceType, permalink, filter);
100
- const config: RouterConfig = {identifier, filter, resourceType, permalink, nql: null};
101
- if (filter) {
102
- config.nql = nql(filter, {expansions: EXPANSIONS, transformer: PAGE_TRANSFORMER});
103
- }
104
- this.routerConfigs.push(config);
105
- }
106
-
107
- onRouterUpdated(): void {
108
- // No state to regenerate. The next URL request reads the (possibly new)
109
- // router config; in-flight requests that already snapshotted the old
110
- // config keep using it, which matches the documented atomic-reload
111
- // behaviour.
112
- }
113
-
114
- /**
115
- * Drop all registered routers. Called when routes.yaml is reloaded.
116
- */
117
- reset(): void {
118
- this.routerConfigs = [];
119
- }
120
-
121
- hasFinished(): boolean {
122
- return true;
123
- }
124
-
125
- getUrlForResource(resource: Resource, options: UrlOptions = {}): string {
126
- const routerType = this._routerTypeOf(resource);
127
- if (!routerType) {
128
- return this._formatPath('/404/', options);
129
- }
130
- const candidates = this.routerConfigs.filter(c => c.resourceType === routerType);
131
- for (const config of candidates) {
132
- // Warn loudly when a caller passes a resource that's missing
133
- // the relations a tag- or author-filtered router needs. The
134
- // URL silently falls through to /404/ on those routes; the
135
- // log lets operators alert on any caller that hasn't been
136
- // inflated with withRelated: ['tags', 'authors'].
137
- this._warnIfThin(config, resource, routerType);
138
- // NQL filters are evaluated against the original resource (with
139
- // its singular DB `type` field intact) because the page:true/false
140
- // transformer rewrites to type:'post'/'page'.
141
- if (this._matchesFilter(config, resource)) {
142
- const path = this.urlUtils.replacePermalink(config.permalink, resource);
143
- return this._formatPath(path, options);
144
- }
145
- }
146
- return this._formatPath('/404/', options);
147
- }
148
-
149
- /**
150
- * Warn when a caller passes a thin resource (missing tags/authors)
151
- * against a router whose filter references those relations. The
152
- * filter eval will fail silently and the URL will resolve to /404/.
153
- * Detection is conservative: only fires when the router filter
154
- * actually references the relation, so a tag-less router (`filter:
155
- * featured:true`) doesn't log for a tag-less resource.
156
- */
157
- private _warnIfThin(config: RouterConfig, resource: Resource, routerType: string): void {
158
- if (!config.filter) {
159
- return;
160
- }
161
- const needsTags = /\btags?\b|\bprimary_tag\b/.test(config.filter);
162
- const needsAuthors = /\bauthors?\b|\bprimary_author\b/.test(config.filter);
163
- const r = resource as Record<string, unknown>;
164
- const missingTags = needsTags && !Array.isArray(r.tags);
165
- const missingAuthors = needsAuthors && !Array.isArray(r.authors);
166
- if (!missingTags && !missingAuthors) {
167
- return;
168
- }
169
- const missing = [missingTags && 'tags', missingAuthors && 'authors']
170
- .filter(Boolean) as string[];
171
- logging.error(new errors.InternalServerError({
172
- message: 'Thin resource passed to LazyUrlService.getUrlForResource',
173
- code: 'LAZY_URL_THIN_RESOURCE',
174
- errorDetails: {
175
- resourceType: routerType,
176
- resourceId: resource.id,
177
- routerIdentifier: config.identifier,
178
- filter: config.filter,
179
- missing
180
- }
181
- }));
182
- }
183
-
184
- ownsResource(routerIdentifier: string, resource: Resource | null): boolean {
185
- const config = this.routerConfigs.find(c => c.identifier === routerIdentifier);
186
- if (!config || !resource) {
187
- return false;
188
- }
189
- const routerType = this._routerTypeOf(resource);
190
- if (config.resourceType !== routerType) {
191
- return false;
192
- }
193
- return this._matchesFilter(config, resource);
194
- }
195
-
196
- /**
197
- * Translate the resource's `type` field into the plural router resource
198
- * type (`'posts'` / `'pages'` / `'tags'` / `'authors'`). Returns `null`
199
- * when the type is missing or unrecognised.
200
- */
201
- private _routerTypeOf(resource: Resource | null | undefined): string | null {
202
- if (!resource || !resource.type) {
203
- return null;
204
- }
205
- return TYPE_TO_ROUTER_TYPE[resource.type] || null;
206
- }
207
-
208
- /**
209
- * Resolve a URL path to a resource record. Iterates router configs in
210
- * priority order, pattern-matching the path against each permalink
211
- * template, querying the database for a matching resource, and verifying
212
- * any NQL filter still matches for posts.
213
- */
214
- async resolveUrl(urlPath: string): Promise<Resource | null> {
215
- if (!this.findResource) {
216
- return null;
217
- }
218
- for (const config of this.routerConfigs) {
219
- const params = this._matchPermalink(config.permalink, urlPath);
220
- if (!params) {
221
- continue;
222
- }
223
- const resource = await this.findResource(config.resourceType, params);
224
- if (!resource) {
225
- continue;
226
- }
227
- // For posts/pages with NQL filters, confirm the DB record still
228
- // satisfies the filter. The match runs against the raw record
229
- // (with its singular `type` column) because the page:true/false
230
- // transformer rewrites to type:'post'/'page'.
231
- if (config.nql && !this._matchesFilter(config, resource)) {
232
- continue;
233
- }
234
- return Object.assign({}, resource, {type: config.resourceType}) as Resource;
235
- }
236
- return null;
237
- }
238
-
239
- private _matchesFilter(config: RouterConfig, resource: Record<string, unknown>): boolean {
240
- if (!config.nql) {
241
- return true;
242
- }
243
- try {
244
- return !!config.nql.queryJSON(resource);
245
- } catch (err) {
246
- const message = err instanceof Error ? err.message : String(err);
247
- debug('NQL match failed', config.filter, message);
248
- return false;
249
- }
250
- }
251
-
252
- private _formatPath(path: string, options: UrlOptions): string {
253
- if (options.absolute) {
254
- return this.urlUtils.createUrl(path, options.absolute);
255
- }
256
- if (options.withSubdirectory) {
257
- return this.urlUtils.createUrl(path, false, true);
258
- }
259
- return path;
260
- }
261
-
262
- /**
263
- * Match a Ghost permalink template (e.g. `/:slug/`,
264
- * `/:year/:month/:slug/`) against a URL path and extract the named
265
- * fields. Ghost's RouteSettings validator rewrites `{field}` placeholders
266
- * into `:field` form before they reach the URL service, so this parser
267
- * works on the `:field` syntax.
268
- *
269
- * Implementation: walk the permalink and path one segment at a time.
270
- * Each segment must be either a literal match or a single placeholder.
271
- * Mixing literals and placeholders inside a segment is unsupported and
272
- * not a documented Ghost feature; rejecting it here also keeps us out of
273
- * regex-backtracking territory entirely.
274
- */
275
- private _matchPermalink(permalink: string, urlPath: string): Record<string, string> | null {
276
- if (typeof permalink !== 'string' || typeof urlPath !== 'string') {
277
- return null;
278
- }
279
- const permalinkSegments = permalink.split('/');
280
- const pathSegments = urlPath.split('/');
281
- if (permalinkSegments.length !== pathSegments.length) {
282
- return null;
283
- }
284
- const params: Record<string, string> = {};
285
- for (let i = 0; i < permalinkSegments.length; i += 1) {
286
- const templateSegment = permalinkSegments[i];
287
- const pathSegment = pathSegments[i];
288
- if (templateSegment.startsWith(':')) {
289
- const fieldName = templateSegment.slice(1);
290
- if (!/^\w+$/.test(fieldName) || pathSegment.length === 0) {
291
- return null;
292
- }
293
- try {
294
- params[fieldName] = decodeURIComponent(pathSegment);
295
- } catch {
296
- // Malformed %-escapes (e.g. "/foo%ZZ/") throw URIError;
297
- // treat as a non-match rather than crash the request.
298
- return null;
299
- }
300
- } else if (templateSegment !== pathSegment) {
301
- return null;
302
- }
303
- }
304
- return params;
305
- }
306
- }
307
-
308
- module.exports = LazyUrlService;