ghost 6.37.1 → 6.38.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 (258) hide show
  1. package/components/tryghost-i18n-0.0.0.tgz +0 -0
  2. package/core/boot.js +13 -9
  3. package/core/bridge.js +10 -6
  4. package/core/built/admin/assets/{PolarAngleAxis-Cavik65s.js → PolarAngleAxis-CB-FJBDa.js} +1 -1
  5. package/core/built/admin/assets/{_baseAssignValue-CnqN6ZMT.js → _baseAssignValue-BEAjPAfy.js} +1 -1
  6. package/core/built/admin/assets/{a-large-small-Wi4LtZX-.js → a-large-small-L1BJa6mG.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-Cf8T5Jfd.mjs → code-editor-view-bOZzJ3a1.mjs} +2 -2
  9. package/core/built/admin/assets/admin-x-settings/{index-BMWEh-vR.mjs → index-BRuMxaCz.mjs} +2 -2
  10. package/core/built/admin/assets/admin-x-settings/{index-Aj-hFSnY.mjs → index-CTAQj489.mjs} +5 -5
  11. package/core/built/admin/assets/admin-x-settings/{index-DjBJrXYn.mjs → index-WoKKBOe4.mjs} +2 -2
  12. package/core/built/admin/assets/admin-x-settings/{modals-8WpWZZdI.mjs → modals-BQ5M-RTA.mjs} +3 -3
  13. package/core/built/admin/assets/{arrow-right-BiAOKOto.js → arrow-right-B2j-ecEK.js} +1 -1
  14. package/core/built/admin/assets/{at-sign-z5gBMxDW.js → at-sign-CtAw8_lw.js} +1 -1
  15. package/core/built/admin/assets/{audience-DxNilLIv.js → audience-CvSOqHuo.js} +1 -1
  16. package/core/built/admin/assets/automations-BVUNuVOh.js +1 -0
  17. package/core/built/admin/assets/automations-Bi9RJq5E.js +1 -0
  18. package/core/built/admin/assets/{avatar-flipboard-5OzseZFV.js → avatar-flipboard-DocLTXLh.js} +1 -1
  19. package/core/built/admin/assets/{bluesky-sharing-D9YcSpW8.js → bluesky-sharing-DafX2lE1.js} +1 -1
  20. package/core/built/admin/assets/chart-BRQeD9eh.js +67 -0
  21. package/core/built/admin/assets/{chevron-up-DVt6fhV3.js → chevron-up-v4_QwmnL.js} +1 -1
  22. package/core/built/admin/assets/{chunk.524.fcd26ef812d73e2b0620.js → chunk.524.c3d844070c24d7984fd7.js} +4 -4
  23. package/core/built/admin/assets/{chunk.582.b61ffaad1c2fa4aba820.js → chunk.582.0e0ede1eac12926acceb.js} +6 -6
  24. package/core/built/admin/assets/{chunk.383.adb5a3fe32b6b98c43ff.js → chunk.995.4b32363ab39da3e7f5d7.js} +3 -3
  25. package/core/built/admin/assets/circle-alert-Dbz54fYb.js +1 -0
  26. package/core/built/admin/assets/{code-editor-view-CbQIqTrK.js → code-editor-view-D-pYCcdu.js} +1 -1
  27. package/core/built/admin/assets/comments-DSv09jRn.js +1 -0
  28. package/core/built/admin/assets/{content-helpers-jANWGIfA.js → content-helpers-DQdPPDGb.js} +1 -1
  29. package/core/built/admin/assets/{copy-BBLeLEnG.js → copy-BULQsPZ4.js} +1 -1
  30. package/core/built/admin/assets/{data-list-BveeOSGI.js → data-list-WxXC0iKE.js} +1 -1
  31. package/core/built/admin/assets/{deleted-feed-item-DupAEk4P.js → deleted-feed-item-ZBLfgRkK.js} +1 -1
  32. package/core/built/admin/assets/{dropzone-D3PSdAHN.js → dropzone-B8mAoWrn.js} +1 -1
  33. package/core/built/admin/assets/{edit-profile-NrCR9_bi.js → edit-profile-Bj3th8H0.js} +1 -1
  34. package/core/built/admin/assets/editor-BZV40eAE.css +1 -0
  35. package/core/built/admin/assets/editor-u20VuPzn.js +7 -0
  36. package/core/built/admin/assets/{empty-indicator-Dhi_KeYX.js → empty-indicator-P3IsUguf.js} +1 -1
  37. package/core/built/admin/assets/{en-BontI3SP.js → en-C9bUnuy7.js} +1 -1
  38. package/core/built/admin/assets/{feed-B3Kfiz5R.js → feed-B94mO944.js} +1 -1
  39. package/core/built/admin/assets/{filter-query-core-Dwh4Qulu.js → filter-query-core-CM0KKIr2.js} +1 -1
  40. package/core/built/admin/assets/filters-D52FTE0z.js +1 -0
  41. package/core/built/admin/assets/gh-chart-Xa6Nvzwz.js +1 -0
  42. package/core/built/admin/assets/{ghost-c25434d5bbf5a5429883d4c3590f1946.js → ghost-82f1c4ffe0e1cd058ce4b66b5253b620.js} +159 -201
  43. package/core/built/admin/assets/{ghost-dark-52df27573e5f2a75e6cf4b1d06eea9e4.css → ghost-dark-9897cf5102772faedf566e5e54d4fbee.css} +1 -1
  44. package/core/built/admin/assets/{ghost-fcf460028e04192f392f16e173f2e3a8.css → ghost-e11f1814be0f38e98bb77b49eba11178.css} +1 -1
  45. package/core/built/admin/assets/{growth-DRH8zUIJ.js → growth-D3TW3JIB.js} +1 -1
  46. package/core/built/admin/assets/{hash-UvX5iOa9.js → hash-CstXS-iB.js} +1 -1
  47. package/core/built/admin/assets/{header-G1a38jnf.js → header-StkTITw-.js} +1 -1
  48. package/core/built/admin/assets/{inbox-By7HZskI.js → inbox-D1RM3kVF.js} +1 -1
  49. package/core/built/admin/assets/index-BEkJ9xtY.css +1 -0
  50. package/core/built/admin/assets/{index-CS_ZmI8F.js → index-BJx5Uas5.js} +1 -1
  51. package/core/built/admin/assets/index-BKzg6OE7.js +1 -0
  52. package/core/built/admin/assets/{index-D7JyKf1m.js → index-BM-MBdD0.js} +1 -1
  53. package/core/built/admin/assets/index-BRzvVuXt.js +2 -0
  54. package/core/built/admin/assets/index-B_3xqeM9.js +1 -0
  55. package/core/built/admin/assets/{index-ydyrczVl.js → index-C-OdR2wJ.js} +1 -1
  56. package/core/built/admin/assets/{index-X-MG1ikg.js → index-C59QlJqT.js} +1 -1
  57. package/core/built/admin/assets/{index-CJo0Qh4V.js → index-CCq_1tCo.js} +1 -1
  58. package/core/built/admin/assets/{index-qHUvPjQB.js → index-CYyEyBYw.js} +11 -11
  59. package/core/built/admin/assets/{index-CX4ztLSk.js → index-DHbiBjyq.js} +1 -1
  60. package/core/built/admin/assets/{index-BP6hBbPK.js → index-DTVgSMyL.js} +2 -2
  61. package/core/built/admin/assets/{index-vZMbI8e8.js → index-DiUjEgRw.js} +1 -1
  62. package/core/built/admin/assets/{index-YM4ZplGj.js → index-DugxKzA6.js} +1 -1
  63. package/core/built/admin/assets/{index-D8InZCr2.js → index-DzVHlhO1.js} +1 -1
  64. package/core/built/admin/assets/{index-D4b7U6K-.js → index-LsaD9Cq7.js} +1 -1
  65. package/core/built/admin/assets/{index-DRUPF8nN.js → index-x0hNfyqU.js} +1 -1
  66. package/core/built/admin/assets/{inline-Bc9zHR7e.js → inline-B4cWemrJ.js} +1 -1
  67. package/core/built/admin/assets/{koenig-lexical-B-ZnqRE1.js → koenig-lexical-BOdeH3QJ.js} +1 -1
  68. package/core/built/admin/assets/{kpi-card-zyi5Onb0.js → kpi-card-P7zSpcx0.js} +1 -1
  69. package/core/built/admin/assets/{kpi-card-CzlPIEPE.js → kpi-card-plhYOEtA.js} +1 -1
  70. package/core/built/admin/assets/{kpi-tabs-Dsa5ldXK.js → kpi-tabs-DZw2Qktb.js} +1 -1
  71. package/core/built/admin/assets/{kpis-Cw5ZRQhk.js → kpis-CDHM0xhW.js} +1 -1
  72. package/core/built/admin/assets/{label-BvxmsD1M.js → label-Ckacdx14.js} +1 -1
  73. package/core/built/admin/assets/{links-CiS5BPKS.js → links-BtXE5d0Y.js} +1 -1
  74. package/core/built/admin/assets/{list-filter-zmkB7rg4.js → list-filter-DLjjiktn.js} +1 -1
  75. package/core/built/admin/assets/{list-header-Cp779e3H.js → list-header-Cc7Ia3nr.js} +1 -1
  76. package/core/built/admin/assets/loader-circle-DLJd-dNm.js +1 -0
  77. package/core/built/admin/assets/{mail-i2qs8CIG.js → mail-B97w28e2.js} +1 -1
  78. package/core/built/admin/assets/{main-layout-KCvKOX8F.js → main-layout-bwWvzc9B.js} +1 -1
  79. package/core/built/admin/assets/{members-w601v5JJ.js → members-CokS8MI2.js} +5 -5
  80. package/core/built/admin/assets/{message-square-text-bG9IoWIt.js → message-square-text-D3rsjDFR.js} +1 -1
  81. package/core/built/admin/assets/minus-BzXQVmMw.js +1 -0
  82. package/core/built/admin/assets/{modals-lNzhiL0o.js → modals-D0lip-iy.js} +2 -2
  83. package/core/built/admin/assets/{moderation-CyfNRSWj.js → moderation-Dv7AadCV.js} +1 -1
  84. package/core/built/admin/assets/newsletter-BazC5rIj.js +1 -0
  85. package/core/built/admin/assets/{newsletters-B8xSXSr_.js → newsletters-m6HQgU5J.js} +1 -1
  86. package/core/built/admin/assets/{note-dNfKEpxw.js → note-CCGiEKz-.js} +1 -1
  87. package/core/built/admin/assets/{onboarding-route-Drbw0QtT.js → onboarding-route-Cbt7qWNE.js} +1 -1
  88. package/core/built/admin/assets/overview-TN50tzUh.js +1 -0
  89. package/core/built/admin/assets/{pagemenu-VJXMoMB4.js → pagemenu-BXrHWtsU.js} +1 -1
  90. package/core/built/admin/assets/{pencil-DHNmlLjO.js → pencil-BL1r8KVy.js} +1 -1
  91. package/core/built/admin/assets/{post-analytics-PsqLsS2e.js → post-analytics-BqEXi6Ps.js} +1 -1
  92. package/core/built/admin/assets/{post-analytics-context-QLZsZCFg.js → post-analytics-context-DekYNlb3.js} +1 -1
  93. package/core/built/admin/assets/{post-analytics-header-PecXsIT-.js → post-analytics-header-AkNbn6uc.js} +1 -1
  94. package/core/built/admin/assets/{post-share-modal-B4Rvo7W1.js → post-share-modal-CXDGVKoQ.js} +1 -1
  95. package/core/built/admin/assets/posts/{app-utils-DIc5TmxO.mjs → app-utils-DxA7edI_.mjs} +2 -2
  96. package/core/built/admin/assets/posts/{automations-BZpT35kJ.mjs → automations-Cwu_gHl4.mjs} +64 -67
  97. package/core/built/admin/assets/posts/automations-D81hV_2c.mjs +13 -0
  98. package/core/built/admin/assets/posts/{avatar-K63cvqSS.mjs → avatar-IkjEzEjI.mjs} +19 -19
  99. package/core/built/admin/assets/posts/{button-ufGBCcsV.mjs → button-KaJZKcou.mjs} +21 -21
  100. package/core/built/admin/assets/posts/{check-IcEnuTpD.mjs → check-Cf74RvZ1.mjs} +21 -21
  101. package/core/built/admin/assets/posts/{comments-CyJU9Ums.mjs → comments-pdKhYbYG.mjs} +54 -52
  102. package/core/built/admin/assets/posts/createLucideIcon-Cj7h3r9g.mjs +320 -0
  103. package/core/built/admin/assets/posts/{dialog-D6_lBvtT.mjs → dialog-Ci3W8fjx.mjs} +71 -76
  104. package/core/built/admin/assets/posts/{dropdown-menu-BSSmAory.mjs → dropdown-menu-DZ9XCHSw.mjs} +5 -5
  105. package/core/built/admin/assets/posts/editor-Bb-xle0o.mjs +6084 -0
  106. package/core/built/admin/assets/posts/ellipsis-ChhZo9aT.mjs +10 -0
  107. package/core/built/admin/assets/posts/{empty-indicator-DvGCvRAX.mjs → empty-indicator-DnFs7hDy.mjs} +2 -2
  108. package/core/built/admin/assets/posts/{filters-BLBaHhJz.mjs → filters-CO_FUPX-.mjs} +42 -40
  109. package/core/built/admin/assets/posts/get-site-timezone-C21yKZT_.mjs +87 -0
  110. package/core/built/admin/assets/posts/{growth-Bz1ChJNB.mjs → growth-CtX9Efm2.mjs} +12 -12
  111. package/core/built/admin/assets/posts/heading-DZ_KtDTL.mjs +138 -0
  112. package/core/built/admin/assets/posts/{hooks-BEngBys9.mjs → hooks-DVhBDVlA.mjs} +3 -2
  113. package/core/built/admin/assets/posts/{index-BAF0YXsp.mjs → index-B4db8w_H.mjs} +181 -172
  114. package/core/built/admin/assets/posts/{inline-C2YFQAdU.mjs → inline-BfB-SNk0.mjs} +2 -2
  115. package/core/built/admin/assets/posts/{input-surface-TqJlNSp_.mjs → input-surface-CNwZJloi.mjs} +2 -2
  116. package/core/built/admin/assets/posts/{kpis-DAX5_0-Q.mjs → kpis-BvY_5Y1u.mjs} +8792 -8803
  117. package/core/built/admin/assets/posts/{links-A1YTsBbF.mjs → links-Ck-h7SKg.mjs} +57 -57
  118. package/core/built/admin/assets/posts/{loading-indicator-BZsjmp8g.mjs → loading-indicator-B_rU9Lie.mjs} +3 -3
  119. package/core/built/admin/assets/posts/mail-DiZ3oKEW.mjs +9 -0
  120. package/core/built/admin/assets/posts/{main-layout-D_HHTP_n.mjs → main-layout-aYo6qOBZ.mjs} +2 -2
  121. package/core/built/admin/assets/posts/{newsletter-BRzgjOIa.mjs → newsletter-BQc53TF2.mjs} +31 -30
  122. package/core/built/admin/assets/posts/{overview-C41VFjn7.mjs → overview-blz9Ak9q.mjs} +42 -41
  123. package/core/built/admin/assets/posts/plus-YVjtYaK3.mjs +15 -0
  124. package/core/built/admin/assets/posts/{post-analytics-DGgMkFaf.mjs → post-analytics-BQbWGoeu.mjs} +6 -6
  125. package/core/built/admin/assets/posts/{post-analytics-context-B1r3HBrp.mjs → post-analytics-context-Be7THgnd.mjs} +7 -7
  126. package/core/built/admin/assets/posts/{post-analytics-header-DTWSO0Aq.mjs → post-analytics-header-iDFy7Bao.mjs} +2762 -2764
  127. package/core/built/admin/assets/posts/{post-share-modal-ohHwk0Yt.mjs → post-share-modal-akP16idN.mjs} +50 -48
  128. package/core/built/admin/assets/posts/posts.js +2 -1
  129. package/core/built/admin/assets/posts/{settings-CZYL6Jhr.mjs → settings-BdQhfHxY.mjs} +2 -2
  130. package/core/built/admin/assets/posts/{sheet-D7YesQJE.mjs → sheet-3dBlKeXf.mjs} +14 -13
  131. package/core/built/admin/assets/posts/{skeleton-S1k58YKa.mjs → skeleton-DMgSvYqr.mjs} +11 -11
  132. package/core/built/admin/assets/posts/{source-icon-DnpCy5N4.mjs → source-icon-Bp5Qxq1E.mjs} +4 -4
  133. package/core/built/admin/assets/posts/{stats-CbOpowXb.mjs → stats-DJx9qc_u.mjs} +4 -4
  134. package/core/built/admin/assets/posts/{table-Cw3Wxf1C.mjs → table-BpVwuzeH.mjs} +2 -2
  135. package/core/built/admin/assets/posts/{data-list-BnZARP88.mjs → tabs-CbZCpxfI.mjs} +4885 -5284
  136. package/core/built/admin/assets/posts/{tags-BoMt6Ce3.mjs → tags-CNtvDS6f.mjs} +33 -33
  137. package/core/built/admin/assets/posts/{tags-CNdsvTtZ.mjs → tags-DFOoFZ_3.mjs} +2 -2
  138. package/core/built/admin/assets/posts/{tooltip-CTcyINxz.mjs → tooltip-Cc_09RDU.mjs} +106 -117
  139. package/core/built/admin/assets/posts/value-CwGM7M-1.mjs +410 -0
  140. package/core/built/admin/assets/posts/{virtual-list-window-Bs88yrut.mjs → virtual-list-window-D6nRoL0y.mjs} +5 -5
  141. package/core/built/admin/assets/posts/{web-Bsd8eTP6.mjs → web-yfPssEt8.mjs} +1042 -1040
  142. package/core/built/admin/assets/posts/x-DrUGcpfp.mjs +9 -0
  143. package/core/built/admin/assets/posts/zap-DAN0ur-o.mjs +24 -0
  144. package/core/built/admin/assets/posts-D-jyPPE6.js +1 -0
  145. package/core/built/admin/assets/power-Cc5ncYVl.js +1 -0
  146. package/core/built/admin/assets/{referrers-kcBT5smx.js → referrers-qnXsKsuv.js} +1 -1
  147. package/core/built/admin/assets/{repeat-Cn2sKd-L.js → repeat-BGIBynZH.js} +1 -1
  148. package/core/built/admin/assets/{reply-D7E-ZMd5.js → reply-DOKdAsL_.js} +1 -1
  149. package/core/built/admin/assets/{rocket-DnH0XAGW.js → rocket-CAN2NB82.js} +1 -1
  150. package/core/built/admin/assets/{select-f0MMMQWu.js → select-8S9kYGSM.js} +1 -1
  151. package/core/built/admin/assets/{settings-BAQNP3gU.js → settings-DeD-bmZc.js} +4 -4
  152. package/core/built/admin/assets/{settings-ORttG1jT.js → settings-e4IB9sx6.js} +1 -1
  153. package/core/built/admin/assets/{share-modal-CXU4tGY8.js → share-modal-CBavk20I.js} +1 -1
  154. package/core/built/admin/assets/{sort-button-BXMrChFu.js → sort-button-DlSKBzUq.js} +1 -1
  155. package/core/built/admin/assets/{source-icon-cjy79jGT.js → source-icon-27VIJSXr.js} +1 -1
  156. package/core/built/admin/assets/{sprout-DZ4tZ78G.js → sprout-D66nqc79.js} +1 -1
  157. package/core/built/admin/assets/{square-vOzqJf7x.js → square-DccaXQWe.js} +1 -1
  158. package/core/built/admin/assets/stats/{audience-5NeJICtP.mjs → audience-B84ClTDM.mjs} +3 -3
  159. package/core/built/admin/assets/stats/{content-helpers-DFSTl3uB.mjs → content-helpers-CBAddUg7.mjs} +4 -4
  160. package/core/built/admin/assets/stats/{index-C4T7u699.mjs → index-Bz5oVpED.mjs} +5 -5
  161. package/core/built/admin/assets/stats/{index-B8bp6qe2.mjs → index-Cs8i1uR4.mjs} +74 -75
  162. package/core/built/admin/assets/stats/{index-wvi17m79.mjs → index-DEsP_Tbd.mjs} +8 -8
  163. package/core/built/admin/assets/stats/{index-CFfAYHdD.mjs → index-Ds-pM47x.mjs} +5 -5
  164. package/core/built/admin/assets/stats/{index-Chqk7VlR.mjs → index-Wzk1IcAz.mjs} +6 -6
  165. package/core/built/admin/assets/stats/{kpi-tabs-Df9yOI2P.mjs → kpi-tabs-Cs4l8B_M.mjs} +7 -7
  166. package/core/built/admin/assets/stats/{sort-button-Doi8OgMU.mjs → sort-button-rR-GA9Hu.mjs} +3 -3
  167. package/core/built/admin/assets/stats/{stats-FXeCefyd.mjs → stats-RFpEJpEq.mjs} +34 -34
  168. package/core/built/admin/assets/stats/stats.js +1 -1
  169. package/core/built/admin/assets/stats/{use-growth-stats-93C_3EZk.mjs → use-growth-stats-DwgFzbq9.mjs} +3 -3
  170. package/core/built/admin/assets/{stats-CIGz2Win.js → stats-B4OybenW.js} +1 -1
  171. package/core/built/admin/assets/{stats-view-BTKRxYTQ.js → stats-view-BuExwFXa.js} +1 -1
  172. package/core/built/admin/assets/{step-1-BSMHbT-q.js → step-1-D_5LOQrC.js} +1 -1
  173. package/core/built/admin/assets/{step-2-_YC0LCZv.js → step-2-BZLBj8cj.js} +1 -1
  174. package/core/built/admin/assets/{step-3-DWMqP2bO.js → step-3-s5uNbcOH.js} +1 -1
  175. package/core/built/admin/assets/{table-mHKjXQba.js → table-pGS7EeJe.js} +1 -1
  176. package/core/built/admin/assets/{tabs-B93IVRBz.js → tabs-CjdKssUe.js} +1 -1
  177. package/core/built/admin/assets/{tags-DCb-x2ZB.js → tags-B0vRk0A6.js} +1 -1
  178. package/core/built/admin/assets/{tags-kUHJ1MZo.js → tags-Dso4-FA_.js} +1 -1
  179. package/core/built/admin/assets/{textarea-DswCtM7_.js → textarea-DzFnun0_.js} +1 -1
  180. package/core/built/admin/assets/{tiers-DueraQKd.js → tiers-4Vkr_D-L.js} +1 -1
  181. package/core/built/admin/assets/{toggle-group-ChTo8MPD.js → toggle-group-hDsr47IB.js} +1 -1
  182. package/core/built/admin/assets/{topic-filter-DrW3fYWo.js → topic-filter-C7TpPe1G.js} +1 -1
  183. package/core/built/admin/assets/{trash-B2ESGftj.js → trash-DCVuk7ZX.js} +1 -1
  184. package/core/built/admin/assets/{underline-vo-bPRac.js → underline-CjK8la2G.js} +1 -1
  185. package/core/built/admin/assets/{upload-DXQRkBBq.js → upload-CPYhmtWZ.js} +1 -1
  186. package/core/built/admin/assets/{use-growth-stats-CoB8-lsd.js → use-growth-stats-CaFSgp_R.js} +1 -1
  187. package/core/built/admin/assets/{use-simple-pagination-Y_Wv2N6g.js → use-simple-pagination-D0vJBMvF.js} +1 -1
  188. package/core/built/admin/assets/{user-round-check-Dp9a2x-e.js → user-round-check-CzQQeFd8.js} +1 -1
  189. package/core/built/admin/assets/{user-round-x-DwCTGlxc.js → user-round-x-BDFm8yQj.js} +1 -1
  190. package/core/built/admin/assets/value-BGBiTkF3.js +1 -0
  191. package/core/built/admin/assets/{vendor-17f3b979ba6f3898d1a8c5249cc22ff1.js → vendor-ce3fc901ccbcf37f36eb791b4e454138.js} +29 -31
  192. package/core/built/admin/assets/{virtual-list-window-BF7IvGMC.js → virtual-list-window-EKtWZ5cP.js} +1 -1
  193. package/core/built/admin/assets/{wallet-cards-IEmcxJvG.js → wallet-cards-C1NOBbWE.js} +1 -1
  194. package/core/built/admin/assets/web-DCA7HR1S.js +1 -0
  195. package/core/built/admin/index.html +8 -8
  196. package/core/frontend/helpers/social_accounts.js +91 -0
  197. package/core/frontend/services/sitemap/handler.js +14 -2
  198. package/core/frontend/services/sitemap/site-map-manager.js +133 -9
  199. package/core/frontend/web/middleware/handle-image-sizes.js +4 -0
  200. package/core/server/api/endpoints/automations.js +38 -45
  201. package/core/server/models/member.js +30 -25
  202. package/core/server/services/automations/automations-api.js +58 -0
  203. package/core/server/services/automations/automations-api.ts +71 -0
  204. package/core/server/services/automations/automations-repository.js +2 -0
  205. package/core/server/services/automations/automations-repository.ts +63 -0
  206. package/core/server/services/automations/fake-database-automations-repository.js +210 -0
  207. package/core/server/services/automations/fake-database-automations-repository.ts +273 -0
  208. package/core/server/services/automations/temporary-fake-database.js +5 -4
  209. package/core/server/services/gifts/email-templates/gift-purchase-confirmation.hbs +10 -32
  210. package/core/server/services/gifts/email-templates/gift-purchase-confirmation.js +6 -8
  211. package/core/server/services/gifts/email-templates/gift-purchase-confirmation.ts +6 -8
  212. package/core/server/services/gifts/email-templates/gift-reminder.hbs +12 -37
  213. package/core/server/services/gifts/email-templates/gift-reminder.js +5 -9
  214. package/core/server/services/gifts/email-templates/gift-reminder.ts +5 -9
  215. package/core/server/services/gifts/gift-service.js +1 -1
  216. package/core/server/services/gifts/gift-service.ts +2 -2
  217. package/core/server/services/member-welcome-emails/service.js +0 -1
  218. package/core/server/services/members/members-api/controllers/router-controller.js +52 -7
  219. package/core/server/services/members/members-api/services/member-bread-service.js +0 -8
  220. package/core/server/services/route-settings/route-settings.js +18 -2
  221. package/core/server/services/staff/email-templates/gift.hbs +1 -1
  222. package/core/server/services/staff/email-templates/gift.txt.js +1 -1
  223. package/core/server/services/staff/email-templates/new-gift-subscription.hbs +1 -1
  224. package/core/server/services/staff/email-templates/new-gift-subscription.txt.js +1 -1
  225. package/core/server/services/staff/staff-service-emails.js +1 -1
  226. package/core/server/services/url/index.js +16 -1
  227. package/core/server/services/url/lazy-find-resource.js +49 -0
  228. package/core/server/services/url/lazy-find-resource.ts +62 -0
  229. package/core/server/services/url/lazy-url-service.js +220 -0
  230. package/core/server/services/url/lazy-url-service.ts +265 -0
  231. package/core/server/web/api/endpoints/admin/routes.js +1 -0
  232. package/core/server/web/gift-preview/Inter.ttf +0 -0
  233. package/core/server/web/gift-preview/controller.js +56 -6
  234. package/core/server/web/gift-preview/gift-card-noise.png +0 -0
  235. package/core/server/web/gift-preview/gift-card-orb.png +0 -0
  236. package/core/server/web/gift-preview/image.js +170 -43
  237. package/core/shared/config/defaults.json +1 -0
  238. package/core/shared/one-at-a-time.js +28 -33
  239. package/core/shared/one-at-a-time.ts +58 -0
  240. package/package.json +18 -16
  241. package/pnpm-lock.yaml +989 -356
  242. package/core/built/admin/assets/automations-D1uMjd06.js +0 -1
  243. package/core/built/admin/assets/chart-_EXssQtm.js +0 -67
  244. package/core/built/admin/assets/comments-E00h1SbR.js +0 -1
  245. package/core/built/admin/assets/filters-CC7XfjOS.js +0 -1
  246. package/core/built/admin/assets/gh-chart-B9_ywrJJ.js +0 -1
  247. package/core/built/admin/assets/index-AsV-aY0z.css +0 -1
  248. package/core/built/admin/assets/index-B0y_ynZw.js +0 -1
  249. package/core/built/admin/assets/index-Bs6wWbLA.js +0 -2
  250. package/core/built/admin/assets/index-Cb4641XW.js +0 -1
  251. package/core/built/admin/assets/loader-circle-B5UPqSok.js +0 -1
  252. package/core/built/admin/assets/minus-DWx9VUtr.js +0 -1
  253. package/core/built/admin/assets/newsletter-fkQye9OK.js +0 -1
  254. package/core/built/admin/assets/overview-CZ-VrnXy.js +0 -1
  255. package/core/built/admin/assets/posts/createLucideIcon-DcUfTBt_.mjs +0 -454
  256. package/core/built/admin/assets/posts/get-site-timezone-DlXmHA3y.mjs +0 -93
  257. package/core/built/admin/assets/posts-Bx1RWrub.js +0 -1
  258. package/core/built/admin/assets/web-DSME3e8c.js +0 -1
