ghost 6.18.1 → 6.18.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.
- package/components/tryghost-i18n-6.18.2.tgz +0 -0
- package/components/{tryghost-parse-email-address-6.18.1.tgz → tryghost-parse-email-address-6.18.2.tgz} +0 -0
- package/content/themes/source/package.json +1 -1
- package/core/built/admin/assets/{PolarAngleAxis-Dod0DwfL.js → PolarAngleAxis-DALH8FDm.js} +1 -1
- package/core/built/admin/assets/{_baseAssignValue-DnkbkowM.js → _baseAssignValue-D_UsvJRN.js} +1 -1
- package/core/built/admin/assets/{a-large-small-C5mgFBRg.js → a-large-small-DVyx4GMu.js} +1 -1
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
- package/core/built/admin/assets/admin-x-settings/{code-editor-view-BWi3-ftq.mjs → code-editor-view-DBrulgE8.mjs} +12 -12
- package/core/built/admin/assets/admin-x-settings/{index-B3PtvbUw.mjs → index-CsGHbqSU.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-Did70N9h.mjs → index-Cypgljb3.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-JaMlaX9G.mjs → index-DgGwb1L-.mjs} +1390 -1381
- package/core/built/admin/assets/admin-x-settings/{modals-Bwrf9ptQ.mjs → modals-rdo7i0D3.mjs} +9320 -8655
- package/core/built/admin/assets/{at-sign-Bz-SU-S_.js → at-sign-CGNkBrZS.js} +1 -1
- package/core/built/admin/assets/{audience-DmVdEmIe.js → audience-CJHVR7kD.js} +1 -1
- package/core/built/admin/assets/{avatar-flipboard-C7sxDVEM.js → avatar-flipboard-jqr9Aapd.js} +1 -1
- package/core/built/admin/assets/{bluesky-sharing-DJniSF6N.js → bluesky-sharing-C2749SVt.js} +1 -1
- package/core/built/admin/assets/{chart-mYz3IJwm.js → chart-BAQCVPCH.js} +1 -1
- package/core/built/admin/assets/{chunk.524.9d300778a63b42b0de62.js → chunk.524.428356d01feabbc7b932.js} +7 -7
- package/core/built/admin/assets/{chunk.582.2e363cd976d9bba998b9.js → chunk.582.b8b41ba720f49d724992.js} +9 -9
- package/core/built/admin/assets/{code-editor-view-BIQWFJ01.js → code-editor-view-YNhJ1w71.js} +1 -1
- package/core/built/admin/assets/{comments-B_-hdjc6.js → comments-CPd_iCc3.js} +1 -1
- package/core/built/admin/assets/{copy-Bpq3uhnh.js → copy-CNsHZEFR.js} +1 -1
- package/core/built/admin/assets/{data-list-BYNMbRIq.js → data-list-Dn5MkmyD.js} +1 -1
- package/core/built/admin/assets/{deleted-feed-item-B_AwUKZy.js → deleted-feed-item-Dmm_clMl.js} +1 -1
- package/core/built/admin/assets/{edit-profile-DZo2ZcOu.js → edit-profile-BLSdtROQ.js} +1 -1
- package/core/built/admin/assets/{empty-indicator-Bn5wG9-T.js → empty-indicator-C2Z0dddU.js} +1 -1
- package/core/built/admin/assets/{en-D4zIrMLN.js → en-DuTkaMAI.js} +1 -1
- package/core/built/admin/assets/{feed-Bgq4RarU.js → feed-B4Xp4C2J.js} +1 -1
- package/core/built/admin/assets/{filters-CpVwu1Gk.js → filters-gnOx5Z45.js} +1 -1
- package/core/built/admin/assets/{gh-chart-Br7NXn_b.js → gh-chart-B2aWYkGg.js} +1 -1
- package/core/built/admin/assets/{ghost-98a3c5fd6235ebd344594f491cb6d17b.js → ghost-29f8f3c80e41126ba5e3c52ad5727e8a.js} +176 -176
- package/core/built/admin/assets/ghost-transistor-BXCFhTPG.png +0 -0
- package/core/built/admin/assets/{growth-CiPDnWxJ.js → growth-BysawIWe.js} +1 -1
- package/core/built/admin/assets/{hash-Bc1e38oH.js → hash-j3sh-UI3.js} +1 -1
- package/core/built/admin/assets/{inbox-BcZNBNhk.js → inbox-1cUdr8Mm.js} +1 -1
- package/core/built/admin/assets/index-B2yksBz4.js +13 -0
- package/core/built/admin/assets/{index-CMTzNTew.js → index-B8G0f0hb.js} +3 -3
- package/core/built/admin/assets/{index-DU3bv9jz.js → index-BVoBTlp_.js} +1 -1
- package/core/built/admin/assets/{index-ETsoQLbU.js → index-BgrwCpgw.js} +1 -1
- package/core/built/admin/assets/{index-Bm5ZeLtt.js → index-BhY_SbGB.js} +1 -1
- package/core/built/admin/assets/index-CkAUXgAi.js +1 -0
- package/core/built/admin/assets/index-Cl_EPbQ2.js +1 -0
- package/core/built/admin/assets/{index-B_7QsC5T.js → index-D8CXbJm4.js} +1 -1
- package/core/built/admin/assets/index-DGBK5k9H.js +1 -0
- package/core/built/admin/assets/{index-D212VvOz.js → index-DKAlEWM4.js} +1 -1
- package/core/built/admin/assets/index-D_sGUCda.css +1 -0
- package/core/built/admin/assets/{index-BFEx_ouC.js → index-DarAAzVH.js} +1 -1
- package/core/built/admin/assets/{index-Brg4tecQ.js → index-Dz8eTtOq.js} +1 -1
- package/core/built/admin/assets/{index-Cn32t_gv.js → index-I9n711UW.js} +1 -1
- package/core/built/admin/assets/{index-CsidX7Si.js → index-WjY3mGep.js} +2 -2
- package/core/built/admin/assets/{index-Dv13wKfX.js → index-mhojl7aD.js} +1 -1
- package/core/built/admin/assets/{koenig-lexical-DZUWzN0P.js → koenig-lexical-coiS9dIC.js} +1 -1
- package/core/built/admin/assets/{kpi-card-DENsK2xK.js → kpi-card-BEFHo2gr.js} +1 -1
- package/core/built/admin/assets/{kpis-CDrs2iS1.js → kpis-D-gz-lUk.js} +1 -1
- package/core/built/admin/assets/{label-BznQtEEo.js → label-BvlHzzcJ.js} +1 -1
- package/core/built/admin/assets/{links-lpAC3T1p.js → links-tdJvBrsT.js} +1 -1
- package/core/built/admin/assets/list-filter-DPE4Xj2T.js +6 -0
- package/core/built/admin/assets/{lucide-react-CBigk-fq.js → lucide-react-ClQ3Iy7l.js} +1448 -1453
- package/core/built/admin/assets/{main-layout-_SYQRjIl.js → main-layout-DY96wK-_.js} +1 -1
- package/core/built/admin/assets/{message-square-text-cV8O_qKq.js → message-square-text-Dd1XS5-H.js} +1 -1
- package/core/built/admin/assets/{minus-NvnQTlW7.js → minus-CyEc3BE5.js} +1 -1
- package/core/built/admin/assets/modals-C06GcVIm.js +77 -0
- package/core/built/admin/assets/moderation-B0fdmlig.js +1 -0
- package/core/built/admin/assets/newsletter-Byg3ARa5.js +1 -0
- package/core/built/admin/assets/{newsletters-BexdXUhn.js → newsletters-DbH39vgA.js} +1 -1
- package/core/built/admin/assets/{note-DuaUGOeZ.js → note-Cwy-rIAF.js} +1 -1
- package/core/built/admin/assets/overview-CVMLGrVt.js +1 -0
- package/core/built/admin/assets/{pagemenu-CZyroidv.js → pagemenu-CkzsZrHC.js} +1 -1
- package/core/built/admin/assets/{post-analytics-DLK2SOSQ.js → post-analytics-aA-nG9Fq.js} +1 -1
- package/core/built/admin/assets/{post-analytics-context-CF7C67-0.js → post-analytics-context-BLkup0Xh.js} +1 -1
- package/core/built/admin/assets/{post-analytics-header-7GtJCx0W.js → post-analytics-header-D9srAPT5.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-D8R7DUZP.js → post-share-modal-CkvJtrRw.js} +1 -1
- package/core/built/admin/assets/{posts-CL9UDYoW.js → posts-DDkuYoN7.js} +1 -1
- package/core/built/admin/assets/{repeat-DgH39UKE.js → repeat-B-SL0yPM.js} +1 -1
- package/core/built/admin/assets/{reply-DAaNxiy8.js → reply-BdCPoUQ9.js} +1 -1
- package/core/built/admin/assets/{select-Cor2wFXT.js → select-C7aNW8QS.js} +1 -1
- package/core/built/admin/assets/{settings-xRx917Gj.js → settings-Bzy1GNfD.js} +1 -1
- package/core/built/admin/assets/{settings-BeumESEN.js → settings-D-dhma2e.js} +23 -23
- package/core/built/admin/assets/{sort-button-BNW3i4Lb.js → sort-button-Dv8vjh13.js} +1 -1
- package/core/built/admin/assets/{source-icon-DvDuzw73.js → source-icon-DTz4isLK.js} +1 -1
- package/core/built/admin/assets/{sprout-C3cc0c-K.js → sprout-BwLQTzMf.js} +1 -1
- package/core/built/admin/assets/{square-tZp0_n7e.js → square-YF1YE9ex.js} +1 -1
- package/core/built/admin/assets/{stats-2Jelnn-Q.js → stats-C5ad0fgQ.js} +1 -1
- package/core/built/admin/assets/{stats-view-CESy8ELH.js → stats-view-BvkxPYNX.js} +1 -1
- package/core/built/admin/assets/{step-1-DrqdolAh.js → step-1-D7_s-D99.js} +1 -1
- package/core/built/admin/assets/{step-2-DmEpKck5.js → step-2-B94Yf7FF.js} +1 -1
- package/core/built/admin/assets/{step-3-Bus-0o0n.js → step-3-DswXYYf4.js} +1 -1
- package/core/built/admin/assets/{table-BQUcKHfm.js → table-D30IXfUP.js} +1 -1
- package/core/built/admin/assets/{tabs-BmdL0X4U.js → tabs-CjNdfW0y.js} +1 -1
- package/core/built/admin/assets/{tags-EchqlZUJ.js → tags-B0ux9_dT.js} +1 -1
- package/core/built/admin/assets/{tags-CLxXZlOO.js → tags-PGeGAafJ.js} +1 -1
- package/core/built/admin/assets/{tiers-nCGyTly9.js → tiers-BaXK0JoI.js} +1 -1
- package/core/built/admin/assets/{toggle-group-CM5uf7J1.js → toggle-group-DdY8HF3Y.js} +1 -1
- package/core/built/admin/assets/{topic-filter-LTRvZ8aU.js → topic-filter-Boh22uGD.js} +1 -1
- package/core/built/admin/assets/{trash-u5BxolyH.js → trash-D7ZWrnDq.js} +1 -1
- package/core/built/admin/assets/{url-helpers-D41fEt51.js → url-helpers-mt6MBIi0.js} +1 -1
- package/core/built/admin/assets/{use-growth-stats-BJ0O9ewi.js → use-growth-stats-Bg0nE0WG.js} +1 -1
- package/core/built/admin/assets/{use-infinite-virtual-scroll-APZWciOk.js → use-infinite-virtual-scroll-DoeI5IY-.js} +1 -1
- package/core/built/admin/assets/{use-simple-pagination-DVRHeaAR.js → use-simple-pagination-BJzBULR3.js} +1 -1
- package/core/built/admin/assets/{user-round-check-B6j98D6d.js → user-round-check-BLc3L-ei.js} +1 -1
- package/core/built/admin/assets/{wallet-cards-KmOh29LP.js → wallet-cards-xX4QZik7.js} +1 -1
- package/core/built/admin/assets/web-DQ2qBymm.js +1 -0
- package/core/built/admin/index.html +5 -5
- package/core/server/lib/image/cached-image-size-from-url.js +37 -25
- package/core/server/lib/lexical.js +1 -0
- package/core/server/lib/mobiledoc.js +1 -0
- package/core/server/services/email-service/email-renderer.js +6 -2
- package/core/server/services/email-service/email-service-wrapper.js +2 -2
- package/core/server/services/koenig/node-renderers/call-to-action-renderer.js +2 -2
- package/core/server/services/koenig/node-renderers/gallery-renderer.js +3 -3
- package/core/server/services/koenig/node-renderers/image-renderer.js +3 -3
- package/core/server/services/koenig/render-utils/is-content-image.js +18 -0
- package/core/server/services/koenig/render-utils/srcset-attribute.js +4 -4
- package/core/server/services/members/members-api/controllers/router-controller.js +21 -36
- package/core/server/services/members/members-api/repositories/member-repository.js +8 -1
- package/core/server/services/offers/offer-bookshelf-repository.js +3 -3
- package/core/shared/config/defaults.json +1 -1
- package/core/shared/labs.js +2 -1
- package/package.json +5 -5
- package/components/tryghost-i18n-6.18.1.tgz +0 -0
- package/core/built/admin/assets/index-BBrewxpF.css +0 -1
- package/core/built/admin/assets/index-BpcL7RmI.js +0 -13
- package/core/built/admin/assets/index-CTfJflJ2.js +0 -1
- package/core/built/admin/assets/index-D67LJ_H4.js +0 -1
- package/core/built/admin/assets/index-ZYDRMgcT.js +0 -1
- package/core/built/admin/assets/modals-XRSkribf.js +0 -77
- package/core/built/admin/assets/moderation-BQp1GEWG.js +0 -1
- package/core/built/admin/assets/newsletter-foM6KNNV.js +0 -1
- package/core/built/admin/assets/overview-DgKBNqyc.js +0 -1
- package/core/built/admin/assets/web-Cclotbnz.js +0 -1
- package/core/server/services/koenig/render-utils/is-local-content-image.js +0 -9
|
@@ -0,0 +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-B8G0f0hb.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-C7aNW8QS.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-BLkup0Xh.js";import{a as $e,F as Ge,b as He}from"./message-square-text-Dd1XS5-H.js";import{g as We,a as Qe,S as Ye,b as qe}from"./kpis-D-gz-lUk.js";import{F as Xe,c as W,e as ce}from"./en-DuTkaMAI.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-CkzsZrHC.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-Dn5MkmyD.js";import{a as dt,U as mt}from"./wallet-cards-xX4QZik7.js";import{P as pt,a as ht}from"./post-analytics-header-D9srAPT5.js";import{F as gt,c as ft}from"./filters-gnOx5Z45.js";import{b as bt,c as xt,F as St,d as jt,S as _t,T as Ct}from"./lucide-react-ClQ3Iy7l.js";import{E as Nt}from"./empty-indicator-C2Z0dddU.js";import"./posts-DDkuYoN7.js";import"./source-icon-DTz4isLK.js";import"./source-utils-B1S3ZHA2.js";import"./gh-chart-B2aWYkGg.js";import"./chart-BAQCVPCH.js";import"./_baseAssignValue-D_UsvJRN.js";import"./tabs-CjNdfW0y.js";import"./index-DKAlEWM4.js";import"./post-share-modal-CkvJtrRw.js";import"./post-helpers-gInwAwEv.js";import"./trash-D7ZWrnDq.js";import"./sprout-BwLQTzMf.js";import"./a-large-small-DVyx4GMu.js";import"./at-sign-CGNkBrZS.js";import"./copy-CNsHZEFR.js";import"./hash-j3sh-UI3.js";import"./inbox-1cUdr8Mm.js";import"./list-filter-DPE4Xj2T.js";import"./minus-CyEc3BE5.js";import"./tags-PGeGAafJ.js";import"./square-YF1YE9ex.js";import"./user-round-check-BLc3L-ei.js";import"./repeat-B-SL0yPM.js";import"./reply-BdCPoUQ9.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 Ss=()=>{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{Ss 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-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%
|
|
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%22d795c01849%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,8 +17,8 @@
|
|
|
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-
|
|
21
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
20
|
+
<script type="module" crossorigin src="./assets/index-B8G0f0hb.js"></script>
|
|
21
|
+
<link rel="stylesheet" crossorigin href="./assets/index-D_sGUCda.css">
|
|
22
22
|
</head>
|
|
23
23
|
<body class="react-admin">
|
|
24
24
|
<div id="ember-alerts-wormhole"></div>
|
|
@@ -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.
|
|
42
|
-
<script src="./assets/ghost-
|
|
41
|
+
<script src="./assets/chunk.524.428356d01feabbc7b932.js"></script>
|
|
42
|
+
<script src="./assets/ghost-29f8f3c80e41126ba5e3c52ad5727e8a.js"></script>
|
|
43
43
|
</body>
|
|
44
44
|
</html>
|
|
@@ -14,6 +14,7 @@ const logging = require('@tryghost/logging');
|
|
|
14
14
|
* @property {string} url image url
|
|
15
15
|
* @property {number} height image height
|
|
16
16
|
* @property {number} width image width
|
|
17
|
+
* @property {boolean} notFound true if the image is not found
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
20
|
class CachedImageSizeFromUrl {
|
|
@@ -30,46 +31,57 @@ class CachedImageSizeFromUrl {
|
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Get cached image size from URL
|
|
33
|
-
*
|
|
34
|
+
* Returns null when dimensions are unavailable (invalid URL, 404, transient
|
|
35
|
+
* errors) so consumers can gracefully skip images with missing dimensions.
|
|
36
|
+
*
|
|
37
|
+
* Caching strategy:
|
|
38
|
+
* - Successful fetches are cached
|
|
39
|
+
* - NotFoundError (404) is cached permanently with a marker
|
|
40
|
+
* - Transient errors (timeouts, 500s) are NOT cached, allowing retry on next call
|
|
41
|
+
* - Stale error entries (cached without dimensions) trigger a retry
|
|
42
|
+
*
|
|
34
43
|
* @param {string} url
|
|
35
44
|
* @returns {Promise<ImageSizeCache>}
|
|
36
|
-
* @description Takes a url and returns image width and height from cache if available.
|
|
37
|
-
* If not in cache, `getImageSizeFromUrl` is called and returns the dimensions in a Promise.
|
|
38
45
|
*/
|
|
39
46
|
async getCachedImageSizeFromUrl(url) {
|
|
40
47
|
if (!url || url === undefined || url === null) {
|
|
41
|
-
return;
|
|
48
|
+
return null;
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
const cachedImageSize = await this.cache.get(url);
|
|
45
52
|
|
|
46
|
-
|
|
53
|
+
// Check for cachedImageSize.width to handle legacy cache entries
|
|
54
|
+
// that were stored as {url} without dimensions or a notFound marker.
|
|
55
|
+
// These stale entries fall through to trigger a re-fetch and self-heal.
|
|
56
|
+
if (cachedImageSize && cachedImageSize.width) {
|
|
47
57
|
debug('Read image from cache:', url);
|
|
58
|
+
return {...cachedImageSize};
|
|
59
|
+
}
|
|
48
60
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
debug('Cached image:', url);
|
|
61
|
+
// 404s are cached permanently — don't retry
|
|
62
|
+
if (cachedImageSize && cachedImageSize.notFound) {
|
|
63
|
+
debug('Read image from cache (not found):', url);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
56
66
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
debug('Cached image (not found):', url);
|
|
61
|
-
} else {
|
|
62
|
-
debug('Cached image (error):', url);
|
|
63
|
-
logging.error(err);
|
|
64
|
-
}
|
|
67
|
+
try {
|
|
68
|
+
const res = await this.getImageSizeFromUrl(url);
|
|
69
|
+
await this.cache.set(url, {...res});
|
|
65
70
|
|
|
66
|
-
|
|
67
|
-
await this.cache.set(url, {
|
|
68
|
-
url
|
|
69
|
-
});
|
|
71
|
+
debug('Cached image:', url);
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
return res;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (err instanceof errors.NotFoundError) {
|
|
76
|
+
debug('Cached image (not found):', url);
|
|
77
|
+
// Cache 404s with a marker
|
|
78
|
+
await this.cache.set(url, {url, notFound: true});
|
|
79
|
+
} else {
|
|
80
|
+
debug('Image fetch error (not cached):', url);
|
|
81
|
+
logging.error(err);
|
|
72
82
|
}
|
|
83
|
+
|
|
84
|
+
return null;
|
|
73
85
|
}
|
|
74
86
|
}
|
|
75
87
|
}
|
|
@@ -81,6 +81,7 @@ module.exports = {
|
|
|
81
81
|
|
|
82
82
|
const options = Object.assign({
|
|
83
83
|
siteUrl: config.get('url'),
|
|
84
|
+
imageBaseUrl: config.get('urls:image') || '',
|
|
84
85
|
imageOptimization: config.get('imageOptimization'),
|
|
85
86
|
canTransformImage(storagePath) {
|
|
86
87
|
const imageTransform = require('@tryghost/image-transform');
|
|
@@ -31,6 +31,7 @@ module.exports = {
|
|
|
31
31
|
|
|
32
32
|
cardFactory = new CardFactory({
|
|
33
33
|
siteUrl: config.get('url'),
|
|
34
|
+
imageBaseUrl: config.get('urls:image') || '',
|
|
34
35
|
imageOptimization: config.get('imageOptimization'),
|
|
35
36
|
canTransformImage(storagePath) {
|
|
36
37
|
const imageTransform = require('@tryghost/image-transform');
|
|
@@ -148,7 +148,7 @@ class EmailRenderer {
|
|
|
148
148
|
* @param {object} dependencies.renderers
|
|
149
149
|
* @param {{render(object, options): string}} dependencies.renderers.lexical
|
|
150
150
|
* @param {{render(object, options): string}} dependencies.renderers.mobiledoc
|
|
151
|
-
* @param {{
|
|
151
|
+
* @param {{getCachedImageSizeFromUrl(url: string): Promise<{url: string, width: number, height: number} | null>}} dependencies.imageSize
|
|
152
152
|
* @param {{urlFor(type: string, optionsOrAbsolute, absolute): string, isSiteUrl(url, context): boolean}} dependencies.urlUtils
|
|
153
153
|
* @param {{isLocalImage(url: string): boolean}} dependencies.storageUtils
|
|
154
154
|
* @param {(post: Post) => string} dependencies.getPostUrl
|
|
@@ -1402,7 +1402,11 @@ class EmailRenderer {
|
|
|
1402
1402
|
};
|
|
1403
1403
|
} else {
|
|
1404
1404
|
try {
|
|
1405
|
-
const size = await this.#imageSize.
|
|
1405
|
+
const size = await this.#imageSize.getCachedImageSizeFromUrl(href);
|
|
1406
|
+
|
|
1407
|
+
if (!size || !size.width) {
|
|
1408
|
+
return {href, width: 0, height: null};
|
|
1409
|
+
}
|
|
1406
1410
|
|
|
1407
1411
|
if (size.width >= visibleWidth) {
|
|
1408
1412
|
if (!visibleHeight) {
|
|
@@ -47,7 +47,7 @@ class EmailServiceWrapper {
|
|
|
47
47
|
const audienceFeedback = require('../audience-feedback');
|
|
48
48
|
const storageUtils = require('../../adapters/storage/utils');
|
|
49
49
|
const emailAnalyticsJobs = require('../email-analytics/jobs');
|
|
50
|
-
const {
|
|
50
|
+
const {cachedImageSizeFromUrl} = require('../../lib/image');
|
|
51
51
|
|
|
52
52
|
// capture errors from mailgun client and log them in sentry
|
|
53
53
|
const errorHandler = (error) => {
|
|
@@ -79,7 +79,7 @@ class EmailServiceWrapper {
|
|
|
79
79
|
mobiledoc: mobiledocLib,
|
|
80
80
|
lexical: lexicalLib
|
|
81
81
|
},
|
|
82
|
-
imageSize,
|
|
82
|
+
imageSize: cachedImageSizeFromUrl,
|
|
83
83
|
urlUtils,
|
|
84
84
|
storageUtils,
|
|
85
85
|
getPostUrl: this.getPostUrl,
|
|
@@ -2,7 +2,7 @@ const {renderEmailButton} = require('../render-partials/email-button');
|
|
|
2
2
|
const {addCreateDocumentOption} = require('../render-utils/add-create-document-option');
|
|
3
3
|
const {renderWithVisibility} = require('../render-utils/visibility');
|
|
4
4
|
const {getResizedImageDimensions} = require('../render-utils/get-resized-image-dimensions');
|
|
5
|
-
const {
|
|
5
|
+
const {isContentImage} = require('../render-utils/is-content-image');
|
|
6
6
|
const {buildCleanBasicHtmlForElement} = require('../render-utils/build-clean-basic-html-for-element');
|
|
7
7
|
|
|
8
8
|
const showButton = dataset => dataset.showButton && dataset.buttonUrl && dataset.buttonText;
|
|
@@ -74,7 +74,7 @@ function emailCTATemplate(dataset, options = {}) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
if (dataset.layout === 'minimal' && dataset.imageUrl) {
|
|
77
|
-
if (
|
|
77
|
+
if (isContentImage(dataset.imageUrl, options.siteUrl, options.imageBaseUrl) && options.canTransformImage?.(dataset.imageUrl)) {
|
|
78
78
|
const [, imagesPath, filename] = dataset.imageUrl.match(/(.*\/content\/images)\/(.*)/);
|
|
79
79
|
const iconSize = options?.imageOptimization?.internalImageSizes?.['email-cta-minimal-image'] || {width: 256, height: 256}; // default to 256 since we know the image is a square
|
|
80
80
|
dataset.imageUrl = `${imagesPath}/size/w${iconSize.width}h${iconSize.height}/${filename}`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const {addCreateDocumentOption} = require('../render-utils/add-create-document-option');
|
|
2
2
|
const {getAvailableImageWidths} = require('../render-utils/get-available-image-widths');
|
|
3
|
-
const {
|
|
3
|
+
const {isContentImage} = require('../render-utils/is-content-image');
|
|
4
4
|
const {isUnsplashImage} = require('../render-utils/is-unsplash-image');
|
|
5
5
|
const {getResizedImageDimensions} = require('../render-utils/get-resized-image-dimensions');
|
|
6
6
|
const {setSrcsetAttribute} = require('../render-utils/srcset-attribute');
|
|
@@ -79,7 +79,7 @@ function renderGalleryNode(node, options = {}) {
|
|
|
79
79
|
if (
|
|
80
80
|
defaultMaxWidth &&
|
|
81
81
|
image.width > defaultMaxWidth &&
|
|
82
|
-
|
|
82
|
+
isContentImage(image.src, options.siteUrl, options.imageBaseUrl) &&
|
|
83
83
|
canTransformImage &&
|
|
84
84
|
canTransformImage(image.src)
|
|
85
85
|
) {
|
|
@@ -112,7 +112,7 @@ function renderGalleryNode(node, options = {}) {
|
|
|
112
112
|
img.setAttribute('height', newImageDimensions.height);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
if (
|
|
115
|
+
if (isContentImage(image.src, options.siteUrl, options.imageBaseUrl) && options.canTransformImage && options.canTransformImage(image.src)) {
|
|
116
116
|
// find available image size next up from 2x600 so we can use it for the "retina" src
|
|
117
117
|
const availableImageWidths = getAvailableImageWidths(image, options.imageOptimization.contentImageSizes);
|
|
118
118
|
const srcWidth = availableImageWidths.find(width => width >= 1200);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const {getAvailableImageWidths} = require('../render-utils/get-available-image-widths');
|
|
2
|
-
const {
|
|
2
|
+
const {isContentImage} = require('../render-utils/is-content-image');
|
|
3
3
|
const {setSrcsetAttribute} = require('../render-utils/srcset-attribute');
|
|
4
4
|
const {getResizedImageDimensions} = require('../render-utils/get-resized-image-dimensions');
|
|
5
5
|
const {addCreateDocumentOption} = require('../render-utils/add-create-document-option');
|
|
@@ -49,7 +49,7 @@ function renderImageNode(node, options = {}) {
|
|
|
49
49
|
if (
|
|
50
50
|
defaultMaxWidth &&
|
|
51
51
|
node.width > defaultMaxWidth &&
|
|
52
|
-
|
|
52
|
+
isContentImage(node.src, options.siteUrl, options.imageBaseUrl) &&
|
|
53
53
|
canTransformImage &&
|
|
54
54
|
canTransformImage(node.src)
|
|
55
55
|
) {
|
|
@@ -96,7 +96,7 @@ function renderImageNode(node, options = {}) {
|
|
|
96
96
|
img.setAttribute('width', imageDimensions.width);
|
|
97
97
|
img.setAttribute('height', imageDimensions.height);
|
|
98
98
|
|
|
99
|
-
if (
|
|
99
|
+
if (isContentImage(node.src, options.siteUrl, options.imageBaseUrl) && options.canTransformImage?.(node.src)) {
|
|
100
100
|
// find available image size next up from 2x600 so we can use it for the "retina" src
|
|
101
101
|
const availableImageWidths = getAvailableImageWidths(node, options.imageOptimization.contentImageSizes);
|
|
102
102
|
const srcWidth = availableImageWidths.find(width => width >= 1200);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const matchesContentImagePath = function (url, baseUrl = '', pattern = /^\/?content\/images\//) {
|
|
2
|
+
const normalized = baseUrl.replace(/\/$/, '');
|
|
3
|
+
const path = url.replace(normalized, '');
|
|
4
|
+
return pattern.test(path);
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const isLocalContentImage = function (url, siteUrl = '') {
|
|
8
|
+
return matchesContentImagePath(url, siteUrl, /^(\/.*|__GHOST_URL__)\/?content\/images\//);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const isContentImage = function (url, siteUrl = '', imageBaseUrl = '') {
|
|
12
|
+
return isLocalContentImage(url, siteUrl) || Boolean(imageBaseUrl && matchesContentImagePath(url, imageBaseUrl));
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
isLocalContentImage,
|
|
17
|
+
isContentImage
|
|
18
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {isContentImage} = require('./is-content-image');
|
|
2
2
|
const {getAvailableImageWidths} = require('./get-available-image-widths');
|
|
3
3
|
const {isUnsplashImage} = require('./is-unsplash-image');
|
|
4
4
|
|
|
@@ -9,14 +9,14 @@ const getSrcsetAttribute = function ({src, width, options}) {
|
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
if (
|
|
12
|
+
if (isContentImage(src, options.siteUrl, options.imageBaseUrl) && options.canTransformImage && !options.canTransformImage(src)) {
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const srcsetWidths = getAvailableImageWidths({width}, options.imageOptimization.contentImageSizes);
|
|
17
17
|
|
|
18
|
-
// apply srcset if this is a
|
|
19
|
-
if (
|
|
18
|
+
// apply srcset if this is a local or CDN image that matches Ghost's image url structure
|
|
19
|
+
if (isContentImage(src, options.siteUrl, options.imageBaseUrl)) {
|
|
20
20
|
const [, imagesPath, filename] = src.match(/(.*\/content\/images)\/(.*)/);
|
|
21
21
|
const srcs = [];
|
|
22
22
|
|
|
@@ -24,8 +24,8 @@ const messages = {
|
|
|
24
24
|
unableToCheckout: 'Unable to initiate checkout session',
|
|
25
25
|
inviteOnly: 'This site is invite-only, contact the owner for access.',
|
|
26
26
|
paidOnly: 'This site only accepts paid members.',
|
|
27
|
-
memberNotFound: 'No member exists with this
|
|
28
|
-
memberNotFoundSignUp: 'No member exists with this
|
|
27
|
+
memberNotFound: 'No member exists with this e-mail address.',
|
|
28
|
+
memberNotFoundSignUp: 'No member exists with this e-mail address. Please sign up first.',
|
|
29
29
|
invalidType: 'Invalid checkout type.',
|
|
30
30
|
notConfigured: 'This site is not accepting payments at the moment.',
|
|
31
31
|
invalidNewsletters: 'Cannot subscribe to invalid newsletters {newsletters}',
|
|
@@ -886,33 +886,9 @@ module.exports = class RouterController {
|
|
|
886
886
|
const member = await this._memberRepository.get({email: normalizedEmail});
|
|
887
887
|
|
|
888
888
|
if (!member) {
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
if (this._allowSelfSignup()) {
|
|
893
|
-
const blockedEmailDomains = this._settingsCache.get('all_blocked_email_domains');
|
|
894
|
-
const emailDomain = normalizedEmail.split('@')[1]?.toLowerCase();
|
|
895
|
-
if (emailDomain && blockedEmailDomains.includes(emailDomain)) {
|
|
896
|
-
// To prevent enumeration, we don't reveal this
|
|
897
|
-
return {};
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
const tokenData = {
|
|
901
|
-
reqIp: req.ip ?? undefined,
|
|
902
|
-
attribution: await this._memberAttributionService.getAttribution(req.body.urlHistory)
|
|
903
|
-
};
|
|
904
|
-
// Send a signup email - this allows them to create an account
|
|
905
|
-
return await this._sendEmailWithMagicLink({
|
|
906
|
-
email: normalizedEmail,
|
|
907
|
-
tokenData,
|
|
908
|
-
requestedType: 'signup',
|
|
909
|
-
referrer
|
|
910
|
-
});
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
// Self-signup disabled (invite-only): silently return empty response
|
|
914
|
-
// to prevent member enumeration
|
|
915
|
-
return {};
|
|
889
|
+
throw new errors.BadRequestError({
|
|
890
|
+
message: this._allowSelfSignup() ? tpl(messages.memberNotFoundSignUp) : tpl(messages.memberNotFound)
|
|
891
|
+
});
|
|
916
892
|
}
|
|
917
893
|
|
|
918
894
|
const tokenData = {};
|
|
@@ -980,6 +956,10 @@ module.exports = class RouterController {
|
|
|
980
956
|
return res.end(JSON.stringify({offers}));
|
|
981
957
|
}
|
|
982
958
|
|
|
959
|
+
function sendNoOffersAvailable() {
|
|
960
|
+
return sendOffersResponse([]);
|
|
961
|
+
}
|
|
962
|
+
|
|
983
963
|
if (!identity) {
|
|
984
964
|
res.writeHead(401);
|
|
985
965
|
return res.end('Unauthorized');
|
|
@@ -1029,45 +1009,50 @@ module.exports = class RouterController {
|
|
|
1029
1009
|
|
|
1030
1010
|
// No active subscription - return empty offers
|
|
1031
1011
|
if (activeSubscriptions.length === 0) {
|
|
1032
|
-
return
|
|
1012
|
+
return sendNoOffersAvailable();
|
|
1033
1013
|
}
|
|
1034
1014
|
|
|
1035
1015
|
// Multiple active subscriptions - edge case, return empty offers to avoid ambiguity
|
|
1036
1016
|
if (activeSubscriptions.length > 1) {
|
|
1037
|
-
return
|
|
1017
|
+
return sendNoOffersAvailable();
|
|
1038
1018
|
}
|
|
1039
1019
|
|
|
1040
1020
|
const activeSubscription = activeSubscriptions[0];
|
|
1041
1021
|
|
|
1022
|
+
// If subscription is already set to cancel, don't show retention offers
|
|
1023
|
+
if (activeSubscription.get('cancel_at_period_end')) {
|
|
1024
|
+
return sendNoOffersAvailable();
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1042
1027
|
// If subscription already has an offer applied (e.g. signup offer), don't show retention offers
|
|
1043
1028
|
if (activeSubscription.get('offer_id')) {
|
|
1044
|
-
return
|
|
1029
|
+
return sendNoOffersAvailable();
|
|
1045
1030
|
}
|
|
1046
1031
|
|
|
1047
1032
|
// If subscription is in a trial period (either offer-based or tier-based), don't show retention offers
|
|
1048
1033
|
const trialEndAt = activeSubscription.get('trial_end_at');
|
|
1049
1034
|
if (trialEndAt && trialEndAt > new Date()) {
|
|
1050
|
-
return
|
|
1035
|
+
return sendNoOffersAvailable();
|
|
1051
1036
|
}
|
|
1052
1037
|
|
|
1053
1038
|
// Get tier and cadence from the subscription
|
|
1054
1039
|
const stripePrice = activeSubscription.related('stripePrice');
|
|
1055
1040
|
if (!stripePrice || !stripePrice.id) {
|
|
1056
|
-
return
|
|
1041
|
+
return sendNoOffersAvailable();
|
|
1057
1042
|
}
|
|
1058
1043
|
|
|
1059
1044
|
const stripeProduct = stripePrice.related('stripeProduct');
|
|
1060
1045
|
|
|
1061
1046
|
// If the stripe product is not found, return empty offers
|
|
1062
1047
|
if (!stripeProduct || !stripeProduct.id) {
|
|
1063
|
-
return
|
|
1048
|
+
return sendNoOffersAvailable();
|
|
1064
1049
|
}
|
|
1065
1050
|
|
|
1066
1051
|
const product = stripeProduct.related('product');
|
|
1067
1052
|
|
|
1068
1053
|
// If the product is not found, return empty offers
|
|
1069
1054
|
if (!product || !product.id) {
|
|
1070
|
-
return
|
|
1055
|
+
return sendNoOffersAvailable();
|
|
1071
1056
|
}
|
|
1072
1057
|
|
|
1073
1058
|
const tierId = product.id;
|
|
@@ -31,7 +31,8 @@ const messages = {
|
|
|
31
31
|
offerAlreadyRedeemed: 'This offer has already been redeemed on this subscription',
|
|
32
32
|
subscriptionNotActive: 'Cannot apply offer to an inactive subscription',
|
|
33
33
|
subscriptionHasOffer: 'Subscription already has an offer applied',
|
|
34
|
-
subscriptionInTrial: 'Cannot apply offer to a subscription in a trial period'
|
|
34
|
+
subscriptionInTrial: 'Cannot apply offer to a subscription in a trial period',
|
|
35
|
+
subscriptionCancelling: 'Cannot apply retention offer to a subscription that is already cancelling'
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
const SUBSCRIPTION_STATUS_TRIALING = 'trialing';
|
|
@@ -1769,6 +1770,12 @@ module.exports = class MemberRepository {
|
|
|
1769
1770
|
});
|
|
1770
1771
|
}
|
|
1771
1772
|
|
|
1773
|
+
if (offer.redemption_type === 'retention' && subscriptionModel.get('cancel_at_period_end')) {
|
|
1774
|
+
throw new errors.BadRequestError({
|
|
1775
|
+
message: tpl(messages.subscriptionCancelling)
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1772
1779
|
if (offer.tier && offer.tier.id !== tierId) {
|
|
1773
1780
|
throw new errors.BadRequestError({
|
|
1774
1781
|
message: tpl(messages.offerTierMismatch)
|
|
@@ -19,8 +19,8 @@ const statusTransformer = mapKeyValues({
|
|
|
19
19
|
}]
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
const
|
|
23
|
-
if (key !== 'status') {
|
|
22
|
+
const rejectInvalidTransformer = input => mapQuery(input, function (value, key) {
|
|
23
|
+
if (key !== 'status' && key !== 'redemption_type') {
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -29,7 +29,7 @@ const rejectNonStatusTransformer = input => mapQuery(input, function (value, key
|
|
|
29
29
|
};
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
const mongoTransformer = flowRight(statusTransformer,
|
|
32
|
+
const mongoTransformer = flowRight(statusTransformer, rejectInvalidTransformer);
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* @typedef {object} BaseOptions
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
},
|
|
241
241
|
"portal": {
|
|
242
242
|
"url": "https://cdn.jsdelivr.net/ghost/portal@~{version}/umd/portal.min.js",
|
|
243
|
-
"version": "2.
|
|
243
|
+
"version": "2.63"
|
|
244
244
|
},
|
|
245
245
|
"sodoSearch": {
|
|
246
246
|
"url": "https://cdn.jsdelivr.net/ghost/sodo-search@~{version}/umd/sodo-search.min.js",
|
package/core/shared/labs.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ghost",
|
|
3
|
-
"version": "6.18.
|
|
3
|
+
"version": "6.18.2",
|
|
4
4
|
"description": "The professional publishing platform",
|
|
5
5
|
"author": "Ghost Foundation",
|
|
6
6
|
"homepage": "https://ghost.org",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"@tryghost/helpers": "1.1.97",
|
|
84
84
|
"@tryghost/html-to-plaintext": "1.0.4",
|
|
85
85
|
"@tryghost/http-cache-utils": "0.1.20",
|
|
86
|
-
"@tryghost/i18n": "file:components/tryghost-i18n-6.18.
|
|
86
|
+
"@tryghost/i18n": "file:components/tryghost-i18n-6.18.2.tgz",
|
|
87
87
|
"@tryghost/image-transform": "1.4.6",
|
|
88
88
|
"@tryghost/job-manager": "1.0.3",
|
|
89
89
|
"@tryghost/kg-card-factory": "5.1.7",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"@tryghost/mw-vhost": "1.0.1",
|
|
106
106
|
"@tryghost/nodemailer": "0.3.48",
|
|
107
107
|
"@tryghost/nql": "0.12.8",
|
|
108
|
-
"@tryghost/parse-email-address": "file:components/tryghost-parse-email-address-6.18.
|
|
108
|
+
"@tryghost/parse-email-address": "file:components/tryghost-parse-email-address-6.18.2.tgz",
|
|
109
109
|
"@tryghost/pretty-cli": "1.2.47",
|
|
110
110
|
"@tryghost/prometheus-metrics": "1.0.2",
|
|
111
111
|
"@tryghost/promise": "0.3.15",
|
|
@@ -271,8 +271,8 @@
|
|
|
271
271
|
"jackspeak": "2.3.6",
|
|
272
272
|
"moment": "2.24.0",
|
|
273
273
|
"moment-timezone": "0.5.45",
|
|
274
|
-
"@tryghost/i18n": "file:components/tryghost-i18n-6.18.
|
|
275
|
-
"@tryghost/parse-email-address": "file:components/tryghost-parse-email-address-6.18.
|
|
274
|
+
"@tryghost/i18n": "file:components/tryghost-i18n-6.18.2.tgz",
|
|
275
|
+
"@tryghost/parse-email-address": "file:components/tryghost-parse-email-address-6.18.2.tgz"
|
|
276
276
|
},
|
|
277
277
|
"nx": {
|
|
278
278
|
"targets": {
|
|
Binary file
|