ghost 6.17.2 → 6.18.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 (211) hide show
  1. package/components/{tryghost-i18n-6.17.2.tgz → tryghost-i18n-6.18.1.tgz} +0 -0
  2. package/components/{tryghost-parse-email-address-6.17.2.tgz → tryghost-parse-email-address-6.18.1.tgz} +0 -0
  3. package/core/built/admin/assets/{PolarAngleAxis-B8JrAxf_.js → PolarAngleAxis-Dod0DwfL.js} +1 -1
  4. package/core/built/admin/assets/{_baseAssignValue-Bbalamaq.js → _baseAssignValue-DnkbkowM.js} +1 -1
  5. package/core/built/admin/assets/{a-large-small-CgWPeKpi.js → a-large-small-C5mgFBRg.js} +1 -1
  6. package/core/built/admin/assets/activitypub/activitypub.js +1 -1
  7. package/core/built/admin/assets/activitypub/{at-sign-Fsk3x72r.mjs → at-sign-BR2C2gdz.mjs} +2 -2
  8. package/core/built/admin/assets/activitypub/{avatar-flipboard-C10JfFS_.mjs → avatar-flipboard-CJV3KhLU.mjs} +2 -2
  9. package/core/built/admin/assets/activitypub/{bluesky-sharing-C1xeGSL6.mjs → bluesky-sharing-DY4KCGPZ.mjs} +4 -4
  10. package/core/built/admin/assets/activitypub/{copy-C1fElSkQ.mjs → copy-CBFwbAdE.mjs} +2 -2
  11. package/core/built/admin/assets/activitypub/{deleted-feed-item-Bun4tY2_.mjs → deleted-feed-item-DOm9GxTQ.mjs} +2 -2
  12. package/core/built/admin/assets/activitypub/{edit-profile-CYh00FZ7.mjs → edit-profile-CcbnZWkM.mjs} +3 -3
  13. package/core/built/admin/assets/activitypub/{feed-BxUqmcN9.mjs → feed-BHiZU0Q_.mjs} +4 -4
  14. package/core/built/admin/assets/activitypub/{hash-CNgwAx-U.mjs → hash-Csjloy5t.mjs} +2 -2
  15. package/core/built/admin/assets/activitypub/{inbox-DqNqII4a.mjs → inbox-DGKbBux9.mjs} +2 -2
  16. package/core/built/admin/assets/activitypub/{index-CONoLlDU.mjs → index-Bt_qFJNY.mjs} +2 -2
  17. package/core/built/admin/assets/activitypub/{index--Q6orQkb.mjs → index-CCYuVHjm.mjs} +21 -21
  18. package/core/built/admin/assets/activitypub/{index-jhjmoHwu.mjs → index-CTV39jCH.mjs} +2 -2
  19. package/core/built/admin/assets/activitypub/{index-Dvh9q3jy.mjs → index-ClNx7qzi.mjs} +4 -4
  20. package/core/built/admin/assets/activitypub/{index-C9pnotJK.mjs → index-D6Y6ywyl.mjs} +6 -6
  21. package/core/built/admin/assets/activitypub/{index-BueIufRq.mjs → index-YLvIz6pJ.mjs} +5 -5
  22. package/core/built/admin/assets/activitypub/{index-C3KJXzZE.mjs → index-tWANcGjH.mjs} +7 -7
  23. package/core/built/admin/assets/activitypub/{index-CJJXnqq1.mjs → index-vVqFEPIX.mjs} +3 -3
  24. package/core/built/admin/assets/activitypub/{moderation-CYhwUFi2.mjs → moderation-CUGqRfT1.mjs} +3 -3
  25. package/core/built/admin/assets/activitypub/{note-COVa8CMw.mjs → note-DMy39JHG.mjs} +4 -4
  26. package/core/built/admin/assets/activitypub/{reply-BHpKVBxx.mjs → reply-BboFcDsv.mjs} +2 -2
  27. package/core/built/admin/assets/activitypub/{separator-DP7q5sFH.mjs → separator-DBvIXQnV.mjs} +2 -2
  28. package/core/built/admin/assets/activitypub/{settings-3n7zo_3K.mjs → settings-D4qRhs9E.mjs} +3 -3
  29. package/core/built/admin/assets/activitypub/{step-1-BmUukywZ.mjs → step-1-C-ubwwIT.mjs} +3 -3
  30. package/core/built/admin/assets/activitypub/{step-2-C--I3xxp.mjs → step-2-Dsd2XY1_.mjs} +5 -5
  31. package/core/built/admin/assets/activitypub/{step-3-0Deh5N9c.mjs → step-3-BHz3ow0t.mjs} +7 -7
  32. package/core/built/admin/assets/activitypub/{tabs-D_vmoLBo.mjs → tabs-s_P4XeyF.mjs} +3 -3
  33. package/core/built/admin/assets/activitypub/{topic-filter-DJMrhH-c.mjs → topic-filter-CCf15CR-.mjs} +2 -2
  34. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
  35. package/core/built/admin/assets/admin-x-settings/{code-editor-view-BzvYs1fs.mjs → code-editor-view-BWi3-ftq.mjs} +2 -2
  36. package/core/built/admin/assets/admin-x-settings/{index-DdMo_pvD.mjs → index-B3PtvbUw.mjs} +2 -2
  37. package/core/built/admin/assets/admin-x-settings/{index-DYEF3NiM.mjs → index-Did70N9h.mjs} +2 -2
  38. package/core/built/admin/assets/admin-x-settings/{index-BXChUyr6.mjs → index-JaMlaX9G.mjs} +30 -19
  39. package/core/built/admin/assets/admin-x-settings/{modals-Ck3AHDx3.mjs → modals-Bwrf9ptQ.mjs} +8060 -8067
  40. package/core/built/admin/assets/{at-sign-BottTo2X.js → at-sign-Bz-SU-S_.js} +1 -1
  41. package/core/built/admin/assets/{audience-CcGOt5Fb.js → audience-DmVdEmIe.js} +1 -1
  42. package/core/built/admin/assets/{avatar-flipboard-MxW2k124.js → avatar-flipboard-C7sxDVEM.js} +1 -1
  43. package/core/built/admin/assets/{bluesky-sharing-BBmDBlxr.js → bluesky-sharing-DJniSF6N.js} +1 -1
  44. package/core/built/admin/assets/{chart-pimL5Chk.js → chart-mYz3IJwm.js} +1 -1
  45. package/core/built/admin/assets/{chunk.524.9a8f5123fd3ecf3e4451.js → chunk.524.9d300778a63b42b0de62.js} +7 -7
  46. package/core/built/admin/assets/{chunk.582.41886d06af2895d571bd.js → chunk.582.2e363cd976d9bba998b9.js} +10 -10
  47. package/core/built/admin/assets/{code-editor-view-tCZOpc_h.js → code-editor-view-BIQWFJ01.js} +1 -1
  48. package/core/built/admin/assets/comments-B_-hdjc6.js +1 -0
  49. package/core/built/admin/assets/{copy-aC0WUVtq.js → copy-Bpq3uhnh.js} +1 -1
  50. package/core/built/admin/assets/{data-list-BOdyZGWI.js → data-list-BYNMbRIq.js} +1 -1
  51. package/core/built/admin/assets/{deleted-feed-item-BnUJ8bWP.js → deleted-feed-item-B_AwUKZy.js} +1 -1
  52. package/core/built/admin/assets/{edit-profile-CEgPhWMJ.js → edit-profile-DZo2ZcOu.js} +1 -1
  53. package/core/built/admin/assets/{empty-indicator-CR_9BHAH.js → empty-indicator-Bn5wG9-T.js} +1 -1
  54. package/core/built/admin/assets/{en-ajofhc7o.js → en-D4zIrMLN.js} +1 -1
  55. package/core/built/admin/assets/{feed-Ci-ODCHE.js → feed-Bgq4RarU.js} +1 -1
  56. package/core/built/admin/assets/{filters-CmJJOv3I.js → filters-CpVwu1Gk.js} +1 -1
  57. package/core/built/admin/assets/{gh-chart-hn7wEnik.js → gh-chart-Br7NXn_b.js} +1 -1
  58. package/core/built/admin/assets/{ghost-78b420974adc056bf1c9570275a46535.css → ghost-3c48d37b32f4fcd7eb15cfd739905a03.css} +1 -1
  59. package/core/built/admin/assets/{ghost-a9268074d0a9b02198594a05ecd7a626.js → ghost-98a3c5fd6235ebd344594f491cb6d17b.js} +241 -246
  60. package/core/built/admin/assets/{ghost-dark-12abca67e79c76fe6bfed6cb81ba2a53.css → ghost-dark-0100fe3ec7b4c3f6d8d043c904254a63.css} +1 -1
  61. package/core/built/admin/assets/{growth-CQceZQ1g.js → growth-CiPDnWxJ.js} +1 -1
  62. package/core/built/admin/assets/{hash-BcaWmkw5.js → hash-Bc1e38oH.js} +1 -1
  63. package/core/built/admin/assets/{inbox-D9hID-rO.js → inbox-BcZNBNhk.js} +1 -1
  64. package/core/built/admin/assets/{index-d_UH71zm.js → index-BFEx_ouC.js} +1 -1
  65. package/core/built/admin/assets/{index-kXF42nga.js → index-B_7QsC5T.js} +1 -1
  66. package/core/built/admin/assets/{index-CZ3lARQ2.js → index-Bm5ZeLtt.js} +1 -1
  67. package/core/built/admin/assets/{index-DJDgMk-T.js → index-BpcL7RmI.js} +2 -2
  68. package/core/built/admin/assets/{index-g4BWcHhl.js → index-Brg4tecQ.js} +1 -1
  69. package/core/built/admin/assets/{index-f5Lv9kLb.js → index-CMTzNTew.js} +29 -29
  70. package/core/built/admin/assets/{index-Cl2eSBwg.js → index-CTfJflJ2.js} +1 -1
  71. package/core/built/admin/assets/{index-BJS8H75W.js → index-Cn32t_gv.js} +1 -1
  72. package/core/built/admin/assets/{index-D8syEvSm.js → index-CsidX7Si.js} +1 -1
  73. package/core/built/admin/assets/{index-DO76vL0R.js → index-D212VvOz.js} +1 -1
  74. package/core/built/admin/assets/{index-B1rKIiOr.js → index-D67LJ_H4.js} +1 -1
  75. package/core/built/admin/assets/{index-BmK6ThYl.js → index-DU3bv9jz.js} +1 -1
  76. package/core/built/admin/assets/{index-DYM1MbXR.js → index-Dv13wKfX.js} +1 -1
  77. package/core/built/admin/assets/{index-Do091GLu.js → index-ETsoQLbU.js} +1 -1
  78. package/core/built/admin/assets/{index-rluYcfd-.js → index-ZYDRMgcT.js} +1 -1
  79. package/core/built/admin/assets/{koenig-lexical-D2XHvS2J.js → koenig-lexical-DZUWzN0P.js} +1 -1
  80. package/core/built/admin/assets/{kpi-card-Dga9rFZ5.js → kpi-card-DENsK2xK.js} +1 -1
  81. package/core/built/admin/assets/{kpis-BbnqD1za.js → kpis-CDrs2iS1.js} +1 -1
  82. package/core/built/admin/assets/{label-D8WHEOe0.js → label-BznQtEEo.js} +1 -1
  83. package/core/built/admin/assets/{links-dgwx7ko1.js → links-lpAC3T1p.js} +1 -1
  84. package/core/built/admin/assets/{lucide-react-BARFK2PZ.js → lucide-react-CBigk-fq.js} +1 -1
  85. package/core/built/admin/assets/{main-layout-CcjtCuF_.js → main-layout-_SYQRjIl.js} +1 -1
  86. package/core/built/admin/assets/{message-square-text-BxquqPhA.js → message-square-text-cV8O_qKq.js} +1 -1
  87. package/core/built/admin/assets/{minus-Ziy4LSYD.js → minus-NvnQTlW7.js} +1 -1
  88. package/core/built/admin/assets/{modals-fMQzR7L6.js → modals-XRSkribf.js} +3 -3
  89. package/core/built/admin/assets/{moderation-Ci9UNwiZ.js → moderation-BQp1GEWG.js} +1 -1
  90. package/core/built/admin/assets/{newsletter-DnrS7Fvi.js → newsletter-foM6KNNV.js} +1 -1
  91. package/core/built/admin/assets/{newsletters-D78pgnas.js → newsletters-BexdXUhn.js} +1 -1
  92. package/core/built/admin/assets/{note-BAfNweZN.js → note-DuaUGOeZ.js} +1 -1
  93. package/core/built/admin/assets/{overview-CWQPNOF0.js → overview-DgKBNqyc.js} +1 -1
  94. package/core/built/admin/assets/{pagemenu-DjSqq3Wu.js → pagemenu-CZyroidv.js} +1 -1
  95. package/core/built/admin/assets/{post-analytics-c8kRV0YB.js → post-analytics-DLK2SOSQ.js} +1 -1
  96. package/core/built/admin/assets/{post-analytics-context-CEqWyqgm.js → post-analytics-context-CF7C67-0.js} +1 -1
  97. package/core/built/admin/assets/{post-analytics-header-CpPPBAz4.js → post-analytics-header-7GtJCx0W.js} +1 -1
  98. package/core/built/admin/assets/{post-share-modal-Dln0kZhn.js → post-share-modal-D8R7DUZP.js} +1 -1
  99. package/core/built/admin/assets/posts/{comments-CHEqbZPq.mjs → comments-Bn_vn_sb.mjs} +434 -407
  100. package/core/built/admin/assets/posts/{dialog-VFomJYla.mjs → dialog-CKPcMrj7.mjs} +3 -3
  101. package/core/built/admin/assets/posts/{empty-indicator-CT9qQsMO.mjs → empty-indicator-kkrhP6K_.mjs} +2 -2
  102. package/core/built/admin/assets/posts/{filters-BXCfSKKS.mjs → filters-DHsxxy0F.mjs} +6 -6
  103. package/core/built/admin/assets/posts/{growth-xMitvyvt.mjs → growth-D7ACh-oR.mjs} +12 -12
  104. package/core/built/admin/assets/posts/{heading-DIulQwgD.mjs → heading-BrKVbOxA.mjs} +2 -2
  105. package/core/built/admin/assets/posts/{hooks-BiBMDYOA.mjs → hooks-BBEMuLiW.mjs} +3 -3
  106. package/core/built/admin/assets/posts/{index-uYyMBANY.mjs → index-C0rRguZx.mjs} +9 -9
  107. package/core/built/admin/assets/posts/{kpis-DK6j0CJA.mjs → kpis-1o524OIK.mjs} +10 -10
  108. package/core/built/admin/assets/posts/{links-CBiYZsyk.mjs → links-JpeATx1f.mjs} +4 -4
  109. package/core/built/admin/assets/posts/{loading-indicator-BPPeKpVb.mjs → loading-indicator-BHAmSf8j.mjs} +3 -3
  110. package/core/built/admin/assets/posts/{main-layout-f-PcNn-6.mjs → main-layout-BTItAOQE.mjs} +2 -2
  111. package/core/built/admin/assets/posts/{newsletter-CSOyAfvO.mjs → newsletter-CQdsCEHv.mjs} +13 -13
  112. package/core/built/admin/assets/posts/{overview-Dve3Uxt4.mjs → overview-sYh4JN1D.mjs} +15 -15
  113. package/core/built/admin/assets/posts/{post-analytics-CPKxH7yg.mjs → post-analytics-DtLt2SWx.mjs} +6 -6
  114. package/core/built/admin/assets/posts/{post-analytics-context-B5zQoflZ.mjs → post-analytics-context-CB1yM9fk.mjs} +4 -4
  115. package/core/built/admin/assets/posts/{post-analytics-header-BBxuaptK.mjs → post-analytics-header-BYaWuL9W.mjs} +9 -9
  116. package/core/built/admin/assets/posts/{post-share-modal-D_iMXvnI.mjs → post-share-modal-V0HTOBaf.mjs} +4 -4
  117. package/core/built/admin/assets/posts/{posts-DW6ne0od.mjs → posts-DeQT3knv.mjs} +2 -2
  118. package/core/built/admin/assets/posts/posts.js +1 -1
  119. package/core/built/admin/assets/posts/{search-CVlPqGMA.mjs → search-CLjC37AT.mjs} +2 -2
  120. package/core/built/admin/assets/posts/{separator-YrJkDGVP.mjs → separator-KNoTIaJx.mjs} +5 -5
  121. package/core/built/admin/assets/posts/{sheet-T5GWxxrn.mjs → sheet-DBg_SCDt.mjs} +3 -3
  122. package/core/built/admin/assets/posts/{skeleton-qnvlkDy-.mjs → skeleton-DvsoolWu.mjs} +3 -3
  123. package/core/built/admin/assets/posts/{source-icon-DBU0eZwS.mjs → source-icon-OmRaTU2G.mjs} +3 -3
  124. package/core/built/admin/assets/posts/{stats-udwJVZP8.mjs → stats-BC2DzntY.mjs} +4 -4
  125. package/core/built/admin/assets/posts/{table-Cu0BZfBU.mjs → table-CxX9OKAj.mjs} +2 -2
  126. package/core/built/admin/assets/posts/{tabs-B-RaoD3I.mjs → tabs-B1jw7cBi.mjs} +10 -10
  127. package/core/built/admin/assets/posts/{tags-DSDGzArh.mjs → tags-BBilTo1a.mjs} +11 -11
  128. package/core/built/admin/assets/posts/{tags-DByfrDKO.mjs → tags-BitrLT_j.mjs} +2 -2
  129. package/core/built/admin/assets/posts/{use-infinite-virtual-scroll-B7dD2nWW.mjs → use-infinite-virtual-scroll-DaijA1ao.mjs} +3 -3
  130. package/core/built/admin/assets/posts/{web-B9wvEtJ_.mjs → web-CKmyC4Xj.mjs} +15 -15
  131. package/core/built/admin/assets/{posts-C1Eh5Ypq.js → posts-CL9UDYoW.js} +1 -1
  132. package/core/built/admin/assets/{repeat-CN4fiG5H.js → repeat-DgH39UKE.js} +1 -1
  133. package/core/built/admin/assets/{reply-BfWgvH7y.js → reply-DAaNxiy8.js} +1 -1
  134. package/core/built/admin/assets/{select-B_0NBkLz.js → select-Cor2wFXT.js} +1 -1
  135. package/core/built/admin/assets/{settings-BqaVcJee.js → settings-BeumESEN.js} +5 -5
  136. package/core/built/admin/assets/{settings-Dk9E7Fqv.js → settings-xRx917Gj.js} +1 -1
  137. package/core/built/admin/assets/{sort-button-C2M35dLh.js → sort-button-BNW3i4Lb.js} +1 -1
  138. package/core/built/admin/assets/{source-icon-DkGi13e_.js → source-icon-DvDuzw73.js} +1 -1
  139. package/core/built/admin/assets/{sprout-BKiwQsGn.js → sprout-C3cc0c-K.js} +1 -1
  140. package/core/built/admin/assets/{square-BEpRc_fY.js → square-tZp0_n7e.js} +1 -1
  141. package/core/built/admin/assets/stats/{audience-Dfo7BmNu.mjs → audience-BWqU7WWT.mjs} +3 -3
  142. package/core/built/admin/assets/stats/{index-DUWoiDE_.mjs → index-CUuQaROI.mjs} +5 -5
  143. package/core/built/admin/assets/stats/{index-Yo58temM.mjs → index-CcCyLMxL.mjs} +7 -7
  144. package/core/built/admin/assets/stats/{index-h0rsyxDz.mjs → index-D5mlMG4l.mjs} +8 -8
  145. package/core/built/admin/assets/stats/{index-_vL3Zubi.mjs → index-DXU2rE9t.mjs} +5 -5
  146. package/core/built/admin/assets/stats/{index-Banm1wtA.mjs → index-K7ASx7EG.mjs} +6 -6
  147. package/core/built/admin/assets/stats/{sort-button-i6PS-TUn.mjs → sort-button-CELUx6Zp.mjs} +3 -3
  148. package/core/built/admin/assets/stats/{stats-CjepXEWS.mjs → stats-d_u_in4l.mjs} +2 -2
  149. package/core/built/admin/assets/stats/stats.js +2 -2
  150. package/core/built/admin/assets/stats/{tabs-Q20S1oup.mjs → tabs-3wLZsy0v.mjs} +3 -3
  151. package/core/built/admin/assets/stats/{url-helpers-DrGoeEH1.mjs → url-helpers-Drq3xg0l.mjs} +4 -4
  152. package/core/built/admin/assets/stats/{use-growth-stats-BnffY2W3.mjs → use-growth-stats-28Sr42va.mjs} +3 -3
  153. package/core/built/admin/assets/{stats-CRgy_cWm.js → stats-2Jelnn-Q.js} +1 -1
  154. package/core/built/admin/assets/{stats-view-CTQ4qO3S.js → stats-view-CESy8ELH.js} +1 -1
  155. package/core/built/admin/assets/{step-1-CNbdmB8C.js → step-1-DrqdolAh.js} +1 -1
  156. package/core/built/admin/assets/{step-2-Ddrl2uae.js → step-2-DmEpKck5.js} +1 -1
  157. package/core/built/admin/assets/{step-3-CbqkEtwh.js → step-3-Bus-0o0n.js} +1 -1
  158. package/core/built/admin/assets/{table-Dw9xFlFd.js → table-BQUcKHfm.js} +1 -1
  159. package/core/built/admin/assets/{tabs-DCHagOip.js → tabs-BmdL0X4U.js} +1 -1
  160. package/core/built/admin/assets/{tags-DV2SSdMK.js → tags-CLxXZlOO.js} +1 -1
  161. package/core/built/admin/assets/{tags-BsDlfsPN.js → tags-EchqlZUJ.js} +1 -1
  162. package/core/built/admin/assets/{tiers-tc5tafWV.js → tiers-nCGyTly9.js} +1 -1
  163. package/core/built/admin/assets/{toggle-group-DS-ZVhCr.js → toggle-group-CM5uf7J1.js} +1 -1
  164. package/core/built/admin/assets/{topic-filter-BfoEqHRD.js → topic-filter-LTRvZ8aU.js} +1 -1
  165. package/core/built/admin/assets/{trash-CpnxYen-.js → trash-u5BxolyH.js} +1 -1
  166. package/core/built/admin/assets/{url-helpers-wKPPeCtD.js → url-helpers-D41fEt51.js} +1 -1
  167. package/core/built/admin/assets/{use-growth-stats-CbHIETF_.js → use-growth-stats-BJ0O9ewi.js} +1 -1
  168. package/core/built/admin/assets/{use-infinite-virtual-scroll-DU1EUhyj.js → use-infinite-virtual-scroll-APZWciOk.js} +1 -1
  169. package/core/built/admin/assets/{use-simple-pagination-X6kmRz2u.js → use-simple-pagination-DVRHeaAR.js} +1 -1
  170. package/core/built/admin/assets/{user-round-check-DYkrjHxF.js → user-round-check-B6j98D6d.js} +1 -1
  171. package/core/built/admin/assets/{wallet-cards-CSdWOmJE.js → wallet-cards-KmOh29LP.js} +1 -1
  172. package/core/built/admin/assets/{web-C39IKnvl.js → web-Cclotbnz.js} +1 -1
  173. package/core/built/admin/index.html +5 -5
  174. package/core/frontend/meta/asset-url.js +126 -15
  175. package/core/frontend/services/asset-hash/index.js +73 -0
  176. package/core/frontend/services/theme-engine/active.js +4 -2
  177. package/core/server/api/endpoints/utils/serializers/output/utils/post-gating.js +6 -0
  178. package/core/server/data/migrations/versions/6.17/2026-02-04-10-42-20-offers-nullable-product-id.js +11 -0
  179. package/core/server/data/migrations/versions/6.18/2026-02-04-10-42-20-offers-nullable-product-id.js +4 -0
  180. package/core/server/data/schema/schema.js +1 -1
  181. package/core/server/data/tinybird/datasources/_mv_daily_pages.datasource +13 -0
  182. package/core/server/data/tinybird/endpoints/api_top_pages_v3.pipe +113 -0
  183. package/core/server/data/tinybird/pipes/mv_daily_pages.pipe +23 -0
  184. package/core/server/data/tinybird/scripts/benchmark-top-pages.sh +186 -0
  185. package/core/server/data/tinybird/scripts/compare-top-pages.sh +262 -0
  186. package/core/server/data/tinybird/tests/api_top_pages_v3.yaml +112 -0
  187. package/core/server/models/automated-email.js +34 -0
  188. package/core/server/services/koenig/node-renderers/transistor-renderer.js +3 -8
  189. package/core/server/services/lib/member-signup-contexts.js +23 -0
  190. package/core/server/services/lib/member-signup-contexts.ts +22 -0
  191. package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +13 -7
  192. package/core/server/services/member-welcome-emails/service.js +84 -15
  193. package/core/server/services/members/members-api/controllers/router-controller.js +14 -0
  194. package/core/server/services/members/members-api/repositories/member-repository.js +1 -1
  195. package/core/server/services/members/members-api/services/payments-service.js +5 -0
  196. package/core/server/services/oembed/oembed-service.js +1 -1
  197. package/core/server/services/offers/application/offer-mapper.js +6 -7
  198. package/core/server/services/offers/application/offers-api.js +3 -3
  199. package/core/server/services/offers/domain/errors/index.js +2 -0
  200. package/core/server/services/offers/domain/models/offer.js +15 -3
  201. package/core/server/services/offers/offer-bookshelf-repository.js +4 -5
  202. package/core/server/services/stats/utils/tinybird.js +5 -3
  203. package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +17 -1
  204. package/core/server/services/stripe/stripe-service.js +8 -0
  205. package/core/shared/config/defaults.json +6 -0
  206. package/core/shared/labs.js +3 -3
  207. package/core/shared/url-utils.js +2 -1
  208. package/package.json +6 -6
  209. package/tsconfig.tsbuildinfo +1 -1
  210. package/yarn.lock +23 -2
  211. package/core/built/admin/assets/comments-OXqhcC0t.js +0 -1