@@ -2,18 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderText = renderText;
4
4
  function renderText(data) {
5
- return `Your gift is ready to share!
5
+ return `Your gift is ready
6
6
 
7
- Thank you for supporting ${data.siteTitle}. Send the link below to share your gift with whoever you'd like.
7
+ Thanks for supporting ${data.siteTitle}. Share the link below to give someone access to ${data.gift.tierName} membership for ${data.gift.cadenceLabel}.
8
8
 
9
- Gift subscription: ${data.gift.tierName} • ${data.gift.cadenceLabel}
9
+ ${data.gift.link}
10
10
 
11
- Gift link: ${data.gift.link}
11
+ The link can be redeemed once and expires on ${data.gift.expiresAt}.
12
12
 
13
- This link expires on ${data.gift.expiresAt} and can be redeemed once by anyone who isn't already a paid member of ${data.siteTitle}.
13
+ Happy gifting.
14
14
 
15
15
  ---
16
-
17
- Sent to ${data.toEmail} from ${data.siteDomain}.
18
- You received this email because you purchased a gift subscription on ${data.siteTitle}.`;
16
+ This message was sent from ${data.siteDomain} to ${data.toEmail}.`;
19
17
  }
@@ -11,18 +11,16 @@ export interface GiftPurchaseConfirmationData {
11
11
  }
