ghost 6.21.0 → 6.21.2

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 (198) hide show
  1. package/components/tryghost-i18n-6.21.2.tgz +0 -0
  2. package/components/{tryghost-parse-email-address-6.21.0.tgz → tryghost-parse-email-address-6.21.2.tgz} +0 -0
  3. package/content/themes/casper/LICENSE +1 -1
  4. package/content/themes/casper/README.md +7 -2
  5. package/content/themes/casper/assets/built/casper.js +1 -2
  6. package/content/themes/casper/assets/built/casper.js.map +1 -1
  7. package/content/themes/casper/assets/built/global.css +1 -2
  8. package/content/themes/casper/assets/built/global.css.map +1 -1
  9. package/content/themes/casper/assets/built/screen.css +1 -2
  10. package/content/themes/casper/assets/built/screen.css.map +1 -1
  11. package/content/themes/casper/assets/css/global.css +4 -0
  12. package/content/themes/casper/default.hbs +5 -5
  13. package/content/themes/casper/error-404.hbs +1 -1
  14. package/content/themes/casper/error.hbs +2 -2
  15. package/content/themes/casper/gulpfile.js +13 -2
  16. package/content/themes/casper/locales/context.json +112 -0
  17. package/content/themes/casper/locales/de-CH.json +112 -0
  18. package/content/themes/casper/locales/de.json +112 -0
  19. package/content/themes/casper/locales/en.json +112 -0
  20. package/content/themes/casper/locales/fr.json +112 -0
  21. package/content/themes/casper/locales/ga.json +112 -0
  22. package/content/themes/casper/locales/gd.json +112 -0
  23. package/content/themes/casper/locales/nl.json +112 -0
  24. package/content/themes/casper/locales/pt-BR.json +112 -0
  25. package/content/themes/casper/locales/sv.json +112 -0
  26. package/content/themes/casper/locales/uk.json +112 -0
  27. package/content/themes/casper/locales/zh.json +112 -0
  28. package/content/themes/casper/package.json +12 -11
  29. package/content/themes/casper/partials/post-card.hbs +4 -4
  30. package/content/themes/casper/post.hbs +4 -4
  31. package/content/themes/casper/tag.hbs +1 -1
  32. package/content/themes/source/LICENSE +1 -1
  33. package/content/themes/source/README.md +4 -1
  34. package/content/themes/source/assets/built/screen.css +1 -2
  35. package/content/themes/source/assets/built/screen.css.map +1 -1
  36. package/content/themes/source/assets/built/source.js +1 -2
  37. package/content/themes/source/assets/built/source.js.map +1 -1
  38. package/content/themes/source/assets/css/screen.css +18 -16
  39. package/content/themes/source/default.hbs +1 -1
  40. package/content/themes/source/gulpfile.js +5 -3
  41. package/content/themes/source/locales/context.json +112 -0
  42. package/content/themes/source/locales/de-CH.json +112 -0
  43. package/content/themes/source/locales/de.json +112 -0
  44. package/content/themes/source/locales/en.json +112 -0
  45. package/content/themes/source/locales/fr.json +112 -0
  46. package/content/themes/source/locales/ga.json +112 -0
  47. package/content/themes/source/locales/gd.json +112 -0
  48. package/content/themes/source/locales/nl.json +112 -0
  49. package/content/themes/source/locales/pt-BR.json +112 -0
  50. package/content/themes/source/locales/sv.json +112 -0
  51. package/content/themes/source/locales/uk.json +112 -0
  52. package/content/themes/source/locales/zh.json +112 -0
  53. package/content/themes/source/package.json +13 -11
  54. package/content/themes/source/partials/components/featured.hbs +1 -1
  55. package/content/themes/source/partials/components/footer.hbs +1 -1
  56. package/content/themes/source/partials/components/header-content.hbs +1 -1
  57. package/content/themes/source/partials/components/navigation.hbs +5 -5
  58. package/content/themes/source/partials/components/post-list.hbs +7 -7
  59. package/content/themes/source/partials/email-subscription.hbs +5 -5
  60. package/content/themes/source/partials/lightbox.hbs +6 -6
  61. package/content/themes/source/partials/post-card.hbs +1 -1
  62. package/content/themes/source/partials/search-toggle.hbs +1 -1
  63. package/content/themes/source/post.hbs +2 -2
  64. package/core/built/admin/assets/{PolarAngleAxis-a31TecIQ.js → PolarAngleAxis-D8t9UwHk.js} +1 -1
  65. package/core/built/admin/assets/{_baseAssignValue-Dw5pcaUJ.js → _baseAssignValue-DPyNU5o6.js} +1 -1
  66. package/core/built/admin/assets/{a-large-small-Cf_hUupE.js → a-large-small-BijOKCoa.js} +1 -1
  67. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
  68. package/core/built/admin/assets/admin-x-settings/{code-editor-view-C1ZFtR6M.mjs → code-editor-view-DJsXOcZ2.mjs} +2 -2
  69. package/core/built/admin/assets/admin-x-settings/{index-zyVbQw03.mjs → index-BnhIi5NU.mjs} +2 -2
  70. package/core/built/admin/assets/admin-x-settings/{index-D96qFjP9.mjs → index-CnwJhGbs.mjs} +2 -2
  71. package/core/built/admin/assets/admin-x-settings/{index-ILVAmH8Q.mjs → index-DaGaokMn.mjs} +1067 -1063
  72. package/core/built/admin/assets/admin-x-settings/{modals-BbDVa22r.mjs → modals-D02VJnTV.mjs} +2838 -2840
  73. package/core/built/admin/assets/{at-sign-DyMz5le1.js → at-sign-_JfhlimD.js} +1 -1
  74. package/core/built/admin/assets/{audience-BevM-Udw.js → audience-BdrhpNZ4.js} +1 -1
  75. package/core/built/admin/assets/{avatar-flipboard-DiGYhH_s.js → avatar-flipboard-BdmM8eOn.js} +1 -1
  76. package/core/built/admin/assets/{bluesky-sharing-Dj4VOEDi.js → bluesky-sharing-yeM8KkY8.js} +1 -1
  77. package/core/built/admin/assets/{chart-BDdnkPgC.js → chart-CULaDoVB.js} +1 -1
  78. package/core/built/admin/assets/{chunk.138.5c4b0d0ae3f21c9c726d.js → chunk.482.c45c97fe666cd1a6b060.js} +4 -4
  79. package/core/built/admin/assets/{chunk.524.54ba5b50c191627c46f2.js → chunk.524.7fc2692374515780e66e.js} +4 -4
  80. package/core/built/admin/assets/{chunk.582.3232d44081d84d135e3d.js → chunk.582.fd8c4fa8e637e2d5611e.js} +8 -8
  81. package/core/built/admin/assets/{code-editor-view-V0wBeYuE.js → code-editor-view-UA2xKjk1.js} +1 -1
  82. package/core/built/admin/assets/{comments-CqurDRGu.js → comments-DoNjlMYA.js} +1 -1
  83. package/core/built/admin/assets/{content-helpers-C8lVEp2H.js → content-helpers-BRzxAgjP.js} +1 -1
  84. package/core/built/admin/assets/{copy-BdseBMKx.js → copy-CbX-_mHj.js} +1 -1
  85. package/core/built/admin/assets/{data-list--5Xfh5gm.js → data-list-nZOiCWV9.js} +1 -1
  86. package/core/built/admin/assets/{deleted-feed-item-Bmxp6PAR.js → deleted-feed-item-DjkZpKDC.js} +1 -1
  87. package/core/built/admin/assets/{edit-profile-Bi-G8vrq.js → edit-profile-BY_oJrQn.js} +1 -1
  88. package/core/built/admin/assets/{empty-indicator-C1cLHSRX.js → empty-indicator-DoxAratw.js} +1 -1
  89. package/core/built/admin/assets/{en-DPpVO1wK.js → en-B9ScC5j9.js} +1 -1
  90. package/core/built/admin/assets/{feed-lCu_EJ0a.js → feed-BsgqABLQ.js} +1 -1
  91. package/core/built/admin/assets/{filters-BYYV0fmK.js → filters-CscMb_Gl.js} +1 -1
  92. package/core/built/admin/assets/{gh-chart-BYWaeEuY.js → gh-chart-GokHrG5_.js} +1 -1
  93. package/core/built/admin/assets/{ghost-565670d46ad5b30935426b5dfa95bc81.js → ghost-d1843f84a9476ccbdf2a350848ac88aa.js} +2 -2
  94. package/core/built/admin/assets/{growth-DXHnB1EQ.js → growth-CKOWA1_V.js} +1 -1
  95. package/core/built/admin/assets/{hash-tZATAzZa.js → hash-twG7-iIB.js} +1 -1
  96. package/core/built/admin/assets/{inbox-B-LuWyHY.js → inbox-BdkIJbSq.js} +1 -1
  97. package/core/built/admin/assets/{index-BDrJ3N3y.js → index-B7OzWwwP.js} +1 -1
  98. package/core/built/admin/assets/{index-B8tGZsrG.js → index-BFySh9qo.js} +1 -1
  99. package/core/built/admin/assets/{index-EiGE3Aiy.js → index-BOWUajrT.js} +1 -1
  100. package/core/built/admin/assets/{index-ig4J4RpO.js → index-BQLCImHQ.js} +1 -1
  101. package/core/built/admin/assets/{index-BmKPrbBK.js → index-C398bG84.js} +1 -1
  102. package/core/built/admin/assets/{index-BX0_v5NB.js → index-C5YCjM6z.js} +1 -1
  103. package/core/built/admin/assets/{index-DEwhVcR5.js → index-CHpxzPRR.js} +1 -1
  104. package/core/built/admin/assets/{index-CX1A5TwO.js → index-CM-iA6b-.js} +3 -3
  105. package/core/built/admin/assets/{index-9bj69FKq.js → index-CgCHCdle.js} +1 -1
  106. package/core/built/admin/assets/{index-BocVu6WC.js → index-Cw441AFq.js} +1 -1
  107. package/core/built/admin/assets/{index-jHIJgh6L.js → index-D9S88kRl.js} +1 -1
  108. package/core/built/admin/assets/{index-CqvBC0jL.js → index-DbgA2vK8.js} +1 -1
  109. package/core/built/admin/assets/{index-2H-Jp89Z.js → index-ghPdV1ln.js} +1 -1
  110. package/core/built/admin/assets/{index-BcOzgmTC.js → index-tX0pajfP.js} +1 -1
  111. package/core/built/admin/assets/{index-BxVUX0dN.js → index-upXLfQxx.js} +1 -1
  112. package/core/built/admin/assets/{koenig-lexical-BdX9U-oB.js → koenig-lexical-GZS7bHDz.js} +1 -1
  113. package/core/built/admin/assets/{kpi-card-DQ1AbLJB.js → kpi-card-B6fmS7IH.js} +1 -1
  114. package/core/built/admin/assets/{kpis-vcbQvnLh.js → kpis-B-JXQ-4C.js} +1 -1
  115. package/core/built/admin/assets/{label-CNmRJkLy.js → label-ChHoKmj0.js} +1 -1
  116. package/core/built/admin/assets/{links-BE51SJxR.js → links-DQSOljcR.js} +1 -1
  117. package/core/built/admin/assets/{list-filter-BwnQ0Xlp.js → list-filter-cWuCDOLb.js} +1 -1
  118. package/core/built/admin/assets/{loader-circle-CcLEGvsN.js → loader-circle-B5ILzHHu.js} +1 -1
  119. package/core/built/admin/assets/{lucide-react-DHFgHS1l.js → lucide-react-CM0rB7CS.js} +1 -1
  120. package/core/built/admin/assets/{main-layout-DsxBNVo-.js → main-layout-BE7vZmy0.js} +1 -1
  121. package/core/built/admin/assets/{members-3okEkvWv.js → members-C10Z_BuG.js} +1 -1
  122. package/core/built/admin/assets/{message-square-text-BkERj4f5.js → message-square-text-DVZjnBBF.js} +1 -1
  123. package/core/built/admin/assets/{minus-C6oMPJo2.js → minus-BWrVDnd-.js} +1 -1
  124. package/core/built/admin/assets/{modals-CfUec0CL.js → modals-Bx7uDUHI.js} +15 -15
  125. package/core/built/admin/assets/{moderation-DEHUqi1e.js → moderation-DwlKZoB5.js} +1 -1
  126. package/core/built/admin/assets/{newsletter-Cp-39ksw.js → newsletter-DkOl42UB.js} +1 -1
  127. package/core/built/admin/assets/{newsletters-Bj58VQa2.js → newsletters-DNt9qd4h.js} +1 -1
  128. package/core/built/admin/assets/{note-9ysQWJz6.js → note-C-BLqULD.js} +1 -1
  129. package/core/built/admin/assets/{offers-DORdeAfE.js → offers-CU0KFuyW.js} +1 -1
  130. package/core/built/admin/assets/{overview-aZmI_71o.js → overview-DAEelSKq.js} +1 -1
  131. package/core/built/admin/assets/{pagemenu-DK1tbdsq.js → pagemenu-Cr7_YYR8.js} +1 -1
  132. package/core/built/admin/assets/{post-analytics-context-D8nk51yA.js → post-analytics-context-B2ytyOU0.js} +1 -1
  133. package/core/built/admin/assets/{post-analytics-header-qh1LEasb.js → post-analytics-header-CvR25xxU.js} +1 -1
  134. package/core/built/admin/assets/{post-analytics-HdZI-1J7.js → post-analytics-qpfoP-57.js} +1 -1
  135. package/core/built/admin/assets/{post-share-modal-CvTf3PP9.js → post-share-modal-DDhiG0X4.js} +1 -1
  136. package/core/built/admin/assets/{posts-mIxckqHa.js → posts-DTy4lguT.js} +1 -1
  137. package/core/built/admin/assets/{referrers-qhD64pmp.js → referrers-BzKz9fT9.js} +1 -1
  138. package/core/built/admin/assets/{repeat-Idtsb62O.js → repeat-C_xWSLK1.js} +1 -1
  139. package/core/built/admin/assets/{reply-DXS-C-Rk.js → reply-CXnb6Juu.js} +1 -1
  140. package/core/built/admin/assets/{select-CeD8IhXy.js → select-WZUCQgjI.js} +1 -1
  141. package/core/built/admin/assets/{settings-BHfStwmS.js → settings-BC82mM6N.js} +1 -1
  142. package/core/built/admin/assets/{settings-M0QTt_tT.js → settings-EbntXZw9.js} +7 -7
  143. package/core/built/admin/assets/{sort-button-BoTFsBYE.js → sort-button-CFYf_NOQ.js} +1 -1
  144. package/core/built/admin/assets/{source-icon-opy8g_Nk.js → source-icon-xS8eeWX-.js} +1 -1
  145. package/core/built/admin/assets/{sprout-BKOFkdV8.js → sprout-CLmu2pln.js} +1 -1
  146. package/core/built/admin/assets/{square-BjZk7vJx.js → square-D0Y5-h0C.js} +1 -1
  147. package/core/built/admin/assets/{stats-DXXxmfRV.js → stats-B5O9DjYi.js} +1 -1
  148. package/core/built/admin/assets/{stats-view-D_5J0Nf7.js → stats-view-Cr6M1ycv.js} +1 -1
  149. package/core/built/admin/assets/{step-1-BcaR_MDC.js → step-1-DGbl6AAj.js} +1 -1
  150. package/core/built/admin/assets/{step-2-BR0jqEO_.js → step-2-Rf8oissi.js} +1 -1
  151. package/core/built/admin/assets/{step-3-CMPME0ky.js → step-3-tMPmbeBc.js} +1 -1
  152. package/core/built/admin/assets/{table-CwbnKMYe.js → table-CXo8NKU2.js} +1 -1
  153. package/core/built/admin/assets/{tabs-hfT89L_O.js → tabs-BMk71Tf2.js} +1 -1
  154. package/core/built/admin/assets/{tags-BSwYlj_e.js → tags-CSl0BjsA.js} +1 -1
  155. package/core/built/admin/assets/{tags-C3b5AHTm.js → tags-CqHx8i6Q.js} +1 -1
  156. package/core/built/admin/assets/{ticket-BnxPoED1.js → ticket-BtYGyQvH.js} +1 -1
  157. package/core/built/admin/assets/{tiers-DHKKzaBK.js → tiers-CPwRaPSt.js} +1 -1
  158. package/core/built/admin/assets/{toggle-group-U-Oqm2AA.js → toggle-group-QFge4qQs.js} +1 -1
  159. package/core/built/admin/assets/{topic-filter-28veMlW4.js → topic-filter-DnYtZ5e5.js} +1 -1
  160. package/core/built/admin/assets/{trash-D6TuYuIs.js → trash-B4ncKmRu.js} +1 -1
  161. package/core/built/admin/assets/{use-growth-stats-COmCRCzY.js → use-growth-stats-eFO-f1o1.js} +1 -1
  162. package/core/built/admin/assets/{use-infinite-virtual-scroll-mIEgtLoq.js → use-infinite-virtual-scroll-DTqnBtHM.js} +1 -1
  163. package/core/built/admin/assets/{use-scroll-restoration-CXVseKDj.js → use-scroll-restoration-o1DrSdlb.js} +1 -1
  164. package/core/built/admin/assets/{use-simple-pagination-5-LKveLR.js → use-simple-pagination-BsBGNcxc.js} +1 -1
  165. package/core/built/admin/assets/{user-plus-CKQ-d0d4.js → user-plus-BS8SGBmk.js} +1 -1
  166. package/core/built/admin/assets/{user-round-check-BWc7Da4S.js → user-round-check-BgOPeBFs.js} +1 -1
  167. package/core/built/admin/assets/{wallet-cards-BlDtHc6_.js → wallet-cards-Be7G77lR.js} +1 -1
  168. package/core/built/admin/assets/{web-Bvvtl44p.js → web-BNs01IAt.js} +1 -1
  169. package/core/built/admin/index.html +5 -5
  170. package/core/frontend/helpers/cancel_link.js +2 -2
  171. package/core/server/api/endpoints/authors-public.js +3 -3
  172. package/core/server/api/endpoints/files.js +2 -1
  173. package/core/server/api/endpoints/pages-public.js +3 -3
  174. package/core/server/api/endpoints/posts-public.js +3 -3
  175. package/core/server/api/endpoints/users.js +6 -2
  176. package/core/server/api/endpoints/utils/api-filter-utils.js +22 -0
  177. package/core/server/api/endpoints/utils/api-filter-utils.ts +22 -0
  178. package/core/server/lib/lexical.js +2 -1
  179. package/core/server/lib/mobiledoc.js +1 -1
  180. package/core/server/lib/request-external.js +78 -2
  181. package/core/server/services/email-service/email-templates/partials/styles.hbs +3 -1
  182. package/core/server/services/koenig/node-renderers/image-renderer.js +80 -10
  183. package/core/server/services/koenig/render-utils/srcset-attribute.js +15 -3
  184. package/core/server/services/members/members-api/controllers/router-controller.js +5 -5
  185. package/core/server/services/members/members-api/repositories/member-repository.js +1 -1
  186. package/core/server/services/members/members-api/utils/get-discount-window.js +70 -4
  187. package/core/server/services/members/members-api/utils/has-active-offer.js +10 -14
  188. package/core/server/services/oembed/oembed-service.js +9 -1
  189. package/core/server/services/offers/application/offer-mapper.js +48 -0
  190. package/core/server/services/offers/application/offers-api.js +2 -2
  191. package/core/shared/config/defaults.json +1 -1
  192. package/core/shared/labs.js +2 -1
  193. package/package.json +6 -6
  194. package/tsconfig.tsbuildinfo +1 -1
  195. package/yarn.lock +148 -16
  196. package/components/tryghost-i18n-6.21.0.tgz +0 -0
  197. package/core/server/api/endpoints/utils/public-endpoint-utils.js +0 -18
  198. package/core/server/api/endpoints/utils/public-endpoint-utils.ts +0 -17