@@ -1 +1 @@
1
- import{j as t,aS as be,aR as xe,by as Se,bz as je,B as oe,bA as _e,bB as Ce,bC as Ne,bD as ve,ch as Le,aj as ye,ai as Te,aU as Ee,r as l,ci as Fe,aT as J,bK as Oe,af as ne,ag as $,cj as ke,bG as De,aP as Ae,u as Ie,aV as Me,aQ as Pe}from"./index-f5Lv9kLb.js";import{S as we,a as Ue,b as Ve,c as Re,d as Be,e as ze,f as Ke}from"./select-B_0NBkLz.js";import{u as H,S as ie,a as re,A as Z,b as z,c as q,U as le}from"./post-analytics-context-CEqWyqgm.js";import{a as $e,F as Ge,b as He}from"./message-square-text-BxquqPhA.js";import{g as We,a as Qe,S as Ye,b as qe}from"./kpis-BbnqD1za.js";import{F as Xe,c as W,e as ce}from"./en-ajofhc7o.js";import{C as ue,a as Je,b as Ze,f as et,c as de,g as tt,u as K,j as ee}from"./pagemenu-DjSqq3Wu.js";import{D as st,h as at,i as te,a as ot,b as nt,c as it,d as rt,e as lt,f as ct,g as ut}from"./data-list-BOdyZGWI.js";import{a as dt,U as mt}from"./wallet-cards-CSdWOmJE.js";import{P as pt,a as ht}from"./post-analytics-header-CpPPBAz4.js";import{F as gt,c as ft}from"./filters-CmJJOv3I.js";import{b as bt,c as xt,F as St,d as jt,S as _t,T as Ct}from"./lucide-react-BARFK2PZ.js";import{E as Nt}from"./empty-indicator-CR_9BHAH.js";import"./posts-C1Eh5Ypq.js";import"./source-icon-DkGi13e_.js";import"./source-utils-B1S3ZHA2.js";import"./gh-chart-hn7wEnik.js";import"./chart-pimL5Chk.js";import"./_baseAssignValue-Bbalamaq.js";import"./tabs-DCHagOip.js";import"./index-DO76vL0R.js";import"./post-share-modal-Dln0kZhn.js";import"./post-helpers-gInwAwEv.js";import"./trash-CpnxYen-.js";import"./sprout-BKiwQsGn.js";import"./a-large-small-CgWPeKpi.js";import"./at-sign-BottTo2X.js";import"./copy-aC0WUVtq.js";import"./hash-BcaWmkw5.js";import"./inbox-D9hID-rO.js";import"./minus-Ziy4LSYD.js";import"./tags-DV2SSdMK.js";import"./square-BEpRc_fY.js";import"./user-round-check-DYkrjHxF.js";import"./repeat-CN4fiG5H.js";import"./reply-BfWgvH7y.js";const se=()=>{const{range:e,setRange:s}=H();return t.jsxs(we,{value:`${e}`,onValueChange:a=>{s(Number(a))},children:[t.jsxs(Ue,{className:"w-auto",children:[t.jsx($e,{className:"mr-2",size:16,strokeWidth:1.5}),t.jsx(Ve,{placeholder:"Select a period"})]}),t.jsx(Re,{align:"end",children:t.jsxs(Be,{children:[t.jsx(ze,{children:"Period"}),Object.values(ie).map(a=>t.jsx(Ke,{value:`${a.value}`,children:a.name},a.value))]})})]})};W.registerLocale(ce);const vt=e=>re[e]||W.getName(e,"en")||"Unknown",Lt=e=>{const s={"UNITED STATES":"US","UNITED STATES OF AMERICA":"US",USA:"US","UNITED KINGDOM":"GB",UK:"GB","GREAT BRITAIN":"GB",NETHERLANDS:"NL"},a=e.toUpperCase();return s[a]||(e.length>2?e.substring(0,2):e)},ae=({tableHeader:e,data:s,onLocationClick:a})=>t.jsxs(st,{children:[e&&t.jsxs(at,{children:[t.jsx(te,{children:"Country"}),t.jsx(te,{children:"Visitors"})]}),t.jsx(ot,{children:s.map(i=>{const o=vt(`${i.location}`),r=a&&i.location!=="Unknown",c=i.location?i.location.toLowerCase():"unknown";return t.jsxs(nt,{className:r?"cursor-pointer":"","data-testid":`location-row-${c}`,onClick:r?()=>a(i.location):void 0,children:[t.jsx(it,{style:{width:`${i.percentage?Math.round(i.percentage*100):0}%`}}),t.jsx(rt,{className:"group-hover/data:max-w-[calc(100%-140px)]",children:t.jsxs("div",{className:"flex items-center space-x-3 overflow-hidden",title:o,children:[t.jsx(Xe,{countryCode:`${Lt(i.location)}`,fallback:t.jsx("span",{className:"flex h-[14px] w-[22px] items-center justify-center rounded-[2px] bg-black text-white",children:t.jsx(Le.SkullAndBones,{className:"size-3"})})}),t.jsx("div",{className:"truncate font-medium",children:o})]})}),t.jsxs(lt,{children:[t.jsx(ct,{children:ye(Number(i.visits))}),t.jsx(ut,{children:Te(i.percentage)})]})]},i.location||"unknown")})})]}),yt=({data:e,isLoading:s,onLocationClick:a})=>{const i=e.slice(0,10);return t.jsx(t.Fragment,{children:s?"":t.jsx(t.Fragment,{children:e&&e.length>0&&t.jsxs(ue,{className:"group/datalist","data-testid":"locations-card",children:[t.jsxs("div",{className:"flex items-center justify-between p-6",children:[t.jsxs(Je,{className:"p-0",children:[t.jsx(Ze,{children:"Locations"}),t.jsx(et,{children:"Where are the readers of this post"})]}),t.jsx(be,{className:"mr-2",children:"Visitors"})]}),t.jsxs(de,{className:"overflow-hidden",children:[t.jsx(xe,{}),t.jsx(ae,{data:i,tableHeader:!1,onLocationClick:a})]}),e.length>10&&t.jsx(tt,{children:t.jsxs(Se,{children:[t.jsx(je,{asChild:!0,children:t.jsxs(oe,{variant:"outline",children:["View all ",t.jsx(dt,{})]})}),t.jsxs(_e,{className:"overflow-y-auto pt-0 sm:max-w-[600px]",children:[t.jsxs(Ce,{className:"sticky top-0 z-40 -mx-6 bg-background/60 p-6 backdrop-blur",children:[t.jsx(Ne,{children:"Top locations"}),t.jsx(ve,{children:"Where are the readers of this post"})]}),t.jsx("div",{className:"group/datalist",children:t.jsx(ae,{data:e,tableHeader:!0,onLocationClick:a})})]})]})})]})})})},me=e=>!e||e.length===0?Z:z.filter(s=>e.includes(s.value)).reduce((s,a)=>s|a.bit,0)||Z,pe=e=>{const s=[];return(e&q.PUBLIC)!==0&&s.push(z[0].value),(e&q.FREE)!==0&&s.push(z[1].value),(e&q.PAID)!==0&&s.push(z[2].value),s.join(",")};W.registerLocale(ce);const Tt=e=>re[e]||W.getName(e,"en")||e,Et=({visits:e})=>t.jsx("span",{className:"order-2 font-mono text-xs text-muted-foreground",children:e.toLocaleString()}),Ft={utm_source:{endpoint:"api_top_utm_sources",valueKey:"utm_source",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_medium:{endpoint:"api_top_utm_mediums",valueKey:"utm_medium",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_campaign:{endpoint:"api_top_utm_campaigns",valueKey:"utm_campaign",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_content:{endpoint:"api_top_utm_contents",valueKey:"utm_content",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_term:{endpoint:"api_top_utm_terms",valueKey:"utm_term",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},source:{endpoint:"api_top_sources",valueKey:"source",transformValue:e=>({value:e||"",label:e||"Direct"})},location:{endpoint:"api_top_locations",valueKey:"location",filterItem(e){const s=String(e.location||"");return s!==""&&!le.includes(s)},transformValue:e=>({value:e,label:Tt(e)})},device:{endpoint:"api_top_devices",valueKey:"device",transformValue:e=>({value:e,label:e==="mobile-ios"?"iOS":e==="mobile-android"?"Android":e==="desktop"?"Desktop":e==="bot"?"Bot":e})}},Ot=(e,s,a)=>{const i={...a};return e.forEach(o=>{if(o.field===s||o.values.length===0)return;const r=o.values[0];o.field!=="audience"&&(o.field==="source"||o.field==="device"||o.field==="location"||o.field.startsWith("utm_"))&&(i[o.field]=r)}),i},O=(e,s=[],a,i={})=>{const{enabled:o=!0}=i,{statsConfig:r,range:c}=H(),{startDate:b,endDate:m,timezone:x}=ne(c),p=Ft[e],d=l.useMemo(()=>{const j=s.find(h=>h.field==="audience");return me(j?.values)},[s]),S=l.useMemo(()=>{const j={site_uuid:r?.id||"",date_from:$(b),date_to:$(m),timezone:x,member_status:pe(d),limit:"50"};return a&&(j.post_uuid=a),Ot(s,e,j)},[r?.id,b,m,x,d,s,e,a]),{data:v,loading:k}=K({endpoint:p?.endpoint||"",statsConfig:r,params:S,enabled:o&&!!p});return{options:l.useMemo(()=>p?(v||[]).filter(h=>p.filterItem?p.filterItem(h):!0).map(h=>{const E=String(h[p.valueKey]??""),M=Number(h.visits)||0,{value:F,label:y}=p.transformValue?p.transformValue(E):{value:E,label:E};return{label:y,value:F,icon:t.jsx(Et,{visits:M})}}):[],[v,p]),loading:k}};function kt({filters:e,onChange:s,...a}){const{appSettings:i}=Ee(),{post:o}=H(),r=o?.uuid,[c,b]=l.useState(null),[m,x]=l.useState(!1);l.useEffect(()=>{const g=window.matchMedia("(max-width: 1024px)"),C=T=>{x(T.matches)};return C(g),g.addEventListener("change",C),()=>g.removeEventListener("change",C)},[]);const p=l.useMemo(()=>{const g=[{value:"undefined",label:"Public visitors",icon:t.jsx(J,{className:"text-gray-700"})},{value:"free",label:"Free members",icon:t.jsx(Oe,{className:"text-green"})},{value:"paid",label:"Paid members",icon:t.jsx(mt,{className:"text-orange"})}];return i?.paidMembersEnabled?g:g.filter(C=>C.value!=="paid")},[i?.paidMembersEnabled]),d=l.useCallback(g=>{const C=c===g,T=e.some(n=>n.field===g);return C||T},[c,e]),{options:S,loading:v}=O("utm_source",e,r,{enabled:d("utm_source")}),{options:k,loading:I}=O("utm_medium",e,r,{enabled:d("utm_medium")}),{options:j,loading:h}=O("utm_campaign",e,r,{enabled:d("utm_campaign")}),{options:E,loading:M}=O("utm_content",e,r,{enabled:d("utm_content")}),{options:F,loading:y}=O("utm_term",e,r,{enabled:d("utm_term")}),{options:R,loading:D}=O("source",e,r,{enabled:d("source")}),{options:U,loading:A}=O("device",e,r,{enabled:d("device")}),{options:B,loading:P}=O("location",e,r,{enabled:d("location")}),_=l.useMemo(()=>[{value:"is",label:"is"}],[]),Q=l.useMemo(()=>{const g=[{key:"utm_source",label:"UTM Source",type:"select",icon:t.jsx(jt,{className:"size-4"}),placeholder:"Select source",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:S,isLoading:v,searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_medium",label:"UTM Medium",type:"select",icon:t.jsx(_t,{className:"size-4"}),placeholder:"Select medium",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:k,isLoading:I,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_campaign",label:"UTM Campaign",type:"select",icon:t.jsx(He,{className:"size-4"}),placeholder:"Select campaign",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:j,isLoading:h,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_content",label:"UTM Content",type:"select",icon:t.jsx(Ct,{className:"size-4"}),placeholder:"Select content",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:E,isLoading:M,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_term",label:"UTM Term",type:"select",icon:t.jsx(ke,{className:"size-4"}),placeholder:"Select term",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:F,isLoading:y,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"}];return[{group:"Basic",fields:[{key:"audience",label:"Audience",type:"multiselect",icon:t.jsx(Fe,{}),options:p.map(({value:C,label:T,icon:n})=>({value:C,label:T,icon:n})),defaultOperator:"is any of",hideOperatorSelect:!0,autoCloseOnSelect:!0},{key:"source",label:"Source",type:"select",icon:t.jsx(J,{className:"size-4"}),placeholder:"Select source",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:R,isLoading:D,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"device",label:"Device",type:"select",icon:t.jsx(bt,{className:"size-4"}),placeholder:"Select device",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:U,isLoading:A,selectedOptionsClassName:"hidden"},{key:"location",label:"Location",type:"select",icon:t.jsx(xt,{className:"size-4"}),placeholder:"Select location",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:B,isLoading:P,searchable:!0,selectedOptionsClassName:"hidden"}]},{group:"UTM parameters",fields:g}]},[S,v,k,I,j,h,E,M,F,y,_,p,R,D,U,A,B,P]),w=e.length>0,Y=l.useCallback(()=>{s&&s([])},[s]);return t.jsxs("div",{className:"mt-3 flex w-full justify-between gap-2 lg:mt-0","data-testid":"stats-filter-container",children:[t.jsx(gt,{addButtonIcon:t.jsx(Ge,{}),addButtonText:w?"Add filter":"Filter",allowMultiple:!1,className:`[&>button]:order-last ${w&&"[&>button]:border-none"}`,fields:Q,filters:e,keyboardShortcut:"f",popoverAlign:m||w?"start":"end",showSearchInput:!1,onActiveFieldChange:b,onChange:s||(()=>{}),...a}),w&&t.jsxs(oe,{className:"hidden font-normal text-muted-foreground lg:flex","data-testid":"stats-filter-clear-button",variant:"ghost",onClick:Y,children:[t.jsx(St,{}),"Clear"]})]})}const G=["audience","source","device","location","utm_source","utm_medium","utm_campaign","utm_content","utm_term"],he="__empty__",ge="%2C";function Dt(e){const s=new URLSearchParams;return e.forEach(a=>{if(G.includes(a.field)&&a.values.length>0){const i=a.values.map(o=>o===""?he:String(o).replace(/,/g,ge)).join(",");s.set(a.field,i)}}),s}const X=new Map;function At(e){return X.has(e)||X.set(e,`url-${e}-${Date.now()}-${Math.random().toString(36).substring(2,11)}`),X.get(e)}function It(e){const s=[],a=new Set(G);return e.forEach((i,o)=>{if(!a.has(o))return;const r=i.split(",").map(c=>c===he?"":c.replace(new RegExp(ge,"g"),","));if(r.length>0){const c=o==="audience"?"is any of":"is";s.push({id:At(o),field:o,operator:c,values:r})}}),s}function Mt(e={}){const[s,a]=De(),{onFiltersChange:i}=e,o=l.useRef(!1),r=l.useMemo(()=>It(s),[s]);l.useEffect(()=>{!o.current&&i&&i(r)},[r,i]);const c=l.useCallback(m=>{o.current=!0;const x=typeof m=="function"?m(r):m,p=Dt(x),d=new URLSearchParams(s);G.forEach(S=>{d.delete(S)}),p.forEach((S,v)=>{d.set(v,S)}),a(d,{replace:!0}),setTimeout(()=>{o.current=!1},0)},[r,s,a]),b=l.useCallback(()=>{o.current=!0;const m=new URLSearchParams(s);G.forEach(x=>{m.delete(x)}),a(m,{replace:!0}),setTimeout(()=>{o.current=!1},0)},[s,a]);return{filters:r,setFilters:c,clearFilters:b}}const xs=()=>{const e=Ae(),{postId:s}=Ie(),{statsConfig:a,isLoading:i,range:o,data:r,post:c,isPostLoading:b}=H(),{filters:m,setFilters:x}=Mt(),p=l.useMemo(()=>{const n=m.find(f=>f.field==="audience");return me(n?.values)},[m]);l.useEffect(()=>{!b&&c?.email_only&&e(`/posts/analytics/${s}`)},[b,c?.email_only,e,s]);const d=l.useMemo(()=>{if(!c?.published_at)return ie.ALL_TIME.value;const n=Me(c.published_at);return o>n?n:o},[c?.published_at,o]),{startDate:S,endDate:v,timezone:k}=ne(d),I=l.useCallback(()=>{const n=document.querySelector(".overflow-y-scroll");n&&n.scrollTo({top:0,behavior:"smooth"})},[]),j=l.useMemo(()=>{const n={};return m.forEach(f=>{const N=f.field,L=f.values;if(N==="audience")return;const u=L&&L.length>0&&L[0]!==null&&L[0]!==void 0,V=N==="source"&&L?.[0]==="";if(u&&(L[0]!==""||V)){const fe=String(L[0]);n[N]=fe}}),n},[m]),h=l.useCallback((n,f)=>{x(N=>N.find(u=>u.field===n)?N.map(u=>u.field===n?{...u,values:[f]}:u):[...N,ft(n,"is",[f])]),I()},[x,I]),E=l.useCallback(n=>h("location",n),[h]),M=l.useCallback(n=>h("source",n),[h]),F=l.useMemo(()=>{const n={site_uuid:a?.id||"",date_from:$(S),date_to:$(v),timezone:k,member_status:pe(p),post_uuid:"",...j};return!b&&c?.uuid?{...n,post_uuid:c.uuid}:n},[b,c,a?.id,S,v,k,p,j]),{data:y,loading:R}=K({endpoint:"api_kpis",statsConfig:a||{id:""},params:F}),{data:D,loading:U}=K({endpoint:"api_top_locations",statsConfig:a||{id:""},params:F}),{data:A,loading:B}=K({endpoint:"api_top_sources",statsConfig:a||{id:""},params:F}),P=l.useMemo(()=>D?.reduce((n,f)=>n+Number(f.visits),0)||0,[D]),_=l.useMemo(()=>A?A.reduce((n,f)=>n+Number(f.visits||0),0):0,[A]),Q=r?.url,w=r?.icon,Y=l.useMemo(()=>{const n=D?.map(u=>({location:String(u.location),visits:Number(u.visits),percentage:P>0?Number(u.visits)/P:0,isUnknown:le.includes(String(u.location))}))||[],f=n.filter(u=>!u.isUnknown),N=n.filter(u=>u.isUnknown),L=N.length>0?[{location:"Unknown",visits:N.reduce((u,V)=>u+V.visits,0),percentage:N.reduce((u,V)=>u+V.percentage,0)}]:[];return[...f,...L]},[D,P]),g=i||b||R||U||B,C=We(y),T=m.length>0;return t.jsxs(t.Fragment,{children:[t.jsxs(pt,{currentTab:"Web",children:[T&&t.jsx(ee,{children:t.jsx(se,{})}),t.jsxs(ee,{className:`${T?"!mt-0 [grid-area:subactions] lg:!mt-[25px]":"[grid-area:actions]"}`,children:[t.jsx(kt,{filters:m,onChange:x}),!T&&t.jsx(se,{})]})]}),t.jsx(ht,{children:g?t.jsx(ue,{className:"size-full",variant:"plain",children:t.jsx(de,{className:"size-full items-center justify-center",children:t.jsx(Pe,{})})}):y&&y.length!==0&&C.visits!=="0"?t.jsxs(t.Fragment,{children:[t.jsx(Qe,{data:y,range:d}),t.jsxs("div",{className:"flex flex-col gap-6 lg:grid lg:grid-cols-2",children:[t.jsx(yt,{data:Y,isLoading:U,onLocationClick:E}),t.jsx(Ye,{data:A,range:d,siteIcon:w,siteUrl:Q,totalVisitors:_,onSourceClick:M})]})]}):t.jsx("div",{className:"grow",children:t.jsx(Nt,{className:"h-full",description:"Try adjusting filters to see more data.",title:`No visitors ${qe(o)}`,children:t.jsx(J,{strokeWidth:1.5})})})})]})};export{xs as default};
1
+ import{j as t,aS as be,aR as xe,by as Se,bz as je,B as oe,bA as _e,bB as Ce,bC as Ne,bD as ve,ch as Le,aj as ye,ai as Te,aU as Ee,r as l,ci as Fe,aT as J,bK as Oe,af as ne,ag as $,cj as ke,bG as De,aP as Ae,u as Ie,aV as Me,aQ as Pe}from"./index-CMTzNTew.js";import{S as we,a as Ue,b as Ve,c as Re,d as Be,e as ze,f as Ke}from"./select-Cor2wFXT.js";import{u as H,S as ie,a as re,A as Z,b as z,c as q,U as le}from"./post-analytics-context-CF7C67-0.js";import{a as $e,F as Ge,b as He}from"./message-square-text-cV8O_qKq.js";import{g as We,a as Qe,S as Ye,b as qe}from"./kpis-CDrs2iS1.js";import{F as Xe,c as W,e as ce}from"./en-D4zIrMLN.js";import{C as ue,a as Je,b as Ze,f as et,c as de,g as tt,u as K,j as ee}from"./pagemenu-CZyroidv.js";import{D as st,h as at,i as te,a as ot,b as nt,c as it,d as rt,e as lt,f as ct,g as ut}from"./data-list-BYNMbRIq.js";import{a as dt,U as mt}from"./wallet-cards-KmOh29LP.js";import{P as pt,a as ht}from"./post-analytics-header-7GtJCx0W.js";import{F as gt,c as ft}from"./filters-CpVwu1Gk.js";import{b as bt,c as xt,F as St,d as jt,S as _t,T as Ct}from"./lucide-react-CBigk-fq.js";import{E as Nt}from"./empty-indicator-Bn5wG9-T.js";import"./posts-CL9UDYoW.js";import"./source-icon-DvDuzw73.js";import"./source-utils-B1S3ZHA2.js";import"./gh-chart-Br7NXn_b.js";import"./chart-mYz3IJwm.js";import"./_baseAssignValue-DnkbkowM.js";import"./tabs-BmdL0X4U.js";import"./index-D212VvOz.js";import"./post-share-modal-D8R7DUZP.js";import"./post-helpers-gInwAwEv.js";import"./trash-u5BxolyH.js";import"./sprout-C3cc0c-K.js";import"./a-large-small-C5mgFBRg.js";import"./at-sign-Bz-SU-S_.js";import"./copy-Bpq3uhnh.js";import"./hash-Bc1e38oH.js";import"./inbox-BcZNBNhk.js";import"./minus-NvnQTlW7.js";import"./tags-CLxXZlOO.js";import"./square-tZp0_n7e.js";import"./user-round-check-B6j98D6d.js";import"./repeat-DgH39UKE.js";import"./reply-DAaNxiy8.js";const se=()=>{const{range:e,setRange:s}=H();return t.jsxs(we,{value:`${e}`,onValueChange:a=>{s(Number(a))},children:[t.jsxs(Ue,{className:"w-auto",children:[t.jsx($e,{className:"mr-2",size:16,strokeWidth:1.5}),t.jsx(Ve,{placeholder:"Select a period"})]}),t.jsx(Re,{align:"end",children:t.jsxs(Be,{children:[t.jsx(ze,{children:"Period"}),Object.values(ie).map(a=>t.jsx(Ke,{value:`${a.value}`,children:a.name},a.value))]})})]})};W.registerLocale(ce);const vt=e=>re[e]||W.getName(e,"en")||"Unknown",Lt=e=>{const s={"UNITED STATES":"US","UNITED STATES OF AMERICA":"US",USA:"US","UNITED KINGDOM":"GB",UK:"GB","GREAT BRITAIN":"GB",NETHERLANDS:"NL"},a=e.toUpperCase();return s[a]||(e.length>2?e.substring(0,2):e)},ae=({tableHeader:e,data:s,onLocationClick:a})=>t.jsxs(st,{children:[e&&t.jsxs(at,{children:[t.jsx(te,{children:"Country"}),t.jsx(te,{children:"Visitors"})]}),t.jsx(ot,{children:s.map(i=>{const o=vt(`${i.location}`),r=a&&i.location!=="Unknown",c=i.location?i.location.toLowerCase():"unknown";return t.jsxs(nt,{className:r?"cursor-pointer":"","data-testid":`location-row-${c}`,onClick:r?()=>a(i.location):void 0,children:[t.jsx(it,{style:{width:`${i.percentage?Math.round(i.percentage*100):0}%`}}),t.jsx(rt,{className:"group-hover/data:max-w-[calc(100%-140px)]",children:t.jsxs("div",{className:"flex items-center space-x-3 overflow-hidden",title:o,children:[t.jsx(Xe,{countryCode:`${Lt(i.location)}`,fallback:t.jsx("span",{className:"flex h-[14px] w-[22px] items-center justify-center rounded-[2px] bg-black text-white",children:t.jsx(Le.SkullAndBones,{className:"size-3"})})}),t.jsx("div",{className:"truncate font-medium",children:o})]})}),t.jsxs(lt,{children:[t.jsx(ct,{children:ye(Number(i.visits))}),t.jsx(ut,{children:Te(i.percentage)})]})]},i.location||"unknown")})})]}),yt=({data:e,isLoading:s,onLocationClick:a})=>{const i=e.slice(0,10);return t.jsx(t.Fragment,{children:s?"":t.jsx(t.Fragment,{children:e&&e.length>0&&t.jsxs(ue,{className:"group/datalist","data-testid":"locations-card",children:[t.jsxs("div",{className:"flex items-center justify-between p-6",children:[t.jsxs(Je,{className:"p-0",children:[t.jsx(Ze,{children:"Locations"}),t.jsx(et,{children:"Where are the readers of this post"})]}),t.jsx(be,{className:"mr-2",children:"Visitors"})]}),t.jsxs(de,{className:"overflow-hidden",children:[t.jsx(xe,{}),t.jsx(ae,{data:i,tableHeader:!1,onLocationClick:a})]}),e.length>10&&t.jsx(tt,{children:t.jsxs(Se,{children:[t.jsx(je,{asChild:!0,children:t.jsxs(oe,{variant:"outline",children:["View all ",t.jsx(dt,{})]})}),t.jsxs(_e,{className:"overflow-y-auto pt-0 sm:max-w-[600px]",children:[t.jsxs(Ce,{className:"sticky top-0 z-40 -mx-6 bg-background/60 p-6 backdrop-blur",children:[t.jsx(Ne,{children:"Top locations"}),t.jsx(ve,{children:"Where are the readers of this post"})]}),t.jsx("div",{className:"group/datalist",children:t.jsx(ae,{data:e,tableHeader:!0,onLocationClick:a})})]})]})})]})})})},me=e=>!e||e.length===0?Z:z.filter(s=>e.includes(s.value)).reduce((s,a)=>s|a.bit,0)||Z,pe=e=>{const s=[];return(e&q.PUBLIC)!==0&&s.push(z[0].value),(e&q.FREE)!==0&&s.push(z[1].value),(e&q.PAID)!==0&&s.push(z[2].value),s.join(",")};W.registerLocale(ce);const Tt=e=>re[e]||W.getName(e,"en")||e,Et=({visits:e})=>t.jsx("span",{className:"order-2 font-mono text-xs text-muted-foreground",children:e.toLocaleString()}),Ft={utm_source:{endpoint:"api_top_utm_sources",valueKey:"utm_source",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_medium:{endpoint:"api_top_utm_mediums",valueKey:"utm_medium",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_campaign:{endpoint:"api_top_utm_campaigns",valueKey:"utm_campaign",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_content:{endpoint:"api_top_utm_contents",valueKey:"utm_content",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_term:{endpoint:"api_top_utm_terms",valueKey:"utm_term",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},source:{endpoint:"api_top_sources",valueKey:"source",transformValue:e=>({value:e||"",label:e||"Direct"})},location:{endpoint:"api_top_locations",valueKey:"location",filterItem(e){const s=String(e.location||"");return s!==""&&!le.includes(s)},transformValue:e=>({value:e,label:Tt(e)})},device:{endpoint:"api_top_devices",valueKey:"device",transformValue:e=>({value:e,label:e==="mobile-ios"?"iOS":e==="mobile-android"?"Android":e==="desktop"?"Desktop":e==="bot"?"Bot":e})}},Ot=(e,s,a)=>{const i={...a};return e.forEach(o=>{if(o.field===s||o.values.length===0)return;const r=o.values[0];o.field!=="audience"&&(o.field==="source"||o.field==="device"||o.field==="location"||o.field.startsWith("utm_"))&&(i[o.field]=r)}),i},O=(e,s=[],a,i={})=>{const{enabled:o=!0}=i,{statsConfig:r,range:c}=H(),{startDate:b,endDate:m,timezone:x}=ne(c),p=Ft[e],d=l.useMemo(()=>{const j=s.find(h=>h.field==="audience");return me(j?.values)},[s]),S=l.useMemo(()=>{const j={site_uuid:r?.id||"",date_from:$(b),date_to:$(m),timezone:x,member_status:pe(d),limit:"50"};return a&&(j.post_uuid=a),Ot(s,e,j)},[r?.id,b,m,x,d,s,e,a]),{data:v,loading:k}=K({endpoint:p?.endpoint||"",statsConfig:r,params:S,enabled:o&&!!p});return{options:l.useMemo(()=>p?(v||[]).filter(h=>p.filterItem?p.filterItem(h):!0).map(h=>{const E=String(h[p.valueKey]??""),M=Number(h.visits)||0,{value:F,label:y}=p.transformValue?p.transformValue(E):{value:E,label:E};return{label:y,value:F,icon:t.jsx(Et,{visits:M})}}):[],[v,p]),loading:k}};function kt({filters:e,onChange:s,...a}){const{appSettings:i}=Ee(),{post:o}=H(),r=o?.uuid,[c,b]=l.useState(null),[m,x]=l.useState(!1);l.useEffect(()=>{const g=window.matchMedia("(max-width: 1024px)"),C=T=>{x(T.matches)};return C(g),g.addEventListener("change",C),()=>g.removeEventListener("change",C)},[]);const p=l.useMemo(()=>{const g=[{value:"undefined",label:"Public visitors",icon:t.jsx(J,{className:"text-gray-700"})},{value:"free",label:"Free members",icon:t.jsx(Oe,{className:"text-green"})},{value:"paid",label:"Paid members",icon:t.jsx(mt,{className:"text-orange"})}];return i?.paidMembersEnabled?g:g.filter(C=>C.value!=="paid")},[i?.paidMembersEnabled]),d=l.useCallback(g=>{const C=c===g,T=e.some(n=>n.field===g);return C||T},[c,e]),{options:S,loading:v}=O("utm_source",e,r,{enabled:d("utm_source")}),{options:k,loading:I}=O("utm_medium",e,r,{enabled:d("utm_medium")}),{options:j,loading:h}=O("utm_campaign",e,r,{enabled:d("utm_campaign")}),{options:E,loading:M}=O("utm_content",e,r,{enabled:d("utm_content")}),{options:F,loading:y}=O("utm_term",e,r,{enabled:d("utm_term")}),{options:R,loading:D}=O("source",e,r,{enabled:d("source")}),{options:U,loading:A}=O("device",e,r,{enabled:d("device")}),{options:B,loading:P}=O("location",e,r,{enabled:d("location")}),_=l.useMemo(()=>[{value:"is",label:"is"}],[]),Q=l.useMemo(()=>{const g=[{key:"utm_source",label:"UTM Source",type:"select",icon:t.jsx(jt,{className:"size-4"}),placeholder:"Select source",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:S,isLoading:v,searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_medium",label:"UTM Medium",type:"select",icon:t.jsx(_t,{className:"size-4"}),placeholder:"Select medium",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:k,isLoading:I,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_campaign",label:"UTM Campaign",type:"select",icon:t.jsx(He,{className:"size-4"}),placeholder:"Select campaign",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:j,isLoading:h,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_content",label:"UTM Content",type:"select",icon:t.jsx(Ct,{className:"size-4"}),placeholder:"Select content",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:E,isLoading:M,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_term",label:"UTM Term",type:"select",icon:t.jsx(ke,{className:"size-4"}),placeholder:"Select term",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:F,isLoading:y,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"}];return[{group:"Basic",fields:[{key:"audience",label:"Audience",type:"multiselect",icon:t.jsx(Fe,{}),options:p.map(({value:C,label:T,icon:n})=>({value:C,label:T,icon:n})),defaultOperator:"is any of",hideOperatorSelect:!0,autoCloseOnSelect:!0},{key:"source",label:"Source",type:"select",icon:t.jsx(J,{className:"size-4"}),placeholder:"Select source",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:R,isLoading:D,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"device",label:"Device",type:"select",icon:t.jsx(bt,{className:"size-4"}),placeholder:"Select device",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:U,isLoading:A,selectedOptionsClassName:"hidden"},{key:"location",label:"Location",type:"select",icon:t.jsx(xt,{className:"size-4"}),placeholder:"Select location",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:B,isLoading:P,searchable:!0,selectedOptionsClassName:"hidden"}]},{group:"UTM parameters",fields:g}]},[S,v,k,I,j,h,E,M,F,y,_,p,R,D,U,A,B,P]),w=e.length>0,Y=l.useCallback(()=>{s&&s([])},[s]);return t.jsxs("div",{className:"mt-3 flex w-full justify-between gap-2 lg:mt-0","data-testid":"stats-filter-container",children:[t.jsx(gt,{addButtonIcon:t.jsx(Ge,{}),addButtonText:w?"Add filter":"Filter",allowMultiple:!1,className:`[&>button]:order-last ${w&&"[&>button]:border-none"}`,fields:Q,filters:e,keyboardShortcut:"f",popoverAlign:m||w?"start":"end",showSearchInput:!1,onActiveFieldChange:b,onChange:s||(()=>{}),...a}),w&&t.jsxs(oe,{className:"hidden font-normal text-muted-foreground lg:flex","data-testid":"stats-filter-clear-button",variant:"ghost",onClick:Y,children:[t.jsx(St,{}),"Clear"]})]})}const G=["audience","source","device","location","utm_source","utm_medium","utm_campaign","utm_content","utm_term"],he="__empty__",ge="%2C";function Dt(e){const s=new URLSearchParams;return e.forEach(a=>{if(G.includes(a.field)&&a.values.length>0){const i=a.values.map(o=>o===""?he:String(o).replace(/,/g,ge)).join(",");s.set(a.field,i)}}),s}const X=new Map;function At(e){return X.has(e)||X.set(e,`url-${e}-${Date.now()}-${Math.random().toString(36).substring(2,11)}`),X.get(e)}function It(e){const s=[],a=new Set(G);return e.forEach((i,o)=>{if(!a.has(o))return;const r=i.split(",").map(c=>c===he?"":c.replace(new RegExp(ge,"g"),","));if(r.length>0){const c=o==="audience"?"is any of":"is";s.push({id:At(o),field:o,operator:c,values:r})}}),s}function Mt(e={}){const[s,a]=De(),{onFiltersChange:i}=e,o=l.useRef(!1),r=l.useMemo(()=>It(s),[s]);l.useEffect(()=>{!o.current&&i&&i(r)},[r,i]);const c=l.useCallback(m=>{o.current=!0;const x=typeof m=="function"?m(r):m,p=Dt(x),d=new URLSearchParams(s);G.forEach(S=>{d.delete(S)}),p.forEach((S,v)=>{d.set(v,S)}),a(d,{replace:!0}),setTimeout(()=>{o.current=!1},0)},[r,s,a]),b=l.useCallback(()=>{o.current=!0;const m=new URLSearchParams(s);G.forEach(x=>{m.delete(x)}),a(m,{replace:!0}),setTimeout(()=>{o.current=!1},0)},[s,a]);return{filters:r,setFilters:c,clearFilters:b}}const xs=()=>{const e=Ae(),{postId:s}=Ie(),{statsConfig:a,isLoading:i,range:o,data:r,post:c,isPostLoading:b}=H(),{filters:m,setFilters:x}=Mt(),p=l.useMemo(()=>{const n=m.find(f=>f.field==="audience");return me(n?.values)},[m]);l.useEffect(()=>{!b&&c?.email_only&&e(`/posts/analytics/${s}`)},[b,c?.email_only,e,s]);const d=l.useMemo(()=>{if(!c?.published_at)return ie.ALL_TIME.value;const n=Me(c.published_at);return o>n?n:o},[c?.published_at,o]),{startDate:S,endDate:v,timezone:k}=ne(d),I=l.useCallback(()=>{const n=document.querySelector(".overflow-y-scroll");n&&n.scrollTo({top:0,behavior:"smooth"})},[]),j=l.useMemo(()=>{const n={};return m.forEach(f=>{const N=f.field,L=f.values;if(N==="audience")return;const u=L&&L.length>0&&L[0]!==null&&L[0]!==void 0,V=N==="source"&&L?.[0]==="";if(u&&(L[0]!==""||V)){const fe=String(L[0]);n[N]=fe}}),n},[m]),h=l.useCallback((n,f)=>{x(N=>N.find(u=>u.field===n)?N.map(u=>u.field===n?{...u,values:[f]}:u):[...N,ft(n,"is",[f])]),I()},[x,I]),E=l.useCallback(n=>h("location",n),[h]),M=l.useCallback(n=>h("source",n),[h]),F=l.useMemo(()=>{const n={site_uuid:a?.id||"",date_from:$(S),date_to:$(v),timezone:k,member_status:pe(p),post_uuid:"",...j};return!b&&c?.uuid?{...n,post_uuid:c.uuid}:n},[b,c,a?.id,S,v,k,p,j]),{data:y,loading:R}=K({endpoint:"api_kpis",statsConfig:a||{id:""},params:F}),{data:D,loading:U}=K({endpoint:"api_top_locations",statsConfig:a||{id:""},params:F}),{data:A,loading:B}=K({endpoint:"api_top_sources",statsConfig:a||{id:""},params:F}),P=l.useMemo(()=>D?.reduce((n,f)=>n+Number(f.visits),0)||0,[D]),_=l.useMemo(()=>A?A.reduce((n,f)=>n+Number(f.visits||0),0):0,[A]),Q=r?.url,w=r?.icon,Y=l.useMemo(()=>{const n=D?.map(u=>({location:String(u.location),visits:Number(u.visits),percentage:P>0?Number(u.visits)/P:0,isUnknown:le.includes(String(u.location))}))||[],f=n.filter(u=>!u.isUnknown),N=n.filter(u=>u.isUnknown),L=N.length>0?[{location:"Unknown",visits:N.reduce((u,V)=>u+V.visits,0),percentage:N.reduce((u,V)=>u+V.percentage,0)}]:[];return[...f,...L]},[D,P]),g=i||b||R||U||B,C=We(y),T=m.length>0;return t.jsxs(t.Fragment,{children:[t.jsxs(pt,{currentTab:"Web",children:[T&&t.jsx(ee,{children:t.jsx(se,{})}),t.jsxs(ee,{className:`${T?"!mt-0 [grid-area:subactions] lg:!mt-[25px]":"[grid-area:actions]"}`,children:[t.jsx(kt,{filters:m,onChange:x}),!T&&t.jsx(se,{})]})]}),t.jsx(ht,{children:g?t.jsx(ue,{className:"size-full",variant:"plain",children:t.jsx(de,{className:"size-full items-center justify-center",children:t.jsx(Pe,{})})}):y&&y.length!==0&&C.visits!=="0"?t.jsxs(t.Fragment,{children:[t.jsx(Qe,{data:y,range:d}),t.jsxs("div",{className:"flex flex-col gap-6 lg:grid lg:grid-cols-2",children:[t.jsx(yt,{data:Y,isLoading:U,onLocationClick:E}),t.jsx(Ye,{data:A,range:d,siteIcon:w,siteUrl:Q,totalVisitors:_,onSourceClick:M})]})]}):t.jsx("div",{className:"grow",children:t.jsx(Nt,{className:"h-full",description:"Try adjusting filters to see more data.",title:`No visitors ${qe(o)}`,children:t.jsx(J,{strokeWidth:1.5})})})})]})};export{xs as default};
@@ -2,8 +2,8 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <link rel="stylesheet" href="./assets/vendor-0ede59da8efb5e28fa929557f7ff7154.css">
5
- <link rel="stylesheet" href="./assets/ghost-78b420974adc056bf1c9570275a46535.css">
6
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%226.17%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%22e3b2a7ff32%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%220fccdf8704%22%2C%22activitypubFilename%22%3A%22activitypub.js%22%2C%22activitypubHash%22%3A%2296ba714cd1%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%22f16dcbd51e%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%224556c3f52a%22%2C%22activitypubRemoteConfigUrl%22%3A%22%2F.ghost%2Factivitypub%2Fstable%2Fclient-config%22%7D">
5
+ <link rel="stylesheet" href="./assets/ghost-3c48d37b32f4fcd7eb15cfd739905a03.css">
6
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%226.18%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%22e3b2a7ff32%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%227891cacd1a%22%2C%22activitypubFilename%22%3A%22activitypub.js%22%2C%22activitypubHash%22%3A%224d76e4db3f%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%22171bc5b3ae%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%226f1c278b75%22%2C%22activitypubRemoteConfigUrl%22%3A%22%2F.ghost%2Factivitypub%2Fstable%2Fclient-config%22%7D">
7
7
 
8
8
  <meta charset="UTF-8" />
9
9
 
@@ -17,7 +17,7 @@
17
17
  <meta name="apple-mobile-web-app-title" content="Ghost" />
18
18
  <meta name="apple-mobile-web-app-capable" content="yes" />
19
19
  <meta name="apple-mobile-web-app-status-bar-style" content="black" />
20
- <script type="module" crossorigin src="./assets/index-f5Lv9kLb.js"></script>
20
+ <script type="module" crossorigin src="./assets/index-CMTzNTew.js"></script>
21
21
  <link rel="stylesheet" crossorigin href="./assets/index-BBrewxpF.css">
22
22
  </head>
23
23
  <body class="react-admin">
@@ -38,7 +38,7 @@
38
38
  <div id="ember-liquid-wormhole"></div>
39
39
  <script src="./assets/vendor-0e6161f8da2ad46cf62cef0a53cfb16a.js"></script>
40
40
  <script src="./assets/chunk.397.fc6a300ea45f7b5ebab7.js"></script>
41
- <script src="./assets/chunk.524.9a8f5123fd3ecf3e4451.js"></script>
42
- <script src="./assets/ghost-a9268074d0a9b02198594a05ecd7a626.js"></script>
41
+ <script src="./assets/chunk.524.9d300778a63b42b0de62.js"></script>
42
+ <script src="./assets/ghost-98a3c5fd6235ebd344594f491cb6d17b.js"></script>
43
43
  </body>
44
44
  </html>
@@ -1,8 +1,11 @@
1
1
  const crypto = require('crypto');
2
+ const path = require('path');
2
3
  const config = require('../../shared/config');
3
4
  const {blogIcon} = require('../../server/lib/image');
4
5
  const urlUtils = require('../../shared/url-utils');
5
6
  const {SafeString} = require('../services/handlebars');
7
+ const assetHash = require('../services/asset-hash');
8
+ const themeEngine = require('../services/theme-engine');
6
9
 
7
10
  /**
8
11
  * Serve either uploaded favicon or default
@@ -12,54 +15,162 @@ function getFaviconUrl() {
12
15
  return blogIcon.getIconUrl();
13
16
  }
14
17
 
18
+ /**
19
+ * Get the fallback global asset hash (used for non-theme assets or when file hash unavailable)
20
+ * @returns {string}
21
+ */
22
+ function getGlobalAssetHash() {
23
+ if (!config.get('assetHash')) {
24
+ config.set('assetHash', (crypto.createHash('md5').update(Date.now().toString()).digest('hex')).substring(0, 10));
25
+ }
26
+ return config.get('assetHash');
27
+ }
28
+
29
+ /**
30
+ * Safely resolve a path within a root directory, preventing directory traversal
31
+ * @param {string} rootPath - The root directory path
32
+ * @param {string} relativePath - The relative path to resolve
33
+ * @returns {string|null} - The resolved path, or null if it would escape the root
34
+ */
35
+ function safeResolvePath(rootPath, relativePath) {
36
+ // Normalize and resolve the full path
37
+ const root = path.resolve(rootPath);
38
+ const normalized = relativePath.replace(/^\/+/, ''); // Strip leading slashes
39
+ const fullPath = path.resolve(root, normalized);
40
+
41
+ // Ensure the resolved path is within the root directory
42
+ if (!fullPath.startsWith(root + path.sep) && fullPath !== root) {
43
+ return null;
44
+ }
45
+
46
+ return fullPath;
47
+ }
48
+
49
+ /**
50
+ * Get SHA256-based hash for a theme asset file
51
+ * @param {string} assetPath - The asset path relative to the theme (e.g., "css/screen.css")
52
+ * @returns {string|null} - Hash string or null if file not found
53
+ */
54
+ function getThemeAssetHash(assetPath) {
55
+ const activeTheme = themeEngine.getActive();
56
+ if (!activeTheme || !activeTheme.path) {
57
+ return null;
58
+ }
59
+
60
+ // Theme assets are served from {themePath}/assets/{assetPath}
61
+ const assetsRoot = path.join(activeTheme.path, 'assets');
62
+ const fullPath = safeResolvePath(assetsRoot, assetPath);
63
+ if (!fullPath) {
64
+ return null;
65
+ }
66
+
67
+ return assetHash.getHashForFile(fullPath);
68
+ }
69
+
70
+ /**
71
+ * Get SHA256-based hash for a public asset file
72
+ * Public assets come from two locations:
73
+ * - Static: config.get('paths').publicFilePath (Ghost's built-in files)
74
+ * - Built: config.getContentPath('public') (Generated files like cards.min.css)
75
+ * @param {string} assetPath - The asset path (e.g., "public/ghost.css")
76
+ * @returns {string|null} - Hash string or null if file not found
77
+ */
78
+ function getPublicAssetHash(assetPath) {
79
+ // Remove 'public/' prefix to get the actual filename
80
+ const filename = assetPath.replace(/^public\//, '');
81
+
82
+ // Try static path first (Ghost's built-in files)
83
+ const staticFilePath = config.get('paths').publicFilePath;
84
+ if (staticFilePath) {
85
+ const staticPath = safeResolvePath(staticFilePath, filename);
86
+ if (staticPath) {
87
+ const hash = assetHash.getHashForFile(staticPath);
88
+ if (hash) {
89
+ return hash;
90
+ }
91
+ }
92
+ }
93
+
94
+ // Try built path (generated files like cards.min.css)
95
+ const builtFilePath = config.getContentPath('public');
96
+ if (builtFilePath) {
97
+ const builtPath = safeResolvePath(builtFilePath, filename);
98
+ if (builtPath) {
99
+ const hash = assetHash.getHashForFile(builtPath);
100
+ if (hash) {
101
+ return hash;
102
+ }
103
+ }
104
+ }
105
+
106
+ return null;
107
+ }
108
+
15
109
  /**
16
110
  * Prepare URL for an asset
17
- * @param {string|SafeString} path the assets path
18
- * @param {boolean} hasMinFile flag for the existence of a minified version for the asset
111
+ * @param {string|SafeString} assetPath - the asset's path
112
+ * @param {boolean} hasMinFile - flag for the existence of a minified version for the asset
19
113
  * @returns {string}
20
114
  */
21
- function getAssetUrl(path, hasMinFile) {
22
- path = path instanceof SafeString ? path.string : path;
115
+ function getAssetUrl(assetPath, hasMinFile) {
116
+ assetPath = assetPath instanceof SafeString ? assetPath.string : assetPath;
23
117
 
24
118
  // CASE: favicon - this is special path with its own functionality
25
- if (path.match(/\/?favicon\.(ico|png)$/)) {
119
+ if (assetPath.match(/\/?favicon\.(ico|png)$/)) {
26
120
  // @TODO, resolve this - we should only be resolving subdirectory and extension.
27
121
  return getFaviconUrl();
28
122
  }
29
123
 
124
+ // Determine asset type
125
+ const isPublicAsset = assetPath.match(/^public\//);
126
+ const isThemeAsset = !isPublicAsset && !assetPath.match(/^asset/);
127
+
30
128
  // CASE: Build the output URL
31
129
  // Add subdirectory...
32
130
  let output = urlUtils.urlJoin(urlUtils.getSubdir(), '/');
33
131
 
34
132
  // Optionally add /assets/
35
- if (!path.match(/^public/) && !path.match(/^asset/)) {
133
+ if (isThemeAsset) {
36
134
  output = urlUtils.urlJoin(output, 'assets/');
37
135
  }
38
136
 
39
137
  // replace ".foo" with ".min.foo" if configured
40
138
  if (hasMinFile && config.get('useMinFiles') !== false) {
41
- path = path.replace(/\.([^.]*)$/, '.min.$1');
139
+ assetPath = assetPath.replace(/\.([^.]*)$/, '.min.$1');
42
140
  }
43
141
 
44
142
  // Add the path for the requested asset
45
- output = urlUtils.urlJoin(output, path);
143
+ output = urlUtils.urlJoin(output, assetPath);
46
144
 
47
- // Ensure we have an assetHash
48
- // @TODO rework this!
49
- if (!config.get('assetHash')) {
50
- config.set('assetHash', (crypto.createHash('md5').update(Date.now().toString()).digest('hex')).substring(0, 10));
145
+ // Get the appropriate hash for this asset (ignore URL anchor)
146
+ const hashPath = assetPath.includes('#') ? assetPath.slice(0, assetPath.indexOf('#')) : assetPath;
147
+ let hash;
148
+ // Use file-based SHA256 hash if enabled via config (defaults to false for backwards compatibility)
149
+ if (config.get('caching:assets:contentBasedHash:enabled')) {
150
+ if (isThemeAsset) {
151
+ // For theme assets, use file-based SHA256 hash
152
+ hash = getThemeAssetHash(hashPath);
153
+ } else if (isPublicAsset) {
154
+ // For public assets, use file-based SHA256 hash
155
+ hash = getPublicAssetHash(hashPath);
156
+ }
157
+ }
158
+
159
+ // Fallback to global hash if file hash unavailable
160
+ if (!hash) {
161
+ hash = getGlobalAssetHash();
51
162
  }
52
163
 
53
- // if url has # make sure the hash is at th right place
164
+ // if url has # make sure the hash is at the right place
54
165
  let anchor;
55
- if (path.match('#')) {
166
+ if (assetPath.match('#')) {
56
167
  const index = output.indexOf('#');
57
168
  anchor = output.substring(index);
58
169
  output = output.slice(0, index);
59
170
  }
60
171
 
61
172
  // Finally add the asset hash to the output URL
62
- output += '?v=' + config.get('assetHash');
173
+ output += '?v=' + hash;
63
174
 
64
175
  if (anchor) {
65
176
  output += anchor;
@@ -0,0 +1,73 @@
1
+ const crypto = require('crypto');
2
+ const fs = require('fs');
3
+ const config = require('../../../shared/config');
4
+
5
+ /**
6
+ * Asset Hash Service
7
+ *
8
+ * Provides SHA256-based hashing for theme asset files.
9
+ * Hashes are cached in memory and invalidated when file mtime changes.
10
+ */
11
+
12
+ // Hash length: 16 base64url characters (96 bits) for low collision probability
13
+ // base64url encoding provides more entropy per character than hex (64 vs 16 chars)
14
+ // This is longer than the legacy 10-char global hash, making it easy to distinguish
15
+ const HASH_LENGTH = 16;
16
+
17
+ // Cache structure: { filePath: { hash: string, mtimeMs: number } }
18
+ const hashCache = new Map();
19
+
20
+ /**
21
+ * Calculate SHA256 hash of a file's contents
22
+ * @param {string} filePath - Absolute path to the file
23
+ * @returns {string|null} - First 16 characters of SHA256 hash, or null if file doesn't exist
24
+ */
25
+ function getHashForFile(filePath) {
26
+ try {
27
+ const stat = fs.statSync(filePath);
28
+ const mtimeMs = stat.mtimeMs;
29
+
30
+ // Check cache
31
+ const cached = hashCache.get(filePath);
32
+ if (cached && cached.mtimeMs === mtimeMs) {
33
+ return cached.hash;
34
+ }
35
+
36
+ // Read file and compute hash
37
+ const content = fs.readFileSync(filePath);
38
+ const hash = crypto.createHash('sha256')
39
+ .update(content)
40
+ .digest('base64url')
41
+ .substring(0, HASH_LENGTH);
42
+
43
+ // Clear cache if it exceeds the size limit (safeguard against bugs)
44
+ const maxSize = config.get('caching:assets:contentBasedHash:maxSize');
45
+ if (hashCache.size >= maxSize) {
46
+ hashCache.clear();
47
+ }
48
+
49
+ // Cache the result
50
+ hashCache.set(filePath, {hash, mtimeMs});
51
+
52
+ return hash;
53
+ } catch (err) {
54
+ // File doesn't exist or can't be read
55
+ if (err.code === 'ENOENT' || err.code === 'EACCES') {
56
+ return null;
57
+ }
58
+ throw err;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Clear all cached hashes
64
+ * Should be called when theme is changed/remounted
65
+ */
66
+ function clearCache() {
67
+ hashCache.clear();
68
+ }
69
+
70
+ module.exports = {
71
+ getHashForFile,
72
+ clearCache
73
+ };
@@ -21,6 +21,7 @@ const engine = require('./engine');
21
21
  const themeI18n = require('./i18n');
22
22
  const themeI18next = require('./i18next');
23
23
  const labs = require('../../../shared/labs');
24
+ const assetHash = require('../asset-hash');
24
25
  // Current instance of ActiveTheme
25
26
  let currentActiveTheme;
26
27
 
@@ -119,9 +120,10 @@ class ActiveTheme {
119
120
  }
120
121
 
121
122
  mount(siteApp) {
122
- // reset the asset hash
123
- // @TODO: set this on the theme instead of globally, or use proper file-based hash
123
+ // Reset the global asset hash (used as fallback for non-theme assets)
124
124
  config.set('assetHash', null);
125
+ // Clear the file-based asset hash cache (for theme assets)
126
+ assetHash.clearCache();
125
127
  // clear the view cache
126
128
  siteApp.cache = {};
127
129
  // Set the views and engine
@@ -110,6 +110,12 @@ const forPost = (attrs, frame) => {
110
110
  _updateTextAttrs(attrs);
111
111
  }
112
112
 
113
+ // Replace member UUID placeholder for Transistor embeds (URL-encoded {uuid})
114
+ const member = frame.original.context.member;
115
+ if (member && member.uuid && attrs.html) {
116
+ attrs.html = attrs.html.replace(/%7Buuid%7D/gi, member.uuid);
117
+ }
118
+
113
119
  if (!Object.prototype.hasOwnProperty.call(frame.options, 'columns') || (frame.options.columns.includes('access'))) {
114
120
  attrs.access = memberHasAccess;
115
121
  }
@@ -0,0 +1,11 @@
1
+ const logging = require('@tryghost/logging');
2
+
3
+ // noop: this migration was moved to 6.18
4
+ module.exports = {
5
+ async up() {
6
+ logging.warn('Skipping migration - noop');
7
+ },
8
+ async down() {
9
+ logging.warn('Skipping migration - noop');
10
+ }
11
+ };
@@ -0,0 +1,4 @@
1
+ const {createSetNullableMigration} = require('../../utils');
2
+
3
+ // We need to disable foreign key checks because if MySQL is missing the STRICT_TRANS_TABLES mode, we cannot revert the migration
4
+ module.exports = createSetNullableMigration('offers', 'product_id', {disableForeignKeyChecks: true});
@@ -483,7 +483,7 @@ module.exports = {
483
483
  active: {type: 'boolean', nullable: false, defaultTo: true},
484
484
  name: {type: 'string', maxlength: 191, nullable: false, unique: true},
485
485
  code: {type: 'string', maxlength: 191, nullable: false, unique: true},
486
- product_id: {type: 'string', maxlength: 24, nullable: false, references: 'products.id'},
486
+ product_id: {type: 'string', maxlength: 24, nullable: true, references: 'products.id'},
487
487
  stripe_coupon_id: {type: 'string', maxlength: 255, nullable: true, unique: true},
488
488
  interval: {type: 'string', maxlength: 50, nullable: false, validations: {isIn: [['month', 'year']]}},
489
489
  currency: {type: 'string', maxlength: 50, nullable: true},
@@ -0,0 +1,13 @@
1
+ SCHEMA >
2
+ `site_uuid` String,
3
+ `day` Date,
4
+ `post_uuid` String,
5
+ `pathname` String,
6
+ `post_type` LowCardinality(String),
7
+ `member_status` LowCardinality(String),
8
+ `visits` AggregateFunction(uniqExact, String),
9
+ `hits` AggregateFunction(count, UInt64)
10
+
11
+ ENGINE "AggregatingMergeTree"
12
+ ENGINE_PARTITION_KEY "toYYYYMM(day)"
13
+ ENGINE_SORTING_KEY "site_uuid, day, post_type, member_status, post_uuid, pathname"
@@ -0,0 +1,113 @@
1
+ # api_top_pages_v3 - Optimized top pages endpoint using daily materialized view
2
+ #
3
+ # WHY: The original api_top_pages scans all raw hits and joins with filtered_sessions,
4
+ # which becomes slow (500ms+) and hits memory limits at scale (10M+ rows).
5
+ #
6
+ # HOW: Uses _mv_daily_pages (pre-aggregated by day) for historical data, querying
7
+ # only ~100-200K MV rows instead of millions of raw hits. Today's data is queried
8
+ # fresh from _mv_hits to ensure real-time accuracy.
9
+ #
10
+ # PERFORMANCE (10M rows):
11
+ # - 30-day query: 150ms vs 450ms (3x faster), 25K vs 12M rows scanned
12
+ # - All-time query: 300ms vs MEMORY_LIMIT_EXCEEDED
13
+ #
14
+ # DIFFERENCES: v3 may show slightly lower counts (typically 1-2 per page) for pages
15
+ # where sessions cross midnight at the date range boundary. This is more accurate -
16
+ # v1 incorrectly counts page views outside the requested date range when sessions
17
+ # span multiple days.
18
+
19
+ TOKEN "stats_page" READ
20
+ TOKEN "axis" READ
21
+
22
+ NODE historical_data
23
+ DESCRIPTION >
24
+ Query pre-aggregated daily data for complete days (excluding today).
25
+ Uses uniqExactMerge to combine exact unique session states across days.
26
+ Today is always excluded here and queried fresh from _mv_hits to avoid double-counting.
27
+
28
+ SQL >
29
+ %
30
+ SELECT
31
+ post_uuid,
32
+ pathname,
33
+ uniqExactMerge(visits) as visits
34
+ FROM _mv_daily_pages
35
+ WHERE
36
+ site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
37
+ AND day >= toDate({{ Date(date_from, description="Start date for filtering", required=True) }}, {{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }})
38
+ AND day <= toDate({{ Date(date_to, description="End date for filtering", required=True) }}, {{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }})
39
+ AND day != toDate(now(), {{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }})
40
+ {% if defined(member_status) %}
41
+ AND member_status IN (
42
+ SELECT arrayJoin(
43
+ {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }}
44
+ || if('paid' IN {{ Array(member_status) }}, ['comped'], [])
45
+ )
46
+ )
47
+ {% end %}
48
+ {% if defined(pathname) %} AND pathname = {{ String(pathname, description="Pathname to filter on", required=False) }} {% end %}
49
+ {% if defined(post_uuid) %} AND post_uuid = {{ String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %}
50
+ {% if defined(post_type) %}
51
+ {% if post_type == 'post' %}
52
+ AND post_type = 'post'
53
+ {% else %}
54
+ AND (post_type != 'post' OR post_type = '')
55
+ {% end %}
56
+ {% end %}
57
+ GROUP BY post_uuid, pathname
58
+
59
+ NODE today_data
60
+ DESCRIPTION >
61
+ Query raw _mv_hits for today's partial day to ensure freshness.
62
+
63
+ SQL >
64
+ %
65
+ SELECT
66
+ case when post_uuid = 'undefined' then '' else post_uuid end as post_uuid,
67
+ pathname,
68
+ uniqExact(session_id) as visits
69
+ FROM _mv_hits
70
+ WHERE
71
+ site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
72
+ AND toDate(timestamp, {{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }}) = toDate(now(), {{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }})
73
+ AND timestamp >= toDateTime({{ Date(date_from, description="Start date for filtering", required=True) }}, {{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }})
74
+ AND timestamp < toDateTime({{ Date(date_to, description="End date for filtering", required=True) }}, {{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }}) + interval 1 day
75
+ {% if defined(member_status) %}
76
+ AND member_status IN (
77
+ SELECT arrayJoin(
78
+ {{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }}
79
+ || if('paid' IN {{ Array(member_status) }}, ['comped'], [])
80
+ )
81
+ )
82
+ {% end %}
83
+ {% if defined(pathname) %} AND pathname = {{ String(pathname, description="Pathname to filter on", required=False) }} {% end %}
84
+ {% if defined(post_uuid) %} AND post_uuid = {{ String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %}
85
+ {% if defined(post_type) %}
86
+ {% if post_type == 'post' %}
87
+ AND post_type = 'post'
88
+ {% else %}
89
+ AND (post_type != 'post' OR post_type = '')
90
+ {% end %}
91
+ {% end %}
92
+ GROUP BY post_uuid, pathname
93
+
94
+ NODE combined_results
95
+ DESCRIPTION >
96
+ Combine historical pre-aggregated data with today's fresh data.
97
+
98
+ SQL >
99
+ %
100
+ SELECT
101
+ post_uuid,
102
+ pathname,
103
+ sum(visits) as visits
104
+ FROM (
105
+ SELECT * FROM historical_data
106
+ UNION ALL
107
+ SELECT * FROM today_data
108
+ )
109
+ GROUP BY post_uuid, pathname
110
+ ORDER BY visits DESC
111
+ LIMIT {{ Int32(skip, 0) }}, {{ Int32(limit, 50) }}
112
+
113
+ TYPE ENDPOINT
@@ -0,0 +1,23 @@
1
+ # mv_daily_pages - Daily aggregation of page visits for api_top_pages_v3
2
+ #
3
+ # Pre-aggregates unique sessions per page per day using uniqExactState,
4
+ # reducing query scans from millions of raw hits to thousands of daily rows.
5
+
6
+ TOKEN "axis" READ
7
+
8
+ NODE mv_daily_pages_0
9
+ SQL >
10
+ SELECT
11
+ site_uuid,
12
+ toDate(timestamp) as day,
13
+ case when post_uuid = 'undefined' then '' else post_uuid end as post_uuid,
14
+ pathname,
15
+ post_type,
16
+ member_status,
17
+ uniqExactState(session_id) as visits,
18
+ countState() as hits
19
+ FROM _mv_hits
20
+ GROUP BY site_uuid, day, post_uuid, pathname, post_type, member_status
21
+
22
+ TYPE materialized
23
+ DATASOURCE _mv_daily_pages