12
12
 
13
13
  export function renderText(data: GiftPurchaseConfirmationData): string {
14
- return `Your gift is ready to share!
14
+ return `Your gift is ready
15
15
 
16
- Thank you for supporting ${data.siteTitle}. Send the link below to share your gift with whoever you'd like.
16
+ Thanks for supporting ${data.siteTitle}. Share the link below to give someone access to ${data.gift.tierName} membership for ${data.gift.cadenceLabel}.
17
17
 
18
- Gift subscription: ${data.gift.tierName} • ${data.gift.cadenceLabel}
18
+ ${data.gift.link}
19
19
 
20
- Gift link: ${data.gift.link}
20
+ The link can be redeemed once and expires on ${data.gift.expiresAt}.
21
21
 
22
- This link expires on ${data.gift.expiresAt} and can be redeemed once by anyone who isn't already a paid member of ${data.siteTitle}.
22
+ Happy gifting.
23
23
 
24
24
  ---
25
-
26
- Sent to ${data.toEmail} from ${data.siteDomain}.
27
- You received this email because you purchased a gift subscription on ${data.siteTitle}.`;
25
+ This message was sent from ${data.siteDomain} to ${data.toEmail}.`;
28
26
  }
@@ -6,14 +6,13 @@
6
6
  <title>Your gift subscription is ending soon</title>