@@ -1 +1 @@
1
- import{j as t,aW as be,aV as xe,bK as Se,bL as je,B as oe,bM as _e,bN as Ce,bO as Ne,bP as ve,ck as Le,ao as ye,an as Te,aY as Fe,r as l,cl as Ee,aX as Z,aO as Oe,ak as ne,al as $,cm as ke,bS as De,aT as Me,u as Ie,aZ as Ae,aU as Pe}from"./index-CX1A5TwO.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-CeD8IhXy.js";import{u as W,S as ie,a as re,A as J,b as z,c as X,U as le}from"./post-analytics-context-D8nk51yA.js";import{C as $e,F as Ge}from"./loader-circle-CcLEGvsN.js";import{g as We,a as He,S as Qe,b as Ye}from"./kpis-vcbQvnLh.js";import{F as Xe,c as H,e as ce}from"./en-DPpVO1wK.js";import{C as ue,a as qe,b as Ze,h as Je,c as de,i as et,u as K,j as ee}from"./pagemenu-DK1tbdsq.js";import{D as tt,h as st,i as te,a as at,b as ot,c as nt,d as it,e as rt,f as lt,g as ct}from"./data-list--5Xfh5gm.js";import{a as ut}from"./wallet-cards-BlDtHc6_.js";import{P as dt,a as mt}from"./post-analytics-header-qh1LEasb.js";import{F as pt,c as ht}from"./filters-BYYV0fmK.js";import{b as gt,c as ft,F as bt,S as xt,T as St}from"./lucide-react-DHFgHS1l.js";import{U as jt}from"./user-plus-CKQ-d0d4.js";import{M as _t}from"./ticket-BnxPoED1.js";import{F as Ct}from"./message-square-text-BkERj4f5.js";import{E as Nt}from"./empty-indicator-C1cLHSRX.js";import"./posts-mIxckqHa.js";import"./source-icon-opy8g_Nk.js";import"./source-utils-B1S3ZHA2.js";import"./gh-chart-BYWaeEuY.js";import"./chart-BDdnkPgC.js";import"./_baseAssignValue-Dw5pcaUJ.js";import"./tabs-hfT89L_O.js";import"./index-BX0_v5NB.js";import"./post-share-modal-CvTf3PP9.js";import"./get-site-timezone-BDBAcjmq.js";import"./post-helpers-gInwAwEv.js";import"./trash-D6TuYuIs.js";import"./sprout-BKOFkdV8.js";import"./a-large-small-Cf_hUupE.js";import"./list-filter-BwnQ0Xlp.js";import"./at-sign-DyMz5le1.js";import"./copy-BdseBMKx.js";import"./hash-tZATAzZa.js";import"./inbox-B-LuWyHY.js";import"./minus-C6oMPJo2.js";import"./tags-C3b5AHTm.js";import"./square-BjZk7vJx.js";import"./user-round-check-BWc7Da4S.js";import"./repeat-Idtsb62O.js";import"./reply-DXS-C-Rk.js";const se=()=>{const{range:e,setRange:s}=W();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))]})})]})};H.registerLocale(ce);const vt=e=>re[e]||H.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(tt,{children:[e&&t.jsxs(st,{children:[t.jsx(te,{children:"Country"}),t.jsx(te,{children:"Visitors"})]}),t.jsx(at,{children:s.map(i=>{const o=vt(`${i.location}`),r=a&&i.location!=="Unknown",c=i.location?i.location.toLowerCase():"unknown";return t.jsxs(ot,{className:r?"cursor-pointer":"","data-testid":`location-row-${c}`,onClick:r?()=>a(i.location):void 0,children:[t.jsx(nt,{style:{width:`${i.percentage?Math.round(i.percentage*100):0}%`}}),t.jsx(it,{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(rt,{children:[t.jsx(lt,{children:ye(Number(i.visits))}),t.jsx(ct,{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(qe,{className:"p-0",children:[t.jsx(Ze,{children:"Locations"}),t.jsx(Je,{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(et,{children:t.jsxs(Se,{children:[t.jsx(je,{asChild:!0,children:t.jsxs(oe,{variant:"outline",children:["View all ",t.jsx(ut,{})]})}),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?J:z.filter(s=>e.includes(s.value)).reduce((s,a)=>s|a.bit,0)||J,pe=e=>{const s=[];return(e&X.PUBLIC)!==0&&s.push(z[0].value),(e&X.FREE)!==0&&s.push(z[1].value),(e&X.PAID)!==0&&s.push(z[2].value),s.join(",")};H.registerLocale(ce);const Tt=e=>re[e]||H.getName(e,"en")||e,Ft=({visits:e})=>t.jsx("span",{className:"order-2 font-mono text-xs text-muted-foreground",children:e.toLocaleString()}),Et={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}=W(),{startDate:b,endDate:m,timezone:x}=ne(c),p=Et[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 F=String(h[p.valueKey]??""),A=Number(h.visits)||0,{value:E,label:y}=p.transformValue?p.transformValue(F):{value:F,label:F};return{label:y,value:E,icon:t.jsx(Ft,{visits:A})}}):[],[v,p]),loading:k}};function kt({filters:e,onChange:s,...a}){const{appSettings:i}=Fe(),{post:o}=W(),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(Z,{className:"text-gray-700"})},{value:"free",label:"Free members",icon:t.jsx(Oe,{className:"text-green"})},{value:"paid",label:"Paid members",icon:t.jsx(jt,{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:F,loading:A}=O("utm_content",e,r,{enabled:d("utm_content")}),{options:E,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:M}=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(_t,{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(xt,{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(Ct,{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(St,{className:"size-4"}),placeholder:"Select content",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:F,isLoading:A,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:E,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(Ee,{}),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(Z,{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(gt,{className:"size-4"}),placeholder:"Select device",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:U,isLoading:M,selectedOptionsClassName:"hidden"},{key:"location",label:"Location",type:"select",icon:t.jsx(ft,{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,F,A,E,y,_,p,R,D,U,M,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(pt,{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(bt,{}),"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 q=new Map;function Mt(e){return q.has(e)||q.set(e,`url-${e}-${Date.now()}-${Math.random().toString(36).substring(2,11)}`),q.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:Mt(o),field:o,operator:c,values:r})}}),s}function At(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 Ns=()=>{const e=Me(),{postId:s}=Ie(),{statsConfig:a,isLoading:i,range:o,data:r,post:c,isPostLoading:b}=W(),{filters:m,setFilters:x}=At(),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=Ae(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,ht(n,"is",[f])]),I()},[x,I]),F=l.useCallback(n=>h("location",n),[h]),A=l.useCallback(n=>h("source",n),[h]),E=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:E}),{data:D,loading:U}=K({endpoint:"api_top_locations",statsConfig:a||{id:""},params:E}),{data:M,loading:B}=K({endpoint:"api_top_sources",statsConfig:a||{id:""},params:E}),P=l.useMemo(()=>D?.reduce((n,f)=>n+Number(f.visits),0)||0,[D]),_=l.useMemo(()=>M?M.reduce((n,f)=>n+Number(f.visits||0),0):0,[M]),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(dt,{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(mt,{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(He,{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:F}),t.jsx(Qe,{data:M,range:d,siteIcon:w,siteUrl:Q,totalVisitors:_,onSourceClick:A})]})]}):t.jsx("div",{className:"grow",children:t.jsx(Nt,{className:"h-full",description:"Try adjusting filters to see more data.",title:`No visitors ${Ye(o)}`,children:t.jsx(Z,{strokeWidth:1.5})})})})]})};export{Ns as default};
1
+ import{j as t,aW as be,aV as xe,bK as Se,bL as je,B as oe,bM as _e,bN as Ce,bO as Ne,bP as ve,ck as Le,ao as ye,an as Te,aY as Fe,r as l,cl as Ee,aX as Z,aO as Oe,ak as ne,al as $,cm as ke,bS as De,aT as Me,u as Ie,aZ as Ae,aU as Pe}from"./index-CM-iA6b-.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-WZUCQgjI.js";import{u as W,S as ie,a as re,A as J,b as z,c as X,U as le}from"./post-analytics-context-B2ytyOU0.js";import{C as $e,F as Ge}from"./loader-circle-B5ILzHHu.js";import{g as We,a as He,S as Qe,b as Ye}from"./kpis-B-JXQ-4C.js";import{F as Xe,c as H,e as ce}from"./en-B9ScC5j9.js";import{C as ue,a as qe,b as Ze,h as Je,c as de,i as et,u as K,j as ee}from"./pagemenu-Cr7_YYR8.js";import{D as tt,h as st,i as te,a as at,b as ot,c as nt,d as it,e as rt,f as lt,g as ct}from"./data-list-nZOiCWV9.js";import{a as ut}from"./wallet-cards-Be7G77lR.js";import{P as dt,a as mt}from"./post-analytics-header-CvR25xxU.js";import{F as pt,c as ht}from"./filters-CscMb_Gl.js";import{b as gt,c as ft,F as bt,S as xt,T as St}from"./lucide-react-CM0rB7CS.js";import{U as jt}from"./user-plus-BS8SGBmk.js";import{M as _t}from"./ticket-BtYGyQvH.js";import{F as Ct}from"./message-square-text-DVZjnBBF.js";import{E as Nt}from"./empty-indicator-DoxAratw.js";import"./posts-DTy4lguT.js";import"./source-icon-xS8eeWX-.js";import"./source-utils-B1S3ZHA2.js";import"./gh-chart-GokHrG5_.js";import"./chart-CULaDoVB.js";import"./_baseAssignValue-DPyNU5o6.js";import"./tabs-BMk71Tf2.js";import"./index-C5YCjM6z.js";import"./post-share-modal-DDhiG0X4.js";import"./get-site-timezone-BDBAcjmq.js";import"./post-helpers-gInwAwEv.js";import"./trash-B4ncKmRu.js";import"./sprout-CLmu2pln.js";import"./a-large-small-BijOKCoa.js";import"./list-filter-cWuCDOLb.js";import"./at-sign-_JfhlimD.js";import"./copy-CbX-_mHj.js";import"./hash-twG7-iIB.js";import"./inbox-BdkIJbSq.js";import"./minus-BWrVDnd-.js";import"./tags-CqHx8i6Q.js";import"./square-D0Y5-h0C.js";import"./user-round-check-BgOPeBFs.js";import"./repeat-C_xWSLK1.js";import"./reply-CXnb6Juu.js";const se=()=>{const{range:e,setRange:s}=W();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))]})})]})};H.registerLocale(ce);const vt=e=>re[e]||H.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(tt,{children:[e&&t.jsxs(st,{children:[t.jsx(te,{children:"Country"}),t.jsx(te,{children:"Visitors"})]}),t.jsx(at,{children:s.map(i=>{const o=vt(`${i.location}`),r=a&&i.location!=="Unknown",c=i.location?i.location.toLowerCase():"unknown";return t.jsxs(ot,{className:r?"cursor-pointer":"","data-testid":`location-row-${c}`,onClick:r?()=>a(i.location):void 0,children:[t.jsx(nt,{style:{width:`${i.percentage?Math.round(i.percentage*100):0}%`}}),t.jsx(it,{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(rt,{children:[t.jsx(lt,{children:ye(Number(i.visits))}),t.jsx(ct,{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(qe,{className:"p-0",children:[t.jsx(Ze,{children:"Locations"}),t.jsx(Je,{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(et,{children:t.jsxs(Se,{children:[t.jsx(je,{asChild:!0,children:t.jsxs(oe,{variant:"outline",children:["View all ",t.jsx(ut,{})]})}),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?J:z.filter(s=>e.includes(s.value)).reduce((s,a)=>s|a.bit,0)||J,pe=e=>{const s=[];return(e&X.PUBLIC)!==0&&s.push(z[0].value),(e&X.FREE)!==0&&s.push(z[1].value),(e&X.PAID)!==0&&s.push(z[2].value),s.join(",")};H.registerLocale(ce);const Tt=e=>re[e]||H.getName(e,"en")||e,Ft=({visits:e})=>t.jsx("span",{className:"order-2 font-mono text-xs text-muted-foreground",children:e.toLocaleString()}),Et={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}=W(),{startDate:b,endDate:m,timezone:x}=ne(c),p=Et[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 F=String(h[p.valueKey]??""),A=Number(h.visits)||0,{value:E,label:y}=p.transformValue?p.transformValue(F):{value:F,label:F};return{label:y,value:E,icon:t.jsx(Ft,{visits:A})}}):[],[v,p]),loading:k}};function kt({filters:e,onChange:s,...a}){const{appSettings:i}=Fe(),{post:o}=W(),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(Z,{className:"text-gray-700"})},{value:"free",label:"Free members",icon:t.jsx(Oe,{className:"text-green"})},{value:"paid",label:"Paid members",icon:t.jsx(jt,{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:F,loading:A}=O("utm_content",e,r,{enabled:d("utm_content")}),{options:E,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:M}=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(_t,{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(xt,{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(Ct,{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(St,{className:"size-4"}),placeholder:"Select content",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:F,isLoading:A,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:E,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(Ee,{}),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(Z,{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(gt,{className:"size-4"}),placeholder:"Select device",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:U,isLoading:M,selectedOptionsClassName:"hidden"},{key:"location",label:"Location",type:"select",icon:t.jsx(ft,{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,F,A,E,y,_,p,R,D,U,M,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(pt,{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(bt,{}),"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 q=new Map;function Mt(e){return q.has(e)||q.set(e,`url-${e}-${Date.now()}-${Math.random().toString(36).substring(2,11)}`),q.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:Mt(o),field:o,operator:c,values:r})}}),s}function At(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 Ns=()=>{const e=Me(),{postId:s}=Ie(),{statsConfig:a,isLoading:i,range:o,data:r,post:c,isPostLoading:b}=W(),{filters:m,setFilters:x}=At(),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=Ae(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,ht(n,"is",[f])]),I()},[x,I]),F=l.useCallback(n=>h("location",n),[h]),A=l.useCallback(n=>h("source",n),[h]),E=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:E}),{data:D,loading:U}=K({endpoint:"api_top_locations",statsConfig:a||{id:""},params:E}),{data:M,loading:B}=K({endpoint:"api_top_sources",statsConfig:a||{id:""},params:E}),P=l.useMemo(()=>D?.reduce((n,f)=>n+Number(f.visits),0)||0,[D]),_=l.useMemo(()=>M?M.reduce((n,f)=>n+Number(f.visits||0),0):0,[M]),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(dt,{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(mt,{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(He,{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:F}),t.jsx(Qe,{data:M,range:d,siteIcon:w,siteUrl:Q,totalVisitors:_,onSourceClick:A})]})]}):t.jsx("div",{className:"grow",children:t.jsx(Nt,{className:"h-full",description:"Try adjusting filters to see more data.",title:`No visitors ${Ye(o)}`,children:t.jsx(Z,{strokeWidth:1.5})})})})]})};export{Ns as default};
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <link rel="stylesheet" href="./assets/vendor-0ede59da8efb5e28fa929557f7ff7154.css">
5
5
  <link rel="stylesheet" href="./assets/ghost-1627c2071621864e79a845529769ff19.css">
6
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%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.21%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%228785c87985%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%223574dc2aad%22%2C%22activitypubFilename%22%3A%22activitypub.js%22%2C%22activitypubHash%22%3A%2258788e91cd%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%22b8baca4053%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%22a37abdfe6f%22%2C%22activitypubRemoteConfigUrl%22%3A%22%2F.ghost%2Factivitypub%2Fstable%2Fclient-config%22%7D">
6
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%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.21%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%228785c87985%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%2224fb0ad2c1%22%2C%22activitypubFilename%22%3A%22activitypub.js%22%2C%22activitypubHash%22%3A%2258788e91cd%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%22b8baca4053%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%22a37abdfe6f%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-CX1A5TwO.js"></script>
20
+ <script type="module" crossorigin src="./assets/index-CM-iA6b-.js"></script>
21
21
  <link rel="stylesheet" crossorigin href="./assets/index-BQQ-gECj.css">
22
22
  </head>
23
23
  <body class="react-admin">
@@ -39,8 +39,8 @@
39
39
  <div id="ember-notifications-wormhole"></div>
40
40
  <script src="./assets/vendor-0e6161f8da2ad46cf62cef0a53cfb16a.js"></script>
41
41
  <script src="./assets/chunk.119.8ed8af850063f930a797.js"></script>
42
- <script src="./assets/chunk.138.5c4b0d0ae3f21c9c726d.js"></script>
43
- <script src="./assets/chunk.524.54ba5b50c191627c46f2.js"></script>
44
- <script src="./assets/ghost-565670d46ad5b30935426b5dfa95bc81.js"></script>
42
+ <script src="./assets/chunk.482.c45c97fe666cd1a6b060.js"></script>
43
+ <script src="./assets/chunk.524.7fc2692374515780e66e.js"></script>
44
+ <script src="./assets/ghost-d1843f84a9476ccbdf2a350848ac88aa.js"></script>
45
45
  </body>
46
46
  </html>
@@ -4,7 +4,7 @@
4
4
  // Should be used inside of a subscription context, e.g.: `{{#foreach @member.subscriptions}} {{cancel_link}} {{/foreach}}`
5
5
  // Outputs cancel/renew links to manage subscription renewal after the subscription period ends.
6
6
  //
7
- // Defaults to class="cancel-subscription-link" errorClass="cancel-subscription-error" cancelLabel="Cancel subscription" continueLabel="Continue subscription"
7
+ // Defaults to class="cancel-subscription-link" errorClass="cancel-subscription-error" cancelLabel="Cancel subscription" continueLabel="Resume subscription"
8
8
  const {labs} = require('../services/proxy');
9
9
  const {templates} = require('../services/handlebars');
10
10
 
@@ -28,7 +28,7 @@ function cancel_link(options) { // eslint-disable-line camelcase
28
28
  class: truncateOptions.class || 'gh-subscription-cancel',
29
29
  errorClass: truncateOptions.errorClass || 'gh-error gh-error-subscription-cancel',
30
30
  cancelLabel: truncateOptions.cancelLabel || 'Cancel subscription',
31
- continueLabel: truncateOptions.continueLabel || 'Continue subscription'
31
+ continueLabel: truncateOptions.continueLabel || 'Resume subscription'
32
32
  };
33
33
 
34
34
  return templates.execute('cancel_link', data);
@@ -1,7 +1,7 @@
1
1
  const tpl = require('@tryghost/tpl');
2
2
  const errors = require('@tryghost/errors');
3
3
  const models = require('../../models');
4
- const {rejectPrivateFieldsTransformer} = require('./utils/public-endpoint-utils');
4
+ const {rejectContentApiRestrictedFieldsTransformer} = require('./utils/api-filter-utils');
5
5
 
6
6
  const ALLOWED_INCLUDES = ['count.posts'];
7
7
 
@@ -36,7 +36,7 @@ const controller = {
36
36
  query(frame) {
37
37
  const options = {
38
38
  ...frame.options,
39
- mongoTransformer: rejectPrivateFieldsTransformer
39
+ mongoTransformer: rejectContentApiRestrictedFieldsTransformer
40
40
  };
41
41
  return models.Author.findPage(options);
42
42
  }
@@ -68,7 +68,7 @@ const controller = {
68
68
  async query(frame) {
69
69
  const options = {
70
70
  ...frame.options,
71
- mongoTransformer: rejectPrivateFieldsTransformer
71
+ mongoTransformer: rejectContentApiRestrictedFieldsTransformer
72
72
  };
73
73
 
74
74
  const model = await models.Author.findOne(frame.data, options);
@@ -1,4 +1,5 @@
1
1
  const storage = require('../../adapters/storage');
2
+ const mime = require('mime-types');
2
3
 
3
4
  /** @type {import('@tryghost/api-framework').Controller} */
4
5
  const controller = {
@@ -13,7 +14,7 @@ const controller = {
13
14
  const filePath = await storage.getStorage('files').save({
14
15
  name: frame.file.originalname,
15
16
  path: frame.file.path,
16
- type: frame.file.mimetype
17
+ type: mime.lookup(frame.file.originalname) || 'application/octet-stream'
17
18
  });
18
19
 
19
20
  return {
@@ -1,7 +1,7 @@
1
1
  const tpl = require('@tryghost/tpl');
2
2
  const errors = require('@tryghost/errors');
3
3
  const models = require('../../models');
4
- const {rejectPrivateFieldsTransformer} = require('./utils/public-endpoint-utils');
4
+ const {rejectContentApiRestrictedFieldsTransformer} = require('./utils/api-filter-utils');
5
5
 
6
6
  const ALLOWED_INCLUDES = ['tags', 'authors', 'tiers'];
7
7
 
@@ -42,7 +42,7 @@ const controller = {
42
42
  query(frame) {
43
43
  const options = {
44
44
  ...frame.options,
45
- mongoTransformer: rejectPrivateFieldsTransformer
45
+ mongoTransformer: rejectContentApiRestrictedFieldsTransformer
46
46
  };
47
47
  return models.Post.findPage(options);
48
48
  }
@@ -78,7 +78,7 @@ const controller = {
78
78
  async query(frame) {
79
79
  const options = {
80
80
  ...frame.options,
81
- mongoTransformer: rejectPrivateFieldsTransformer
81
+ mongoTransformer: rejectContentApiRestrictedFieldsTransformer
82
82
  };
83
83
  const model = await models.Post.findOne(frame.data, options);
84
84
  if (!model) {
@@ -4,7 +4,7 @@ const errors = require('@tryghost/errors');
4
4
  const postsPublicService = require('../../services/posts-public');
5
5
  const getPostServiceInstance = require('../../services/posts/posts-service-instance');
6
6
  const postsService = getPostServiceInstance();
7
- const {rejectPrivateFieldsTransformer} = require('./utils/public-endpoint-utils');
7
+ const {rejectContentApiRestrictedFieldsTransformer} = require('./utils/api-filter-utils');
8
8
 
9
9
  const ALLOWED_INCLUDES = ['tags', 'authors', 'tiers', 'sentiment'];
10
10
 
@@ -100,7 +100,7 @@ const controller = {
100
100
  query(frame) {
101
101
  const options = {
102
102
  ...frame.options,
103
- mongoTransformer: rejectPrivateFieldsTransformer
103
+ mongoTransformer: rejectContentApiRestrictedFieldsTransformer
104
104
  };
105
105
  return postsService.browsePosts(options);
106
106
  }
@@ -154,7 +154,7 @@ const controller = {
154
154
  async query(frame) {
155
155
  const options = {
156
156
  ...frame.options,
157
- mongoTransformer: rejectPrivateFieldsTransformer
157
+ mongoTransformer: rejectContentApiRestrictedFieldsTransformer
158
158
  };
159
159
  const model = await models.Post.findOne(frame.data, options);
160
160
  if (!model) {
@@ -6,6 +6,7 @@ const dbBackup = require('../../data/db/backup');
6
6
  const auth = require('../../services/auth');
7
7
  const apiMail = require('./index').mail;
8
8
  const apiSettings = require('./index').settings;
9
+ const {rejectAdminApiRestrictedFieldsTransformer} = require('./utils/api-filter-utils');
9
10
  const UsersService = require('../../services/users');
10
11
  const userService = new UsersService({dbBackup, models, auth, apiMail, apiSettings});
11
12
  const ALLOWED_INCLUDES = ['count.posts', 'permissions', 'roles', 'roles.permissions'];
@@ -106,7 +107,11 @@ const controller = {
106
107
  },
107
108
  permissions: true,
108
109
  query(frame) {
109
- return models.User.findPage(frame.options);
110
+ const options = {
111
+ ...frame.options,
112
+ mongoTransformer: rejectAdminApiRestrictedFieldsTransformer
113
+ };
114
+ return models.User.findPage(options);
110
115
  }
111
116
  },
112
117
 
@@ -116,7 +121,6 @@ const controller = {
116
121
  },
117
122
  options: [
118
123
  'include',
119
- 'filter',
120
124
  'fields',
121
125
  'debug'
122
126
  ],
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rejectAdminApiRestrictedFieldsTransformer = exports.rejectContentApiRestrictedFieldsTransformer = void 0;
4
+ const mongo_utils_1 = require("@tryghost/mongo-utils");
5
+ const CONTENT_API_RESTRICTED_FIELDS = new Set([
6
+ 'password',
7
+ 'email'
8
+ ]);
9
+ const ADMIN_API_RESTRICTED_FIELDS = new Set([
10
+ 'password'
11
+ ]);
12
+ function hasRestrictedSegment(key, fields) {
13
+ return key.toLowerCase().split('.').some(segment => fields.has(segment));
14
+ }
15
+ const rejectContentApiRestrictedFieldsTransformer = (input) => {
16
+ return (0, mongo_utils_1.rejectStatements)(input, (key) => hasRestrictedSegment(key, CONTENT_API_RESTRICTED_FIELDS));
17
+ };
18
+ exports.rejectContentApiRestrictedFieldsTransformer = rejectContentApiRestrictedFieldsTransformer;
19
+ const rejectAdminApiRestrictedFieldsTransformer = (input) => {
20
+ return (0, mongo_utils_1.rejectStatements)(input, (key) => hasRestrictedSegment(key, ADMIN_API_RESTRICTED_FIELDS));
21
+ };
22
+ exports.rejectAdminApiRestrictedFieldsTransformer = rejectAdminApiRestrictedFieldsTransformer;
@@ -0,0 +1,22 @@
1
+ import {rejectStatements} from '@tryghost/mongo-utils';
2
+
3
+ const CONTENT_API_RESTRICTED_FIELDS = new Set([
4
+ 'password',
5
+ 'email'
6
+ ]);
7
+
8
+ const ADMIN_API_RESTRICTED_FIELDS = new Set([
9
+ 'password'
10
+ ]);
11
+
12
+ function hasRestrictedSegment(key: string, fields: Set<string>): boolean {
13
+ return key.toLowerCase().split('.').some(segment => fields.has(segment));
14
+ }
15
+
16
+ export const rejectContentApiRestrictedFieldsTransformer = (input: unknown) => {
17
+ return rejectStatements(input, (key: string) => hasRestrictedSegment(key, CONTENT_API_RESTRICTED_FIELDS));
18
+ };
19
+
20
+ export const rejectAdminApiRestrictedFieldsTransformer = (input: unknown) => {
21
+ return rejectStatements(input, (key: string) => hasRestrictedSegment(key, ADMIN_API_RESTRICTED_FIELDS));
22
+ };
@@ -97,7 +97,8 @@ module.exports = {
97
97
  feature: {
98
98
  contentVisibility: true, // force on until Koenig has been bumped
99
99
  emailCustomization: true, // force on until Koenig has been bumped
100
- emailUniqueid: labs.isSet('emailUniqueid')
100
+ emailUniqueid: labs.isSet('emailUniqueid'),
101
+ pictureImageFormats: labs.isSet('pictureImageFormats')
101
102
  },
102
103
  nodeRenderers: this.customNodeRenderers
103
104
  }, userOptions);
@@ -140,7 +140,7 @@ module.exports = {
140
140
  size = await getUnsplashSize(payload.src);
141
141
  } else if (isRelativeImagePath || storageUtils.isLocalImage(payload.src)) {
142
142
  size = await imageSize.getOriginalImageSizeFromStorageUrl(payload.src);
143
- } else {
143
+ } else if (storageUtils.isInternalImage(payload.src)) {
144
144
  size = await imageSize.getImageSizeFromUrl(payload.src);
145
145
  }
146
146
 
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  const got = /** @type {Got} */ (/** @type {unknown} */ (require('got')));
7
+ const dns = require('dns');
7
8
  const net = require('net');
8
9
  const dnsPromises = require('dns').promises;
9
10
  const errors = require('@tryghost/errors');
@@ -139,6 +140,23 @@ function isPrivateIp(addr) {
139
140
  if (/^fe[89ab][0-9a-f]:/i.test(normalized6)) {
140
141
  return true;
141
142
  }
143
+ // Re-check for IPv4-mapped IPv6 after normalization
144
+ // Handles expanded forms like 0:0:0:0:0:ffff:127.0.0.1 which normalize to ::ffff:...
145
+ const v4DottedNorm = normalized6.match(/^::ffff:(\d[\d.]+)$/i);
146
+ if (v4DottedNorm) {
147
+ const normV4 = normalizeIPv4(v4DottedNorm[1]);
148
+ if (normV4) {
149
+ return isPrivateIPv4(normV4);
150
+ }
151
+ return true;
152
+ }
153
+ const v4HexNorm = normalized6.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
154
+ if (v4HexNorm) {
155
+ const hi = parseInt(v4HexNorm[1], 16);
156
+ const lo = parseInt(v4HexNorm[2], 16);
157
+ const mapped = ((hi >> 8) & 0xff) + '.' + (hi & 0xff) + '.' + ((lo >> 8) & 0xff) + '.' + (lo & 0xff);
158
+ return isPrivateIPv4(mapped);
159
+ }
142
160
  return false;
143
161
  }
144
162
 
@@ -193,8 +211,65 @@ async function disableRetries(options) {
193
211
  options.timeout = 5000;
194
212
  }
195
213
 
214
+ /**
215
+ * Install a custom dnsLookup on the request options that validates the resolved
216
+ * IP at connection time. This eliminates the DNS rebinding / TOCTOU gap between
217
+ * the beforeRequest DNS check and the actual TCP connection: the IP validated
218
+ * here is the same one Node's http module will connect to.
219
+ */
220
+ function installSafeDnsLookup(options) {
221
+ if (config.get('env') === 'development') {
222
+ return;
223
+ }
224
+
225
+ const siteUrl = new URL(config.get('url'));
226
+ if (options.url.host === siteUrl.host) {
227
+ return;
228
+ }
229
+
230
+ const requestHref = options.url.href;
231
+ // Use 'lookup' (the native http.request option) rather than 'dnsLookup'
232
+ // (got's public API property which doesn't flow to the native request).
233
+ options.lookup = (hostname, dnsOpts, callback) => {
234
+ if (typeof dnsOpts === 'function') {
235
+ callback = dnsOpts;
236
+ dnsOpts = {};
237
+ }
238
+ dns.lookup(hostname, dnsOpts, (err, addressOrResult, family) => {
239
+ if (err) {
240
+ return callback(err, addressOrResult, family);
241
+ }
242
+ // When all:true, result is an array of {address, family} objects
243
+ if (dnsOpts && dnsOpts.all) {
244
+ const results = /** @type {{address: string, family: number}[]} */ (addressOrResult);
245
+ for (const entry of results) {
246
+ if (isPrivateIp(entry.address)) {
247
+ return callback(new errors.InternalServerError({
248
+ message: 'URL resolves to a non-permitted private IP block',
249
+ code: 'URL_PRIVATE_INVALID',
250
+ context: requestHref
251
+ }));
252
+ }
253
+ }
254
+ return callback(null, results);
255
+ }
256
+ if (isPrivateIp(/** @type {string} */ (addressOrResult))) {
257
+ return callback(new errors.InternalServerError({
258
+ message: 'URL resolves to a non-permitted private IP block',
259
+ code: 'URL_PRIVATE_INVALID',
260
+ context: requestHref
261
+ }));
262
+ }
263
+ callback(null, addressOrResult, family);
264
+ });
265
+ };
266
+ }
267
+
196
268
  // same as our normal request lib but if any request in a redirect chain resolves
197
269
  // to a private IP address it will be blocked before the request is made.
270
+ // The beforeRequest hooks provide a first-pass DNS check with clear error messages.
271
+ // installSafeDnsLookup provides the authoritative gate at the connection layer,
272
+ // preventing DNS rebinding attacks where the IP changes between check and connect.
198
273
  /** @type {ExtendOptions} */
199
274
  const gotOpts = {
200
275
  headers: {
@@ -203,11 +278,12 @@ const gotOpts = {
203
278
  timeout: 10000, // default is no timeout
204
279
  hooks: {
205
280
  init: process.env.NODE_ENV?.startsWith('test') ? [disableRetries] : [],
206
- beforeRequest: [errorIfInvalidUrl, errorIfHostnameResolvesToPrivateIp],
207
- beforeRedirect: [errorIfHostnameResolvesToPrivateIp]
281
+ beforeRequest: [errorIfInvalidUrl, errorIfHostnameResolvesToPrivateIp, installSafeDnsLookup],
282
+ beforeRedirect: [errorIfHostnameResolvesToPrivateIp, installSafeDnsLookup]
208
283
  }
209
284
  };
210
285
 
211
286
  const externalRequest = got.extend(gotOpts);
212
287
  externalRequest.isPrivateIp = isPrivateIp;
288
+ externalRequest._installSafeDnsLookup = installSafeDnsLookup;
213
289
  module.exports = externalRequest;
@@ -1048,7 +1048,8 @@ td.kg-card-spacing {
1048
1048
  }
1049
1049
 
1050
1050
  .kg-bookmark-thumbnail {
1051
- min-width: 140px;
1051
+ flex: 0 1 33%;
1052
+ width: 33%;
1052
1053
  max-width: 180px;
1053
1054
  background-repeat: no-repeat;
1054
1055
  background-size: cover;
@@ -1082,6 +1083,7 @@ td.kg-card-spacing {
1082
1083
 
1083
1084
  .kg-bookmark-author {
1084
1085
  line-height: 1.5em;
1086
+ white-space: nowrap;
1085
1087
  }
1086
1088
 
1087
1089
  .kg-bookmark-publisher {
@@ -1,10 +1,24 @@
1
+ const path = require('path');
2
+ const imageTransform = require('@tryghost/image-transform');
1
3
  const {getAvailableImageWidths} = require('../render-utils/get-available-image-widths');
2
4
  const {isContentImage} = require('../render-utils/is-content-image');
3
- const {setSrcsetAttribute} = require('../render-utils/srcset-attribute');
5
+ const {getSrcsetAttribute, setSrcsetAttribute} = require('../render-utils/srcset-attribute');
4
6
  const {getResizedImageDimensions} = require('../render-utils/get-resized-image-dimensions');
5
7
  const {addCreateDocumentOption} = require('../render-utils/add-create-document-option');
6
8
  const {renderEmptyContainer} = require('../render-utils/render-empty-container');
7
9
 
10
+ const MODERN_IMAGE_FORMATS = ['avif', 'webp'];
11
+
12
+ function isAnimatedImage(url = '') {
13
+ try {
14
+ const parsedUrl = new URL(url, 'http://localhost');
15
+ const extension = path.extname(parsedUrl.pathname).toLowerCase();
16
+ return extension === '.gif';
17
+ } catch (err) {
18
+ return false;
19
+ }
20
+ }
21
+
8
22
  function renderImageNode(node, options = {}) {
9
23
  addCreateDocumentOption(options);
10
24
 
@@ -62,22 +76,78 @@ function renderImageNode(node, options = {}) {
62
76
  img.setAttribute('height', height);
63
77
  }
64
78
 
79
+ const imgAttributes = {
80
+ src: node.src,
81
+ width: node.width,
82
+ height: node.height
83
+ };
84
+
85
+ let picture = null;
86
+
65
87
  if (options.target !== 'email') {
66
- const imgAttributes = {
67
- src: node.src,
68
- width: node.width,
69
- height: node.height
70
- };
71
88
  setSrcsetAttribute(img, imgAttributes, options);
72
89
 
90
+ let sizes;
73
91
  if (img.getAttribute('srcset') && node.width && node.width >= 720) {
74
92
  // standard size
75
93
  if (!node.cardWidth || node.cardWidth === 'regular') {
76
- img.setAttribute('sizes', '(min-width: 720px) 720px');
94
+ sizes = '(min-width: 720px) 720px';
77
95
  }
78
96
 
79
97
  if (node.cardWidth === 'wide' && node.width >= 1200) {
80
- img.setAttribute('sizes', '(min-width: 1200px) 1200px');
98
+ sizes = '(min-width: 1200px) 1200px';
99
+ }
100
+ }
101
+
102
+ if (sizes) {
103
+ img.setAttribute('sizes', sizes);
104
+ }
105
+
106
+ const shouldRenderPicture = Boolean(
107
+ options.feature?.pictureImageFormats &&
108
+ img.getAttribute('srcset') &&
109
+ !isAnimatedImage(node.src) &&
110
+ isContentImage(node.src, options.siteUrl, options.imageBaseUrl) &&
111
+ options.canTransformImage?.(node.src) &&
112
+ imageTransform.canTransformFiles()
113
+ );
114
+
115
+ if (shouldRenderPicture) {
116
+ picture = document.createElement('picture');
117
+ let sourcesAdded = false;
118
+
119
+ MODERN_IMAGE_FORMATS.forEach((format) => {
120
+ if (!imageTransform.canTransformToFormat(format)) {
121
+ return;
122
+ }
123
+
124
+ const formattedSrcset = getSrcsetAttribute({
125
+ src: node.src,
126
+ width: node.width,
127
+ options,
128
+ format
129
+ });
130
+
131
+ if (!formattedSrcset) {
132
+ return;
133
+ }
134
+
135
+ const source = document.createElement('source');
136
+ source.setAttribute('srcset', formattedSrcset);
137
+ source.setAttribute('type', `image/${format}`);
138
+
139
+ if (sizes) {
140
+ source.setAttribute('sizes', sizes);
141
+ }
142
+
143
+ picture.appendChild(source);
144
+ sourcesAdded = true;
145
+ });
146
+
147
+ if (sourcesAdded) {
148
+ picture.appendChild(img);
149
+ } else {
150
+ picture = null;
81
151
  }
82
152
  }
83
153
  }
@@ -113,10 +183,10 @@ function renderImageNode(node, options = {}) {
113
183
  if (node.href) {
114
184
  const a = document.createElement('a');
115
185
  a.setAttribute('href', node.href);
116
- a.appendChild(img);
186
+ a.appendChild(picture || img);
117
187
  figure.appendChild(a);
118
188
  } else {
119
- figure.appendChild(img);
189
+ figure.appendChild(picture || img);
120
190
  }
121
191
 
122
192
  if (node.caption) {
@@ -4,7 +4,7 @@ const {isUnsplashImage} = require('./is-unsplash-image');
4
4
 
5
5
  // default content sizes: [600, 1000, 1600, 2400]
6
6
 
7
- const getSrcsetAttribute = function ({src, width, options}) {
7
+ const getSrcsetAttribute = function ({src, width, options, format}) {
8
8
  if (!options.imageOptimization || options.imageOptimization.srcsets === false || !width || !options.imageOptimization.contentImageSizes) {
9
9
  return;
10
10
  }
@@ -23,10 +23,19 @@ const getSrcsetAttribute = function ({src, width, options}) {
23
23
  srcsetWidths.forEach((srcsetWidth) => {
24
24
  if (srcsetWidth === width) {
25
25
  // use original image path if width matches exactly (avoids 302s from size->original)
26
- srcs.push(`${src} ${srcsetWidth}w`);
26
+ // unless a specific output format was requested
27
+ if (format) {
28
+ srcs.push(`${imagesPath}/size/w${srcsetWidth}/format/${format}/${filename} ${srcsetWidth}w`);
29
+ } else {
30
+ srcs.push(`${src} ${srcsetWidth}w`);
31
+ }
27
32
  } else if (srcsetWidth <= width) {
28
33
  // avoid creating srcset sizes larger than intrinsic image width
29
- srcs.push(`${imagesPath}/size/w${srcsetWidth}/${filename} ${srcsetWidth}w`);
34
+ if (format) {
35
+ srcs.push(`${imagesPath}/size/w${srcsetWidth}/format/${format}/${filename} ${srcsetWidth}w`);
36
+ } else {
37
+ srcs.push(`${imagesPath}/size/w${srcsetWidth}/${filename} ${srcsetWidth}w`);
38
+ }
30
39
  }
31
40
  });
32
41
 
@@ -42,6 +51,9 @@ const getSrcsetAttribute = function ({src, width, options}) {
42
51
 
43
52
  srcsetWidths.forEach((srcsetWidth) => {
44
53
  unsplashUrl.searchParams.set('w', srcsetWidth);
54
+ if (format) {
55
+ unsplashUrl.searchParams.set('fm', format);
56
+ }
45
57
  srcs.push(`${unsplashUrl.href} ${srcsetWidth}w`);
46
58
  });
47
59