7
7
  <style>
8
8
  @media only screen and (max-width: 620px) {
9
- table.body h1 { font-size: 22px !important; padding-bottom: 16px !important; }
10
9
  table.body p, table.body td, table.body a { font-size: 16px !important; }
11
10
  table.body .wrapper { padding: 10px !important; }
12
11
  table.body .content { padding: 0 !important; }
13
12
  table.body .container { padding: 0 !important; width: 100% !important; }
14
13
  table.body .main { border-radius: 0 !important; }
15
- table.body p.large, table.body p.large a { font-size: 18px !important; }
16
- table.body p.small, table.body a.small { font-size: 12px !important; }
14
+ table.body .btn a { width: 100% !important; }
15
+ table.body p.small, table.body a.small { font-size: 11px !important; }
17
16
  }
18
17
  </style>
19
18
  </head>
@@ -39,37 +38,18 @@
39
38
  {{/if}}
40
39
  <tr>
41
40
  <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
42
- <h1 style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 26px; color: #15212A; font-weight: bold; line-height: 28px; margin: 0; padding-bottom: 12px;">Your gift subscription is ending&nbsp;soon</h1>
43
- <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; padding-bottom: 24px;">Your gift subscription to {{siteTitle}} ends on {{gift.consumesAt}}. Continue with a paid subscription to keep reading.</p>
44
- <table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; table-layout: fixed; width: 100%; min-width: 100%; box-sizing: border-box; background: #F4F5F6; border-radius: 8px;">
41
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #15212A; font-weight: normal; line-height: 24px; margin: 0; padding-bottom: 24px;">Hey there,</p>
42
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 24px; margin: 0; padding-bottom: 24px;">Your gift subscription expires on <strong>{{gift.consumesAt}}</strong>.</p>
43
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 24px; margin: 0; padding-bottom: 32px;">If you've been enjoying {{siteTitle}}, continue your membership for {{gift.priceAfter}} to keep full access to every post and newsletter.</p>
44
+ <table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
45
45
  <tbody>
46
46
  <tr>
47
- <td align="left" style="padding: 24px;">
48
- <table border="0" cellpadding="0" cellspacing="0">
49
- <tr>
50
- <td style="padding-right: 8px; background-color: #F4F5F6; text-align: left; vertical-align: middle;" valign="middle">
51
- <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; margin: 0; padding-bottom: 4px; color: #15171A; font-weight: 700;">Gift subscription</p>
52
- <p class="large" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; margin: 0; padding-bottom: 24px; color: #15171A; font-weight: 400;">{{gift.tierName}}</p>
53
- <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; margin: 0; padding-bottom: 4px; color: #15171A; font-weight: 700;">Ends on</p>
54
- <p class="large" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; margin: 0; padding-bottom: 24px; color: #15171A; font-weight: 400;">{{gift.consumesAt}}</p>
55
- <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; margin: 0; padding-bottom: 4px; color: #15171A; font-weight: 700;">Price after gift ends</p>
56
- <p class="large" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 18px; margin: 0; padding-bottom: 0; color: #15171A; font-weight: 400;">{{gift.priceAfter}}</p>
57
- </td>
58
- </tr>
59
- </table>
60
- <table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box; padding-top: 24px;">
47
+ <td align="left" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top;">
48
+ <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
61
49
  <tbody>
62
50
  <tr>
63
- <td align="left" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top;">
64
- <table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
65
- <tbody>
66
- <tr>
67
- <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; vertical-align: top; background-color: {{accentColor}}; border-radius: 8px; text-align: center;">
68
- <a href="{{gift.manageSubscriptionUrl}}" target="_blank" style="display: inline-block; color: #ffffff; background-color: {{accentColor}}; border: solid 1px {{accentColor}}; border-radius: 8px; padding: 10px 20px; text-decoration: none;">Continue subscription</a>
69
- </td>
70
- </tr>
71
- </tbody>
72
- </table>
51
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; background-color: {{accentColor}}; border-radius: 6px; text-align: center;">
52
+ <a href="{{gift.manageSubscriptionUrl}}" target="_blank" style="display: inline-block; color: #ffffff; background-color: {{accentColor}}; border: solid 1px {{accentColor}}; border-radius: 6px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 16px; font-weight: 600; margin: 0; padding: 12px 22px; border-color: {{accentColor}};">Continue membership</a>
73
53
  </td>
74
54
  </tr>
75
55
  </tbody>
@@ -83,13 +63,8 @@
83
63
 
84
64
  <!-- START FOOTER -->
85
65
  <tr>
86
- <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 12px; vertical-align: top; padding-top: 56px;">
87
- <p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 12px; color: #7C8B9A; font-weight: normal; margin: 0;">This message was sent from <a class="small" href="{{siteUrl}}" style="text-decoration: underline; color: #7C8B9A; font-size: 12px;">{{siteDomain}}</a> to <a class="small" href="mailto:{{memberEmail}}" style="text-decoration: underline; color: #7C8B9A; font-size: 12px;">{{memberEmail}}</a></p>
88
- </td>
89
- </tr>
90
- <tr>
91
- <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 12px; vertical-align: top;">
92
- <p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 12px; color: #7C8B9A; font-weight: normal; margin: 0;">You received this email because your gift subscription to <a class="small" href="{{siteUrl}}" style="text-decoration: underline; color: #7C8B9A; font-size: 12px;">{{siteTitle}}</a> is ending soon.</p>
66
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-top: 80px;">
67
+ <p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 16px; font-size: 11px; color: #738A94; font-weight: normal; margin: 0;">This message was sent from <a class="small" href="{{siteUrl}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">{{siteDomain}}</a> to <a class="small" href="mailto:{{memberEmail}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">{{memberEmail}}</a>.</p>
93
68
  </td>
94
69
  </tr>
95
70
 
@@ -2,19 +2,15 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderText = renderText;
4
4
  function renderText(data) {
5
- return `Your gift subscription is ending soon
5
+ return `Hey there,
6
6
 
7
- Your gift subscription to ${data.siteTitle} ends on ${data.gift.consumesAt}. Continue with a paid subscription to keep reading.
7
+ Your gift subscription expires on ${data.gift.consumesAt}.
8
8
 
9
- Gift subscription: ${data.gift.tierName}
10
- Ends on: ${data.gift.consumesAt}
11
- Price after gift ends: ${data.gift.priceAfter}
9
+ If you've been enjoying ${data.siteTitle}, continue your membership for ${data.gift.priceAfter} to keep full access to every post and newsletter.
12
10
 
13
- Continue subscription:
11
+ Continue membership:
14
12
  ${data.gift.manageSubscriptionUrl}
15
13
 
16
14
  ---
17
-
18
- Sent to ${data.memberEmail} from ${data.siteDomain}.
19
- You received this email because your gift subscription to ${data.siteTitle} is ending soon.`;
15
+ This message was sent from ${data.siteDomain} to ${data.memberEmail}.`;
20
16
  }
@@ -14,19 +14,15 @@ export interface GiftReminderData {
14
14
  }
15
15
 
16
16
  export function renderText(data: GiftReminderData): string {
17
- return `Your gift subscription is ending soon
17
+ return `Hey there,
18
18
 
19
- Your gift subscription to ${data.siteTitle} ends on ${data.gift.consumesAt}. Continue with a paid subscription to keep reading.
19
+ Your gift subscription expires on ${data.gift.consumesAt}.
20
20
 
21
- Gift subscription: ${data.gift.tierName}
22
- Ends on: ${data.gift.consumesAt}
23
- Price after gift ends: ${data.gift.priceAfter}
21
+ If you've been enjoying ${data.siteTitle}, continue your membership for ${data.gift.priceAfter} to keep full access to every post and newsletter.
24
22
 
25
- Continue subscription:
23
+ Continue membership:
26
24
  ${data.gift.manageSubscriptionUrl}
27
25
 
28
26
  ---
29
-
30
- Sent to ${data.memberEmail} from ${data.siteDomain}.
31
- You received this email because your gift subscription to ${data.siteTitle} is ending soon.`;
27
+ This message was sent from ${data.siteDomain} to ${data.memberEmail}.`;
32
28
  }
@@ -74,7 +74,7 @@ class GiftService {
74
74
  throw new errors_1.default.NotFoundError({ message: `Tier not found: ${data.tierId}` });
75
75
  }
76
76
  try {
77
- await this.deps.staffServiceEmails.notifyGiftReceived({
77
+ await this.deps.staffServiceEmails.notifyGiftPurchased({
78
78
  name: member?.get('name') ?? null,
79
79
  email: member?.get('email') ?? data.buyerEmail,
80
80
  memberId: member?.id ?? null,
@@ -80,7 +80,7 @@ interface GiftEmailService {
80
80
  }
81
81
 
82
82
  interface StaffServiceEmails {
83
- notifyGiftReceived(data: {
83
+ notifyGiftPurchased(data: {
84
84
  name: string | null;
85
85
  email: string;
86
86
  memberId: string | null;
@@ -208,7 +208,7 @@ export class GiftService {
208
208
  }
209
209
 
210
210
  try {
211
- await this.deps.staffServiceEmails.notifyGiftReceived({
211
+ await this.deps.staffServiceEmails.notifyGiftPurchased({
212
212
  name: member?.get('name') ?? null,
213
213
  email: member?.get('email') ?? data.buyerEmail,
214
214
  memberId: member?.id ?? null,
@@ -9,7 +9,6 @@ const emailAddressService = require('../email-address');
9
9
  const settingsHelpers = require('../settings-helpers');
10
10
  const EmailAddressParser = require('../email-address/email-address-parser');
11
11
  const mail = require('../mail');
12
- // @ts-expect-error type checker has trouble with the dynamic exporting in models
13
12
  const {WelcomeEmailAutomation, WelcomeEmailAutomatedEmail, Newsletter} = require('../../models');
14
13
  const MemberWelcomeEmailRenderer = require('./member-welcome-email-renderer');
15
14
  const {MEMBER_WELCOME_EMAIL_LOG_KEY, MEMBER_WELCOME_EMAIL_TAG, MEMBER_WELCOME_EMAIL_SLUGS, MESSAGES} = require('./constants');
@@ -65,6 +65,45 @@ function extractGiftToken(input) {
65
65
  return input.trim();
66
66
  }
67
67
 
68
+ /**
69
+ * Validate that a candidate return URL (e.g. Stripe Checkout success/cancel URL) points back
70
+ * to the configured Ghost site. Returns `undefined` when the candidate is missing, malformed,
71
+ * on a different origin, or outside of the site's subpath (for subpath installs) — leaving
72
+ * the downstream Stripe service to fall back to its configured default URL.
73
+ *
74
+ * This prevents the public checkout endpoints being abused as open-redirect surfaces.
75
+ *
76
+ * @param {string | undefined} candidate - URL provided in the request body
77
+ * @param {string} siteUrl - The site URL returned by urlUtils.getSiteUrl()
78
+ * @returns {string | undefined} The candidate URL if same-origin, otherwise undefined
79
+ */
80
+ function sanitizeReturnUrl(candidate, siteUrl) {
81
+ if (typeof candidate !== 'string' || candidate.length === 0) {
82
+ return undefined;
83
+ }
84
+
85
+ let site;
86
+ let url;
87
+
88
+ try {
89
+ site = new URL(siteUrl);
90
+ url = new URL(candidate);
91
+ } catch {
92
+ return undefined;
93
+ }
94
+
95
+ if (url.origin !== site.origin) {
96
+ return undefined;
97
+ }
98
+
99
+ // Normalize site/candidate paths to trailing-slash form so that /blog and /blog/
100
+ // are treated equivalently when the site is configured at a subpath.
101
+ const sitePath = site.pathname.endsWith('/') ? site.pathname : `${site.pathname}/`;
102
+ const urlPath = url.pathname.endsWith('/') ? url.pathname : `${url.pathname}/`;
103
+
104
+ return urlPath.startsWith(sitePath) ? url.href : undefined;
105
+ }
106
+
68
107
  module.exports = class RouterController {
69
108
  #inboxLinksDnsResolver = new dns.Resolver({maxTimeout: 1000});
70
109
 
@@ -202,9 +241,10 @@ module.exports = class RouterController {
202
241
  customer = await this._stripeAPIService.getCustomer(subscription.get('customer_id'));
203
242
  }
204
243
 
244
+ const siteUrl = this._urlUtils.getSiteUrl();
205
245
  const session = await this._stripeAPIService.createCheckoutSetupSession(customer, {
206
- successUrl: req.body.successUrl,
207
- cancelUrl: req.body.cancelUrl,
246
+ successUrl: sanitizeReturnUrl(req.body.successUrl, siteUrl),
247
+ cancelUrl: sanitizeReturnUrl(req.body.cancelUrl, siteUrl),
208
248
  subscription_id: req.body.subscription_id,
209
249
  currency
210
250
  });
@@ -267,8 +307,9 @@ module.exports = class RouterController {
267
307
 
268
308
  const configurationId = this._settingsCache.get('stripe_billing_portal_configuration_id');
269
309
 
310
+ const siteUrl = this._urlUtils.getSiteUrl();
270
311
  const session = await this._stripeAPIService.createBillingPortalSession(customer, {
271
- returnUrl: req.body.returnUrl,
312
+ returnUrl: sanitizeReturnUrl(req.body.returnUrl, siteUrl),
272
313
  ...(configurationId && {configurationId})
273
314
  });
274
315
  const sessionInfo = {
@@ -694,10 +735,14 @@ module.exports = class RouterController {
694
735
  metadata.newsletters = JSON.stringify(await this._validateNewsletters(JSON.parse(metadata.newsletters)));
695
736
  }
696
737
 
738
+ const siteUrl = this._urlUtils.getSiteUrl();
739
+ const successUrl = sanitizeReturnUrl(req.body.successUrl, siteUrl);
740
+ const cancelUrl = sanitizeReturnUrl(req.body.cancelUrl, siteUrl);
741
+
697
742
  // Build options
698
743
  const options = {
699
- successUrl: req.body.successUrl,
700
- cancelUrl: req.body.cancelUrl,
744
+ successUrl,
745
+ cancelUrl,
701
746
  email: req.body.customerEmail,
702
747
  member,
703
748
  metadata,
@@ -788,8 +833,8 @@ module.exports = class RouterController {
788
833
  ...options,
789
834
  ...data,
790
835
  duration: 1, // gifts are currently 1 month or 1 year only
791
- successUrl: this._urlUtils.getSiteUrl(),
792
- cancelUrl: this._urlUtils.getSiteUrl()
836
+ successUrl: siteUrl,
837
+ cancelUrl: options.cancelUrl || siteUrl
793
838
  });
794
839
  }
795
840
 
@@ -349,10 +349,6 @@ module.exports = class MemberBREADService {
349
349
  withRelated.add('productEvents');
350
350
  }
351
351
 
352
- if (withRelated.has('email_recipients')) {
353
- withRelated.add('email_recipients.email');
354
- }
355
-
356
352
  const model = await this.memberRepository.get(data, {
357
353
  ...options,
358
354
  withRelated: Array.from(withRelated)
@@ -610,10 +606,6 @@ module.exports = class MemberBREADService {
610
606
  withRelated.add('productEvents');
611
607
  }
612
608
 
613
- if (withRelated.has('email_recipients')) {
614
- withRelated.add('email_recipients.email');
615
- }
616
-
617
609
  //option param to skip distinct from count query, distinct adds a lot of latency and in this case the result set will always be unique.
618
610
  options.useBasicCount = true;
619
611
 
@@ -95,10 +95,20 @@ class RouteSettings {
95
95
  await this.createBackupFile(this.settingsPath, this.backupPath);
96
96
  await this.saveFile(filePath, this.settingsPath);
97
97
 
98
- urlService.resetGenerators({releaseResourcesOnly: true});
98
+ const isLazy = urlService.facade.isLazy();
99
+ const resetUrlState = () => {
100
+ if (isLazy) {
101
+ // Drop registered router configs; reloadFrontend re-registers them.
102
+ urlService.facade.reset();
103
+ } else {
104
+ urlService.resetGenerators({releaseResourcesOnly: true});
105
+ }
106
+ };
107
+
108
+ resetUrlState();
99
109
 
100
110
  const bringBackValidRoutes = async () => {
101
- urlService.resetGenerators({releaseResourcesOnly: true});
111
+ resetUrlState();
102
112
 
103
113
  await this.restoreBackupFile(this.settingsPath, this.backupPath);
104
114
 
@@ -114,6 +124,12 @@ class RouteSettings {
114
124
  });
115
125
  }
116
126
 
127
+ // The lazy URL service has nothing to precompute, so the readiness
128
+ // poll below is unnecessary; hasFinished() returns true immediately.
129
+ if (isLazy) {
130
+ return;
131
+ }
132
+
117
133
  // @TODO: how can we get rid of this from here?
118
134
  let tries = 0;
119
135
 
@@ -28,7 +28,7 @@
28
28
  {{/if}}
29
29
  <tr>
30
30
  <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
31
- <h1 style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 26px; color: #15212A; font-weight: bold; line-height: 28px; margin: 0; padding-bottom: 24px;">Someone purchased a gift&nbsp;subscription</h1>
31
+ <h1 style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 26px; color: #15212A; font-weight: bold; line-height: 28px; margin: 0; padding-bottom: 24px;">A gift subscription was&nbsp;purchased</h1>
32
32
  <table width="100" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; table-layout: fixed; width: 100%; min-width: 100%; box-sizing: border-box; background: #F4F5F6; border-radius: 8px;">
33
33
  <tbody>
34
34
  <tr>
@@ -1,7 +1,7 @@
1
1
  module.exports = function giftText(data) {
2
2
  // Be careful when you indent the email, because whitespaces are visible in emails!
3
3
  return `
4
- Someone purchased a gift subscription
4
+ A gift subscription was purchased
5
5
 
6
6
  From: ${data.gift.name}
7
7
  Tier: ${data.gift.tierName} • ${data.gift.cadenceLabel}
@@ -33,7 +33,7 @@
33
33
  {{/if}}
34
34
  <tr>
35
35
  <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
36
- <h1 style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 26px; color: #15212A; font-weight: bold; line-height: 28px; margin: 0; padding-bottom: 24px;">Someone redeemed a gift&nbsp;subscription</h1>
36
+ <h1 style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 26px; color: #15212A; font-weight: bold; line-height: 28px; margin: 0; padding-bottom: 24px;">A gift subscription was&nbsp;redeemed</h1>
37
37
  <table width="100" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; table-layout: fixed; width: 100%; min-width: 100%; box-sizing: border-box; background: #F4F5F6; border-radius: 8px;">
38
38
  <tbody>
39
39
  <tr>
@@ -1,7 +1,7 @@
1
1
  module.exports = function (data) {
2
2
  // Be careful when you indent the email, because whitespaces are visible in emails!
3
3
  return `
4
- Someone redeemed a gift subscription
4
+ A gift subscription was redeemed
5
5
 
6
6
  Member: ${data.memberData.name}
7
7
  Tier: ${data.tierData.name}${data.tierData.details ? ` • ${data.tierData.details}` : ''}
@@ -317,7 +317,7 @@ class StaffServiceEmails {
317
317
  *
318
318
  * @returns {Promise<void>}
319
319
  */
320
- async notifyGiftReceived({name, email, memberId, amount, currency, tierName, cadence, duration}) {
320
+ async notifyGiftPurchased({name, email, memberId, amount, currency, tierName, cadence, duration}) {
321
321
  const users = await this.models.User.getEmailAlertUsers('gift-subscriptions');
322
322
  const formattedAmount = this.getFormattedAmount({currency, amount: amount / 100});
323
323
 
@@ -2,6 +2,7 @@ const config = require('../../../shared/config');
2
2
  const LocalFileCache = require('./local-file-cache');
3
3
  const UrlService = require('./url-service');
4
4
  const UrlServiceFacade = require('./url-service-facade');
5
+ const LazyUrlService = require('./lazy-url-service');
5
6
 
6
7
  // NOTE: instead of a path we could give UrlService a "data-resolver" of some sort
7
8
  // so it doesn't have to contain the logic to read data at all. This would be
@@ -22,7 +23,21 @@ if (process.env.NODE_ENV.startsWith('test')){
22
23
 
23
24
  const cache = new LocalFileCache({storagePath, writeDisabled});
24
25
  const urlService = new UrlService({cache});
25
- const urlServiceFacade = new UrlServiceFacade({urlService});
26
+
27
+ // LazyUrlService is only attached to the facade when the lazyRouting flag is
28
+ // on. The eager UrlService stays in the require graph either way so test code
29
+ // and partially-migrated callers continue to work. The model layer is only
30
+ // pulled in when the flag is on so flag-off boot keeps its existing
31
+ // require-graph shape.
32
+ let lazyUrlService = null;
33
+ if (config.get('lazyRouting')) {
34
+ const models = require('../../models');
35
+ const {createFindResource} = require('./lazy-find-resource');
36
+
37
+ lazyUrlService = new LazyUrlService({findResource: createFindResource(models)});
38
+ }
39
+
40
+ const urlServiceFacade = new UrlServiceFacade({urlService, lazyUrlService});
26
41
 
27
42
  // Singleton: default export remains the eager UrlService for backwards
28
43
  // compatibility with existing imports. The new facade is exposed alongside
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createFindResource = createFindResource;
4
+ /**
5
+ * Builds the on-demand DB lookup hook used by LazyUrlService.resolveUrl.
6
+ *
7
+ * The eager UrlService only registers URLs for resources matching the
8
+ * resourceConfig filter (status:published, public visibility). Mirror that
9
+ * here so reverse lookups can't return drafts / private resources even though
10
+ * we now hit the DB on demand. We also disambiguate posts vs pages — both
11
+ * share `models.Post` and a permalink template like `/:slug/` could otherwise
12
+ * return either record.
13
+ *
14
+ * For tags and authors we use the public-facing scoped models (TagPublic,
15
+ * Author) which carry the shouldHavePosts gate, so reverse lookups can't
16
+ * return tags/users with no published posts (and in particular can't expose
17
+ * staff User accounts via a guessed slug).
18
+ */
19
+ function createFindResource(models) {
20
+ const loadOne = async (Model, query, options = {}) => {
21
+ const result = await Model.findOne(query, { require: false, ...options });
22
+ return result ? result.toJSON() : null;
23
+ };
24
+ // Posts and pages need their tags/authors relations loaded so NQL
25
+ // filters like `tag:news` / `author:jane` can be evaluated against the
26
+ // returned record (LazyUrlService re-checks the route's filter after
27
+ // the DB lookup). Without these, every tag- or author-filtered route
28
+ // would silently 404.
29
+ const POSTLIKE_RELATIONS = ['tags', 'authors'];
30
+ const loadPostlike = (Model, query, dbType) => {
31
+ return loadOne(Model, { ...query, type: dbType, status: 'published' }, { withRelated: POSTLIKE_RELATIONS });
32
+ };
33
+ return (type, query) => {
34
+ if (type === 'posts') {
35
+ return loadPostlike(models.Post, query, 'post');
36
+ }
37
+ if (type === 'pages') {
38
+ return loadPostlike(models.Post, query, 'page');
39
+ }
40
+ if (type === 'tags') {
41
+ return loadOne(models.TagPublic, { ...query, visibility: 'public' });
42
+ }
43
+ if (type === 'authors') {
44
+ return loadOne(models.Author, { ...query, visibility: 'public' });
45
+ }
46
+ return Promise.resolve(null);
47
+ };
48
+ }
49
+ module.exports = { createFindResource };
@@ -0,0 +1,62 @@
1
+ import type {FindResource} from './lazy-url-service';
2
+
3
+ interface BookshelfModel {
4
+ findOne(query: Record<string, unknown>, options?: Record<string, unknown>): Promise<{toJSON(): Record<string, unknown>} | null>;
5
+ }
6
+
7
+ interface Models {
8
+ Post: BookshelfModel;
9
+ TagPublic: BookshelfModel;
10
+ Author: BookshelfModel;
11
+ }
12
+
13
+ /**
14
+ * Builds the on-demand DB lookup hook used by LazyUrlService.resolveUrl.
15
+ *
16
+ * The eager UrlService only registers URLs for resources matching the
17
+ * resourceConfig filter (status:published, public visibility). Mirror that
18
+ * here so reverse lookups can't return drafts / private resources even though
19
+ * we now hit the DB on demand. We also disambiguate posts vs pages — both
20
+ * share `models.Post` and a permalink template like `/:slug/` could otherwise
21
+ * return either record.
22
+ *
23
+ * For tags and authors we use the public-facing scoped models (TagPublic,
24
+ * Author) which carry the shouldHavePosts gate, so reverse lookups can't
25
+ * return tags/users with no published posts (and in particular can't expose
26
+ * staff User accounts via a guessed slug).
27
+ */
28
+ export function createFindResource(models: Models): FindResource {
29
+ const loadOne = async (Model: BookshelfModel, query: Record<string, unknown>, options: Record<string, unknown> = {}) => {
30
+ const result = await Model.findOne(query, {require: false, ...options});
31
+ return result ? result.toJSON() : null;
32
+ };
33
+
34
+ // Posts and pages need their tags/authors relations loaded so NQL
35
+ // filters like `tag:news` / `author:jane` can be evaluated against the
36
+ // returned record (LazyUrlService re-checks the route's filter after
37
+ // the DB lookup). Without these, every tag- or author-filtered route
38
+ // would silently 404.
39
+ const POSTLIKE_RELATIONS = ['tags', 'authors'];
40
+
41
+ const loadPostlike = (Model: BookshelfModel, query: Record<string, unknown>, dbType: string) => {
42
+ return loadOne(Model, {...query, type: dbType, status: 'published'}, {withRelated: POSTLIKE_RELATIONS});
43
+ };
44
+
45
+ return (type: string, query: Record<string, string>) => {
46
+ if (type === 'posts') {
47
+ return loadPostlike(models.Post, query, 'post');
48
+ }
49
+ if (type === 'pages') {
50
+ return loadPostlike(models.Post, query, 'page');
51
+ }
52
+ if (type === 'tags') {
53
+ return loadOne(models.TagPublic, {...query, visibility: 'public'});
54
+ }
55
+ if (type === 'authors') {
56
+ return loadOne(models.Author, {...query, visibility: 'public'});
57
+ }
58
+ return Promise.resolve(null);
59
+ };
60
+ }
61
+
62
+ module.exports = {createFindResource};