domma-cms 1.6.33 → 1.6.35
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/admin/js/api.js +1 -1
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +1 -1
- package/admin/js/lib/pages-arrange.js +1 -1
- package/admin/js/lib/scribe-composer.js +1 -1
- package/admin/js/lib/scribe-model.js +1 -1
- package/admin/js/lib/shortcode-preview.js +1 -1
- package/admin/js/lib/shortcode-registry.js +3 -3
- package/admin/js/templates/docs/usage-site-settings.html +2 -1
- package/admin/js/templates/page-editor.html +2 -1
- package/admin/js/templates/pages.html +1 -1
- package/admin/js/templates/theme.html +1 -1
- package/admin/js/views/pages.js +3 -3
- package/admin/js/views/theme.js +5 -5
- package/package.json +1 -1
- package/plugins/_lib/outreach/audience.js +76 -0
- package/plugins/_lib/outreach/collections/outreach-contacts.schema.json +12 -0
- package/plugins/_lib/outreach/collections/outreach-groups.schema.json +10 -0
- package/plugins/_lib/outreach/collections/outreach-unsubscribes.schema.json +11 -0
- package/plugins/_lib/outreach/expiry.js +46 -0
- package/plugins/_lib/outreach/index.js +15 -0
- package/plugins/_lib/outreach/ledger.js +127 -0
- package/plugins/_lib/outreach/sending.js +121 -0
- package/plugins/_lib/outreach/suppression.js +62 -0
- package/plugins/_lib/outreach/tokens.js +35 -0
- package/plugins/_lib/outreach/unsubscribe.js +46 -0
- package/plugins/free-tier.lock.json +35 -24
- package/plugins/shopping-cart/CLAUDE.md +64 -0
- package/plugins/shopping-cart/admin/css/index.css +16 -0
- package/plugins/shopping-cart/admin/lib/chart.js +64 -0
- package/plugins/shopping-cart/admin/lib/kit.js +15 -0
- package/plugins/shopping-cart/admin/views/orders.js +3 -2
- package/plugins/shopping-cart/admin/views/overview.js +4 -53
- package/plugins/shopping-cart/admin/views/sales.js +133 -0
- package/plugins/shopping-cart/admin/views/settings.js +3 -1
- package/plugins/shopping-cart/admin/views/shop.js +2 -1
- package/plugins/shopping-cart/config.js +1 -0
- package/plugins/shopping-cart/docs/guide.md +25 -0
- package/plugins/shopping-cart/inject/body.html +2 -2
- package/plugins/shopping-cart/lib/accounts.js +180 -0
- package/plugins/shopping-cart/lib/commission.js +90 -0
- package/plugins/shopping-cart/lib/extensions.js +12 -4
- package/plugins/shopping-cart/lib/pricing.js +65 -13
- package/plugins/shopping-cart/lib/render.js +17 -4
- package/plugins/shopping-cart/lib/sales.js +193 -0
- package/plugins/shopping-cart/lib/shop.js +24 -3
- package/plugins/shopping-cart/lib/summary.js +4 -2
- package/plugins/shopping-cart/plugin.js +156 -22
- package/plugins/shopping-cart/plugin.json +2 -2
- package/plugins/shopping-cart/plugin.public.js +17 -1
- package/plugins/shopping-cart/public/account.js +277 -0
- package/plugins/shopping-cart/public/cart.js +16 -0
- package/plugins/shopping-cart/public/lib/session.js +82 -0
- package/plugins/shopping-cart/public/shop.css +27 -0
- package/plugins/shopping-cart/roles/shop-customer.json +8 -0
- package/plugins/shopping-cart/tests/accounts.test.js +127 -0
- package/plugins/shopping-cart/tests/api.test.js +63 -0
- package/plugins/shopping-cart/tests/commission.test.js +90 -0
- package/plugins/shopping-cart/tests/pure.test.js +30 -1
- package/plugins/shopping-cart/tests/sales.test.js +73 -0
- package/public/css/site.css +1 -1
- package/public/js/effects.js +1 -1
- package/public/js/scribe-style.mjs +1 -1
- package/public/js/shortcode-expiry.mjs +1 -0
- package/public/js/site.js +1 -1
- package/public/js/text-style.mjs +1 -1
- package/server/routes/api/theme.js +12 -2
- package/server/routes/outreach-public.js +68 -0
- package/server/routes/public.js +24 -1
- package/server/server.js +5 -0
- package/server/services/content.js +7 -7
- package/server/services/markdown.js +15 -5
- package/server/services/outreachUnsubscribe.js +54 -0
- package/server/services/shortcodeExpiry.js +146 -0
- package/server/templates/page.html +3 -3
package/public/js/site.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{decodeBase64Json as P}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as Y,applyColour as J,applyPill as te,applyFloat as ne,applyHighlight as R,applyItemHighlight as oe,itemHighlightAttrs as ae,isCurrentItem as X,menuAlign as se,applyClasses as O,cssClasses as W}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{initFloatingMenus as ie}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as U,markInvalidFields as G,showValidationPopup as K}from"/public/js/forms.js?v=20261009-form-classes";import{buildWizardSteps as re}from"/public/js/form-steps.mjs";import{applyFormClasses as Q,applySubmitButtons as ce,applyWizardButtons as le}from"/public/js/form-submit.mjs";document.addEventListener("click",p=>{const l=p.target?.closest?.(".dm-banner__dismiss");l&&(p.preventDefault(),l.closest(".dm-banner")?.remove())}),$(()=>{const p=window.__CMS_NAV__||{},l=window.__CMS_SITE__||{};if(l.autoTheme?.enabled){let n=function(e){const a=(e||"07:00").split(":");return+a[0]*60+(+a[1]||0)},s=function(){const e=new Date,a=e.getHours()*60+e.getMinutes();return a>=n(o.dayStart)&&a<n(o.nightStart)?o.dayTheme:o.nightTheme},t=function(e){const a=o.apply&&o.apply[e]||{theme:e,custom:""};Domma.theme.set(a.theme);for(const f of[...document.body.classList])f.startsWith("dm-custom-")&&document.body.classList.remove(f);a.custom&&document.body.classList.add(a.custom)};const o=l.autoTheme;t(s()),setInterval(()=>t(s()),6e4)}const x=$("#site-navbar");if(x.length&&p.brand){let s=function(d,m=0){return Array.isArray(d)?d.filter(u=>!(u&&(u.type==="spacer"||u.type==="theme-switcher"))).filter(u=>!(u&&u.type==="separator"&&m===0)).map(u=>u&&u.type==="separator"?{divider:!0}:Array.isArray(u?.items)?{...u,items:s(u.items,m+1)}:u):d},t=function(d){return Array.isArray(d)?d.filter(m=>!(m&&(m.type==="separator"||m.type==="spacer"||m.type==="theme-switcher"))):[]};const o={...p.brand},n=o.size&&o.size!=="md"?` navbar-brand-${o.size}`:"";if(o.logo||o.icon||o.tagline){let d="";o.logo?d+=`<img src="${o.logo}" class="navbar-brand-logo" alt="${o.text||""}">`:o.icon&&(d+=`<span data-icon="${o.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),o.text&&(d+=`<span class="navbar-brand-text${n}">${o.text}</span>`),o.tagline&&(d+=`<small class="navbar-brand-tagline">${o.tagline}</small>`),o.html=d}else n&&o.text&&(o.html=`<span class="navbar-brand-text${n}">${o.text}</span>`);const e=p.position==="floating",a=p.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:o,items:s(p.items||[]),variant:p.variant||"dark",position:e?"static":p.position||"sticky",collapsible:!0,appearOnHover:!!p.appearOnHover}),Domma.icons.scan("#site-navbar");const f=x.get(0);f.classList.add(`dm-nav-align-${se(p)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",p.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const v=p.dock||{};a&&!e&&de(f,p.side==="right"),!a&&v.hideOnScroll&&me(f),R(f,p),O(f,p.class),e&&(ne(x.get(0),p),ie(x.get(0).parentNode||document));const i=f.querySelector(".navbar-toggle"),c=f.querySelector(".navbar-collapse");if(i&&c){let d=null;const m=()=>{c.classList.contains("show")&&i.click()},u=()=>{d&&(d.remove(),d=null),document.body.classList.remove("dm-drawer-open")},g=()=>{d||(d=document.createElement("div"),d.className="dm-navbar-backdrop",d.addEventListener("click",m),document.body.appendChild(d),document.body.classList.add("dm-drawer-open"))};i.addEventListener("click",()=>{requestAnimationFrame(()=>{c.classList.contains("show")?g():u()})}),c.addEventListener("click",C=>{C.target.closest("a")&&requestAnimationFrame(u)}),document.addEventListener("keydown",C=>{C.key==="Escape"&&c.classList.contains("show")&&m()})}const h=f.querySelector(".navbar-nav");if(h){const d=Array.from(h.children);let m=0;for(const u of p.items||[])if(!(u&&u.type==="separator")){if(u&&u.type==="spacer"){const g=document.createElement("li"),C=["xs","sm","md","lg","xl","flex"].includes(u.size)?u.size:"md";g.className=`dm-menu-spacer dm-menu-spacer--${C}`,g.setAttribute("aria-hidden","true"),h.insertBefore(g,d[m]||null);continue}if(u&&u.type==="theme-switcher"){const g=document.createElement("li");g.className="dm-menu-theme-switcher";const C=document.createElement("span");C.dataset.themeSwitcher="menu",C.dataset.position="inline",g.appendChild(C),h.insertBefore(g,d[m]||null);continue}m++}window.CMSThemeSwitcher?.scan(h)}const b=t(p.items||[]);f.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(d=>{const m=b[parseInt(d.dataset.index,10)];if(!m)return;const u=d.dataset.subindex!=null,g=u&&Array.isArray(m.items)?m.items[parseInt(d.dataset.subindex,10)]:m;if(!g)return;g.colour&&J(d,g.colour),g.pill&&!u&&te(d,g.pill),oe(d,g),O(d.closest("li"),g.class);const C=Y(g.badge);C&&d.appendChild(C),X(g.url,location.pathname)&&(d.classList.add("active"),d.setAttribute("aria-current","page"))})}const y=$("#site-footer");if(y.length){let e=function(r){return String(r??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},a=function(r){return e(r).replace(/'/g,"'")},f=function(r){if(r&&r.type==="separator")return'<li class="footer-sep"><hr></li>';if(r&&r.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md"}" aria-hidden="true"></li>`;if(r&&r.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const A=a(r.url||"#"),k=e(r.text||""),q=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(f).join("")}</ul>`:"",N=ae(r),Z=[N.hover&&` data-menu-item-hover="${a(N.hover)}"`,N.active&&` data-menu-item-active="${a(N.active)}"`].filter(Boolean).join(""),ee=N.accent?` style="--dm-menu-highlight:${a(N.accent)}"`:"",V=W(r.class).join(" ");return`<li${V?` class="${a(V)}"`:""}><a href="${A}"${Z}${ee}>${k}</a>${q}</li>`},v=function(){if(!l.footer)return"";const r=Object.keys(n).filter(k=>o[k]);if(!r.length)return"";const A=W(l.footer.socialClass).join(" ");return`<div class="footer-social">${r.map(k=>{const{label:q,svg:N}=n[k];return`<a href="${o[k]}" target="_blank" rel="noopener noreferrer" aria-label="${q}" class="footer-social-link${A?` ${a(A)}`:""}">${N}</a>`}).join("")}</div>`},b=function(r){if(!r||r.type==="separator"||r.type==="spacer")return"";if(r.type==="theme-switcher")return`<li class="cms-footer-col">${f(r).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const A=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(f).join("")}</ul>`:"",k=h(r)?`<span class="cms-footer-col-title">${e(r.text||"")}</span>`:`<a class="cms-footer-col-title" href="${a(r.url||"#")}">${e(r.text||"")}</a>`,q=["cms-footer-col",...W(r.class)].join(" ");return`<li class="${a(q)}">${k}${A}</li>`},m=function(r,A,k=!1){return(r||[]).forEach(q=>{q&&(q.type==="separator"||q.type==="spacer"||q.type==="theme-switcher")||(k&&h(q)||A.push(q),Array.isArray(q.items)&&q.items.length&&m(q.items,A))}),A};const o=l.social||{},n={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let s='<div class="footer-inner container">';const t=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""},i=["columns","row"].includes(l.footer?.layout)?l.footer.layout:"auto",c=i==="columns"||i==="auto"&&t.primary.some(r=>Array.isArray(r?.items)&&r.items.length),h=r=>c&&!r.url&&Array.isArray(r.items)&&r.items.length>0;if(c){const r=p.brand||{},A=r.text||l.title||"",k=r.logo?`<img src="${a(r.logo)}" class="cms-footer-brand-logo" alt="">`:r.icon?'<span class="cms-footer-brand-icon"></span>':"",q=String(l.footer?.blurb||"").trim();s='<div class="footer-inner footer-inner--columns container"><div class="cms-footer-top">',s+=`<div class="cms-footer-brand">${A||k?`<a class="cms-footer-brand-name" href="${a(r.url||"/")}">${k}<span>${e(A)}</span></a>`:""}${q?`<p class="cms-footer-blurb">${e(q)}</p>`:""}${v()}</div>`,t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${t.primary.map(b).join("")}</ul></nav>`),s+='</div><div class="cms-footer-bottom">',t.copyright&&(s+=`<p class="cms-footer-copyright">${e(t.copyright)}</p>`),t.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${t.legal.map(f).join("")}</ul></nav>`),s+="</div>"}else t.copyright&&(s+=`<p>${e(t.copyright)}</p>`),t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${t.primary.map(f).join("")}</ul></nav>`),t.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(f).join("")}</ul></nav>`),s+=v();s+="</div>",y.html(s),window.CMSThemeSwitcher?.scan(y.get(0));const d=y.get(0).querySelector(".cms-footer-brand-icon");d&&p.brand?.icon&&d.setAttribute("data-icon",p.brand.icon),O(y.get(0),l.footer?.class);const u=[...m(t.primary,[],!0),...m(t.legal,[])];y.get(0).querySelectorAll(".footer-nav a").forEach((r,A)=>{const k=u[A];if(!k)return;k.colour&&J(r,k.colour);const q=Y(k.badge);q&&r.appendChild(q),X(k.url,location.pathname)&&(r.classList.add("is-active"),r.setAttribute("aria-current","page"))}),R(y.get(0).querySelector(".footer-nav--primary"),{highlight:t.primaryHighlight}),R(y.get(0).querySelector(".footer-nav--legal"),{highlight:t.legalHighlight}),O(y.get(0).querySelector(".footer-nav--primary"),t.primaryClass),O(y.get(0).querySelector(".footer-nav--legal"),t.legalClass);const C=l.footer?.showMotionToggle!==!1,M=S.get("reduced_motion"),D=l.footer?.motionDefault,z=M!=null?!!M:D==="on"?!0:D==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),B=l.footer?.motionControl==="checkbox",I=y.get(0).querySelector(".cms-footer-bottom")||y.get(0).querySelector(".footer-inner");if(C&&I){const r=document.createElement("input");r.type="checkbox",r.className=B?"footer-motion-checkbox":"form-switch-input",r.id="dm-motion-switch",r.checked=z,r.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const A=document.createElement("span");A.className=B?"footer-motion-label":"form-switch-label",A.textContent="Reduce motion";const k=document.createElement("label");k.className=B?"footer-motion-check":"form-switch footer-motion-switch",k.title="Reduce motion",k.appendChild(r),k.appendChild(A),I.appendChild(k)}}const L=document.getElementById("site-sidebar");L&&ue(L),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const w=$(".page-body");if(w.length){w.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),w.find(".tabs").each(function(){Domma.elements.tabs(this)}),w.find(".carousel").each(function(){const o={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide"};if(this.dataset.animationDuration){const n=parseInt(this.dataset.animationDuration,10);Number.isFinite(n)&&(o.animationDuration=n)}this.dataset.animationEasing&&(o.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(n=>n.cloneNode(!0)),this._dmCarouselOpts=o),this._dmCarousel=Domma.elements.carousel(this,o)}),w.find(".dm-countdown").each(function(){const o={autoStart:!0},n=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),t=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=t?new Date(+t[1],+t[2]-1,+t[3]).getTime():s?new Date(s).getTime():NaN;Number.isFinite(e)?o.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(o.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const a=/^dd:/i.test(n);o.format=a?"hh:mm:ss":n||(o.duration>=36e5?"hh:mm:ss":"mm:ss");const f=this,v=c=>String(c).padStart(2,"0"),i=c=>{const h=Math.max(0,Math.floor(c/1e3)),b=f.querySelector(".dm-timer-time");b&&(b.textContent=`${v(Math.floor(h/86400))}:${v(Math.floor(h%86400/3600))}:${v(Math.floor(h%3600/60))}:${v(h%60)}`)};a&&(o.onTick=i),Domma.elements.timer(this,o),a&&i(o.duration||0)}),w.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),w.find(".dm-progression").each(function(){const o=Array.from(this.querySelectorAll(".dm-progression-item")).map(t=>{const e={title:t.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:t.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return t.dataset.date&&(e.date=t.dataset.date),t.dataset.status&&(e.status=t.dataset.status),t.dataset.eventIcon&&(e.icon=t.dataset.eventIcon),t.dataset.entryId&&(e._entryId=t.dataset.entryId),e}),n=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:n,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...o.length?{items:o}:{}});if((this.dataset.mode||"timeline")==="timeline"){const t={completed:{label:"Completed",icon:"check-circle",tone:"success"},planned:{label:"Planned",icon:"circle",tone:"secondary"},"in-progress":{label:"In progress",icon:"clock",tone:"primary"},blocked:{label:"Blocked",icon:"alert-circle",tone:"danger"}};this.querySelectorAll(".dm-progression-item").forEach((e,a)=>{const f=o[a];if(!f)return;const v=e.querySelector(".dm-progression-title");if(f.icon&&v){const c=document.createElement("span");c.dataset.icon=f.icon,c.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",c.setAttribute("aria-hidden","true"),v.prepend(c)}const i=t[f.status];if(i&&v){const c=document.createElement("span");c.className=`badge badge-${i.tone} cms-timeline-status`,c.style.cssText="display:inline-flex;align-items:center;gap:.35em;margin:.4rem 0";const h=document.createElement("span");h.dataset.icon=i.icon,h.dataset.iconSize="14",h.setAttribute("aria-hidden","true"),c.append(h,document.createTextNode(i.label)),v.after(c)}})}Domma.icons.scan(this),o.length&&(this._dmCtx={instance:s,items:o})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}document.querySelectorAll(".page-body .row[data-reveal]").forEach(o=>{const n=o.dataset.revealMode||"stagger",s=o.dataset.revealAnimation||"slide-up",t=parseInt(o.dataset.revealDuration,10)||400,e=parseInt(o.dataset.revealStagger,10)||60,a=parseInt(o.dataset.revealDelay,10)||0,f=o.dataset.revealDirection==="rtl",v=Array.from(o.children),i={"slide-up":"translateY(30px)","slide-down":"translateY(-30px)","slide-left":"translateX(30px)","slide-right":"translateX(-30px)",zoom:"scale(0.85)",flip:"perspective(600px) rotateX(15deg)"};v.forEach((c,h)=>{c.style.opacity="0",c.style.transform=i[s]||"",c.style.transition=`opacity ${t}ms ease, transform ${t}ms ease`;const b=f?v.length-1-h:h;c.style.transitionDelay=n==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const c=new IntersectionObserver(h=>{h.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",c.unobserve(b.target))})},{threshold:.1});v.forEach(h=>c.observe(h))}))}),w.find(".card[data-collapsible]").each(function(){const o=this.querySelector(".card-header");o&&o.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),w.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const o=this.dataset.soTarget,n=document.getElementById(o);if(!n)return;const s=E.slideover({title:n.dataset.soTitle||"",size:n.dataset.soSize||"md",position:n.dataset.soPosition||"right"});n.style.display="",s.setContent(n),s.open()})})}if(typeof $.setup=="function"){const o=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(n=>{try{const s=P(n.dataset.config);Object.assign(o,s)}catch{}}),Object.keys(o).length>0){const n={};for(const[s,t]of Object.entries(o)){const e=t?.events?.click,{confirm:a,toast:f,alert:v,prompt:i,...c}=e||{};a||f||v||i?($(s).on("click",async function(b){if(b.preventDefault(),a&&!await E.confirm(a))return;let d=null;if(!(i&&(d=await E.prompt(i,{inputPlaceholder:c.promptPlaceholder||"",inputValue:c.promptDefault||""}),d===null))){if(c.target){const m=$(c.target);c.toggleClass&&m.toggleClass(c.toggleClass),c.addClass&&m.addClass(c.addClass),c.removeClass&&m.removeClass(c.removeClass),d!==null&&(c.setText&&m.text(d),c.setVal&&m.val(d),c.setAttr&&m.attr(c.setAttr,d))}c.href&&(window.location.href=c.href),f&&E.toast(f,{type:c.toastType||"success"}),v&&E.alert(v)}}),Object.keys(c).length&&(n[s]={...t,events:{...t.events,click:c}})):n[s]=t}$.setup(n)}}w.length&&j(w.get(0))}),window.wireCTAButtons=j;function j(p){p.querySelectorAll(".dm-cta-trigger").forEach(l=>{l.__ctaWired||(l.__ctaWired=!0,l.addEventListener("click",async()=>{const x=l.dataset.action,y=l.dataset.entry,L=l.dataset.confirm;let w=S.get("auth_token");if(!w){E.toast("Please log in to perform this action.",{type:"warning"});return}if(L&&!await E.confirm(L))return;const o=Array.from(l.childNodes).map(s=>s.cloneNode(!0));l.disabled=!0,l.textContent="Running\u2026";const n=s=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(l.dataset.scope?{entryId:y,scope:l.dataset.scope}:{entryId:y})});try{let s=await n(w);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const a=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(a.ok){const{token:f}=await a.json();S.set("auth_token",f),w=f,s=await n(w)}}}const t=await s.json().catch(()=>({}));if(!s.ok||t.success===!1)throw new Error(t.error||t.message||`Error ${s.status}`);E.toast(t.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{l.disabled=!1,l.textContent="",o.forEach(s=>l.appendChild(s)),Domma.icons.scan(l)}}))})}(function(){const l=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!l.length)return;const x=S.get("auth_token");function y(w,o){const n=document.createElement("div");n.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(o){s.appendChild(document.createTextNode("Please "));const t=document.createElement("a");t.href="/login",t.textContent="sign in",s.appendChild(t),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=w;return n.appendChild(s),n}function L(w,o){const n=document.createElement("template");n.innerHTML=o||"",w.replaceWith(n.content)}l.forEach(async w=>{const o=w.dataset.collectionAttrs;if(!o)return;if(!x){w.replaceChildren(y("",!0));return}const n=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:o})});try{let s=x,t=await n(s);if(t.status===401){const a=S.get("auth_refresh_token");if(a){const f=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(f.ok){const{token:v}=await f.json();S.set("auth_token",v),s=v,t=await n(s)}}}if(!t.ok){const{error:a}=await t.json().catch(()=>({}));w.replaceChildren(y(a||"Unable to load entries.",!1));return}const{html:e}=await t.json();L(w,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{w.replaceChildren(y("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),j(document.body)},100)})(),(function(){const l=document.querySelectorAll("[data-collection-table]");!l.length||typeof T>"u"||!T.create||l.forEach(x=>{let y;try{y=P(x.dataset.payload)}catch{return}const{columns:L,rows:w,search:o,sortable:n,exportable:s,pageSize:t,empty:e,ctaConfig:a,slug:f,exportCfg:v}=y;if(!L?.length)return;if(a){const h=L.findIndex(b=>b.key==="_cta");h!==-1&&(L[h]={key:"_cta",title:"",render:(b,d)=>{const m=document.createElement("button");if(m.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,m.dataset.action=a.action||"",a.scope&&(m.dataset.scope=a.scope),m.dataset.entry=d._entryId||"",a.confirm&&(m.dataset.confirm=a.confirm),a.icon){const u=document.createElement("span");u.dataset.icon=a.icon,m.appendChild(u),m.appendChild(document.createTextNode(" "))}return m.appendChild(document.createTextNode(a.label||"Run")),m}})}const i="col-table-"+Math.random().toString(36).slice(2,7),c=document.createElement("div");c.id=i,x.replaceChildren(c),T.create("#"+i,{data:w,columns:L,search:o,sortable:n,exportable:s,pageSize:t,emptyMessage:e}),a&&j(c),x._dmTable={columns:L,rows:w,slug:f,exportCfg:v,render(h){T.create("#"+i,{data:h,columns:L,search:o,sortable:n,exportable:s,pageSize:t,emptyMessage:e}),a&&j(c)}}})})(),(function(){const l=document.querySelectorAll("[data-form-inline]");if(!l.length||typeof F>"u")return;function x(n){const s=n&&n.logic;return!!(s&&(s.visibility||s.requirement))}function y(n,s){const t={};return(n||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const a=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),f=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,v={...e.formConfig||{}};v.span==="full"&&s&&(v.span=s);const i={type:f,required:x(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...e.tooltip&&{tooltip:e.tooltip},...v}};e.minLength!==void 0&&(i.minLength=e.minLength),e.maxLength!==void 0&&(i.maxLength=e.maxLength),e.min!==void 0&&(i.min=e.min),e.max!==void 0&&(i.max=e.max),a&&(e.variant&&(i.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(i.multiple=!0),e.density&&(i.density=e.density),e.columns&&(i.columns=e.columns),e.accent&&(i.accent=e.accent),e.accentStyle&&(i.accentStyle=e.accentStyle),e.glow&&(i.glow=!0),e.glowColour&&(i.glowColour=e.glowColour),e.shadow&&(i.shadow=e.shadow),e.shadowColour&&(i.shadowColour=e.shadowColour)),t[e.name]=i}),t}function L(n){const s={};return(n||[]).forEach(t=>{if(!(!t.name||t.type==="page-break"||t.type==="spacer")&&(t.type==="select"||t.type==="multiselect")&&t.required){const e=(t.options||[])[0];e&&(s[t.name]=typeof e=="object"?e.value:e)}}),s}function w(n,s){(s||[]).forEach(t=>{if(t.type!=="date"||!t.name)return;const e=n.querySelector(`[name="${t.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function o(n,s,t){let e=n.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",n.appendChild(e)),e.textContent=s,e.style.cssText=t?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}l.forEach(n=>{let s;try{s=P(n.dataset.formInline)}catch{return}const t=s.fields||[],e=s.settings||{},a=Number(e.columns)===3?3:2,f=e.layout||"grid",v=t.some(m=>m.type==="page-break"),i=async m=>{if(n.dataset.triggerBlocked==="true"){const u=n._formLogicRuntime,g=u&&u.submitBlock?u.submitBlock().reasons:[];o(n,g.join(" ")||"This form cannot be submitted yet.",!0);const C=new Error("Blocked by a form trigger");throw C.formSubmitFailed=!0,C}try{const u=n.querySelector(".fb-form-honeypot"),g=u?.querySelector('input[type="text"]')||null,C=u?.querySelector('[name="_t"]')||null,M=Object.assign({},m);delete M._t,u?.dataset.hpName&&delete M[u.dataset.hpName],g!==null&&(M._hp=g.value),C!==null&&(M._t=C.value);const D=await H.post(`/api/forms/submit/${s.slug}`,M);if(D&&D.error){o(n,D.error,!0);const z=new Error(D.error);throw z.formSubmitFailed=!0,z}if(D?.redirect){window.location.href=D.redirect;return}for(;n.firstChild;)n.removeChild(n.firstChild);o(n,D?.message||e.successMessage||"Thank you for your submission.",!1)}catch(u){throw u.formSubmitFailed||o(n,u.message||"Submission failed. Please try again.",!0),u}},c=Date.now();function h(m){const u=m.querySelector("form");if(!u||u.querySelector(".fb-form-honeypot"))return;const g=document.createElement("div");g.className="fb-form-honeypot",g.setAttribute("aria-hidden","true");const C=document.createElement("input");C.name=u.querySelector('[name="website"]')?"_website":"website",g.dataset.hpName=C.name,C.type="text",C.tabIndex=-1,C.autocomplete="url",C.placeholder="https://",g.appendChild(C);const M=document.createElement("input");M.name="_t",M.type="hidden",M.value=c,g.appendChild(M),u.appendChild(g)}function b(m){if(!window.FormLogicEngine||!t.some(g=>g.logic||Array.isArray(g.triggers)&&g.triggers.length))return null;const u=new window.FormLogicEngine.FormLogicRuntime(s,n,m?{baseValues:m}:void 0);if(u.init(),n._formLogicRuntime=u,n._formLogicObserved)return u;if(n._formLogicObserved=!0,n.parentNode&&typeof MutationObserver<"u"){const g=new MutationObserver(function(C){for(const M of C)for(const D of M.removedNodes)if(D===n||D.nodeType===1&&D.contains&&D.contains(n)){n._formLogicRuntime&&n._formLogicRuntime.destroy(),g.disconnect();return}});g.observe(n.parentNode,{childList:!0,subtree:!1}),n._formLogicObserver=g}return u}let d=!1;if(n.addEventListener("dm-form-end",m=>{const u=m.detail?.validation;if(u&&!u.valid){G(n,u.missing),K(u);return}d||(d=!0,i(m.detail?.values||{}).catch(()=>{}).finally(()=>{d=!1}))}),v&&F.wizard){const m=re(t,s.title),u=m.map(I=>({title:I.title,description:I.description,fields:y(I.fields,a)})),g=m.map(I=>I.fields);let C=0,M=null,D=null;const z=I=>{const r=window.FormLogicEngine;if(!r||!r.validateFields)return;const A=r.validateFields(n,g[C]||[]);A.valid||(I.preventDefault(),I.stopImmediatePropagation(),G(n,A.missing),K(A))},B=()=>{const I=g[C]||t;le(n,e),Q(n,e,I),w(n,I),U(n,I),e.honeypot!==!1&&h(n),M&&M.destroy(),M=b(D?.getStepData?D.getStepData():void 0),n.querySelectorAll(".wizard-next, .wizard-submit").forEach(r=>r.addEventListener("click",z,!0))};D=F.wizard(n,{schema:{steps:u},layout:f,columns:a,finishText:e.submitText||"Finish",onSubmit:i,onStepChange:I=>{C=I,setTimeout(B,0)}}),n.addEventListener("dm-form-trigger",I=>{if(I.detail?.type!=="goto-step"||!D?.goToStep)return;const r=Number(I.detail.action.step);!Number.isFinite(r)||r<0||r>=u.length||r!==C&&D.goToStep(r)}),Promise.resolve(D).then(B)}else if(F.render){const m=F.render(n,y(t,a),L(t),{submitText:e.submitText||"Submit",layout:f,columns:a,onSubmit:i});Promise.resolve(m).then(function(){ce(n,e,f),Q(n,e,t),w(n,t),U(n,t),e.honeypot!==!1&&h(n),b()})}})})();function de(p,l){const x=document.body,y=x.classList,L=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),w=()=>window.matchMedia("(min-width: 993px)").matches;p.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const f=a.textContent.trim();f&&(a.setAttribute("title",f),a.querySelector("svg, [data-icon]")||(a.dataset.initial=f.charAt(0).toUpperCase()))});const o=document.createElement("button");o.type="button",o.className="dm-nav-collapse-toggle";const n=()=>{const a=y.contains("dm-vnav-collapsed");o.setAttribute("aria-pressed",String(a)),o.setAttribute("aria-label",a?"Expand menu":"Collapse menu"),o.title=o.getAttribute("aria-label");const f=l?"chevrons-left":"chevrons-right",v=l?"chevrons-right":"chevrons-left";o.innerHTML=`<span data-icon="${a?f:v}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(o)},s=a=>{y.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),n()};o.addEventListener("click",()=>s(!y.contains("dm-vnav-collapsed"))),n();const t=p.querySelector(".navbar-container")||p;t.insertBefore(o,t.firstChild?.nextSibling||null);const e=a=>{y.toggle("dm-vnav-autohide",a),y.toggle("dm-vnav-collapsible",!a&&L.has("collapsible")),a?y.remove("dm-vnav-collapsed"):L.has("collapsible")&&S.get("nav_collapsed")===!0&&y.add("dm-vnav-collapsed"),p.classList.remove("dm-nav-revealed"),n()};p.addEventListener("pointerdown",a=>{y.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!p.classList.contains("dm-nav-revealed")&&(p.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{p.contains(a.target)||p.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(p,{items:()=>{const a=y.contains("dm-vnav-autohide"),f=y.contains("dm-vnav-collapsed"),v=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:f?"Expand menu":"Collapse to icons",icon:f?"maximize":"minimize",disabled:!w()||a||!L.has("collapsible"),action:()=>s(!f)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!w(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!v,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(L.has("autohide")),y.toggle("dm-vnav-collapsed",!L.has("autohide")&&L.has("start-collapsed")),n()}}]}})}function me(p){p.classList.add("dm-nav-scroll-hide");let l=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const y=window.scrollY,L=p.matches(":focus-within")||p.querySelector(".navbar-dropdown.open, .navbar-collapse.show");y>l&&y>p.offsetHeight&&!L?p.classList.add("dm-nav-scrolled-away"):y<l&&p.classList.remove("dm-nav-scrolled-away"),l=y}))},{passive:!0})}function ue(p){let l={};try{l=JSON.parse(p.dataset.contents||"{}")}catch{}const x=p.closest(".site-main"),y=document.querySelector(".site-content"),L=new Set(String(l.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),w=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),o=i=>(y?[...y.querySelectorAll(i)]:[]).filter(w).filter(c=>!L.has(c.textContent.trim().toLowerCase())),n=l.headings==="h2"?o("h2"):l.headings==="h3"?o("h3"):l.headings==="h2h3"?o("h2, h3"):o("h2").length?o("h2"):o("h3");if(!n.length){x?.classList.add("no-sidebar-items");return}const s=new Set([...document.querySelectorAll("[id]")].map(i=>i.id)),t=document.createElement("nav");t.className="dm-contents",t.setAttribute("aria-label",l.title||"Page contents"),l.hover&&(t.dataset.menuHover=l.hover),l.selected&&(t.dataset.menuActive=l.selected),l.accent&&t.style.setProperty("--dm-menu-highlight",l.accent.startsWith("#")?l.accent:`var(--dm-${l.accent})`),l.font&&(t.style.fontFamily=l.font),l.fontSize&&(t.style.fontSize=l.fontSize),l.fontWeight&&(t.style.fontWeight=l.fontWeight);const e=document.createElement("div");e.className="dm-contents-head";const a=document.createElement("p");a.className="dm-contents-title",a.textContent=l.title||"",e.appendChild(a),t.appendChild(e);const f=document.createElement("ol");f.className="dm-contents-list"+(l.numbered?" is-numbered":"");const v=[];for(const i of n){if(!i.id){const b=(i.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let d=b;for(let m=2;s.has(d);m++)d=`${b}-${m}`;s.add(d),i.id=d}const c=document.createElement("li");c.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const h=document.createElement("a");h.href=`#${i.id}`,h.textContent=i.textContent.trim(),c.appendChild(h),f.appendChild(c),v.push([i,h])}if(t.appendChild(f),p.replaceChildren(t),l.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,c=()=>{const b=v.find(([d])=>i.get(d))||null;v.forEach(([,d])=>{const m=b&&d===b[1];d.classList.toggle("is-active",!!m),m?d.setAttribute("aria-current","location"):d.removeAttribute("aria-current")})},h=new IntersectionObserver(b=>{b.forEach(d=>i.set(d.target,d.isIntersecting)),c()},{rootMargin:"-80px 0px -60% 0px"});v.forEach(([b])=>h.observe(b))}if(x?.classList.contains("contents-collapsible")){const i=document.createElement("button");i.type="button",i.className="dm-contents-toggle";const c=()=>{const h=x.classList.contains("contents-collapsed");i.setAttribute("aria-expanded",String(!h)),i.setAttribute("aria-label",h?"Show the contents":"Hide the contents"),i.title=i.getAttribute("aria-label"),i.innerHTML=`<span data-icon="${h?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(i)};i.addEventListener("click",()=>{S.set("contents_collapsed",x.classList.toggle("contents-collapsed")),c()}),c(),e.insertBefore(i,e.firstChild)}if(x?.classList.contains("contents-floating")&&l.movable){t.classList.add("is-movable");const i=S.get("contents_position");i&&Number.isFinite(i.x)&&Object.assign(p.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",c=>{if(c.target.closest("button"))return;const h=p.getBoundingClientRect(),b=c.clientX-h.left,d=c.clientY-h.top,m=g=>{const C=Math.max(0,Math.min(window.innerWidth-h.width,g.clientX-b)),M=Math.max(0,Math.min(window.innerHeight-40,g.clientY-d));Object.assign(p.style,{left:`${C}px`,top:`${M}px`,right:"auto",bottom:"auto"})},u=()=>{document.removeEventListener("pointermove",m),S.set("contents_position",{x:parseInt(p.style.left,10),y:parseInt(p.style.top,10)})};document.addEventListener("pointermove",m),document.addEventListener("pointerup",u,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(p,{items:()=>{const i=x?.classList.contains("contents-collapsible"),c=x?.classList.contains("contents-collapsed"),h=v.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:c?"Show the contents":"Collapse to a button",icon:c?"list":"minimize",action:()=>p.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!h,action:async()=>{const b=`${location.origin}${location.pathname}#${h[0].id}`;try{await _.copyToClipboard(b),E.toast("Link copied.",{type:"success"})}catch{E.toast("Could not copy the link.",{type:"error"})}}},{label:"Back to the top",icon:"arrow-up",action:()=>window.scrollTo({top:0,behavior:"smooth"})}]}}),x?.classList.contains("contents-autohide")&&(p.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!p.classList.contains("is-revealed")&&(p.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{p.contains(i.target)||p.classList.remove("is-revealed")}))}
|
|
1
|
+
import{decodeBase64Json as P}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as Y,applyColour as J,applyPill as te,applyFloat as oe,applyHighlight as R,applyItemHighlight as ne,itemHighlightAttrs as ae,isCurrentItem as X,menuAlign as se,applyClasses as O,cssClasses as W}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{initFloatingMenus as ie}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as U,markInvalidFields as G,showValidationPopup as K}from"/public/js/forms.js?v=20261009-form-classes";import{buildWizardSteps as re}from"/public/js/form-steps.mjs";import{applyFormClasses as Q,applySubmitButtons as ce,applyWizardButtons as le}from"/public/js/form-submit.mjs";document.addEventListener("click",f=>{const l=f.target?.closest?.(".dm-banner__dismiss");l&&(f.preventDefault(),l.closest(".dm-banner")?.remove())}),$(()=>{const f=window.__CMS_NAV__||{},l=window.__CMS_SITE__||{};if(l.autoTheme?.enabled){let o=function(e){const a=(e||"07:00").split(":");return+a[0]*60+(+a[1]||0)},s=function(){const e=new Date,a=e.getHours()*60+e.getMinutes();return a>=o(n.dayStart)&&a<o(n.nightStart)?n.dayTheme:n.nightTheme},t=function(e){const a=n.apply&&n.apply[e]||{theme:e,custom:""};Domma.theme.set(a.theme);for(const p of[...document.body.classList])p.startsWith("dm-custom-")&&document.body.classList.remove(p);a.custom&&document.body.classList.add(a.custom)};const n=l.autoTheme;t(s()),setInterval(()=>t(s()),6e4)}const x=$("#site-navbar");if(x.length&&f.brand){let s=function(d,m=0){return Array.isArray(d)?d.filter(u=>!(u&&(u.type==="spacer"||u.type==="theme-switcher"))).filter(u=>!(u&&u.type==="separator"&&m===0)).map(u=>u&&u.type==="separator"?{divider:!0}:Array.isArray(u?.items)?{...u,items:s(u.items,m+1)}:u):d},t=function(d){return Array.isArray(d)?d.filter(m=>!(m&&(m.type==="separator"||m.type==="spacer"||m.type==="theme-switcher"))):[]};const n={...f.brand},o=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let d="";n.logo?d+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(d+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(d+=`<span class="navbar-brand-text${o}">${n.text}</span>`),n.tagline&&(d+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=d}else o&&n.text&&(n.html=`<span class="navbar-brand-text${o}">${n.text}</span>`);const e=f.position==="floating",a=f.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:s(f.items||[]),variant:f.variant||"dark",position:e?"static":f.position||"sticky",collapsible:!0,appearOnHover:!!f.appearOnHover}),Domma.icons.scan("#site-navbar");const p=x.get(0);p.classList.add(`dm-nav-align-${se(f)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",f.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const g=f.dock||{};a&&!e&&de(p,f.side==="right"),!a&&g.hideOnScroll&&me(p),R(p,f),O(p,f.class),e&&(oe(x.get(0),f),ie(x.get(0).parentNode||document));const i=p.querySelector(".navbar-toggle"),c=p.querySelector(".navbar-collapse");if(i&&c){let d=null;const m=()=>{c.classList.contains("show")&&i.click()},u=()=>{d&&(d.remove(),d=null),document.body.classList.remove("dm-drawer-open")},y=()=>{d||(d=document.createElement("div"),d.className="dm-navbar-backdrop",d.addEventListener("click",m),document.body.appendChild(d),document.body.classList.add("dm-drawer-open"))};i.addEventListener("click",()=>{requestAnimationFrame(()=>{c.classList.contains("show")?y():u()})}),c.addEventListener("click",C=>{C.target.closest("a")&&requestAnimationFrame(u)}),document.addEventListener("keydown",C=>{C.key==="Escape"&&c.classList.contains("show")&&m()})}const h=p.querySelector(".navbar-nav");if(h){const d=Array.from(h.children);let m=0;for(const u of f.items||[])if(!(u&&u.type==="separator")){if(u&&u.type==="spacer"){const y=document.createElement("li"),C=["xs","sm","md","lg","xl","flex"].includes(u.size)?u.size:"md";y.className=`dm-menu-spacer dm-menu-spacer--${C}`,y.setAttribute("aria-hidden","true"),h.insertBefore(y,d[m]||null);continue}if(u&&u.type==="theme-switcher"){const y=document.createElement("li");y.className="dm-menu-theme-switcher";const C=document.createElement("span");C.dataset.themeSwitcher="menu",C.dataset.position="inline",y.appendChild(C),h.insertBefore(y,d[m]||null);continue}m++}window.CMSThemeSwitcher?.scan(h)}const b=t(f.items||[]);p.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(d=>{const m=b[parseInt(d.dataset.index,10)];if(!m)return;const u=d.dataset.subindex!=null,y=u&&Array.isArray(m.items)?m.items[parseInt(d.dataset.subindex,10)]:m;if(!y)return;y.colour&&J(d,y.colour),y.pill&&!u&&te(d,y.pill),ne(d,y),O(d.closest("li"),y.class);const C=Y(y.badge);C&&d.appendChild(C),X(y.url,location.pathname)&&(d.classList.add("active"),d.setAttribute("aria-current","page"))})}const v=$("#site-footer");if(v.length){let e=function(r){return String(r??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},a=function(r){return e(r).replace(/'/g,"'")},p=function(r){if(r&&r.type==="separator")return'<li class="footer-sep"><hr></li>';if(r&&r.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md"}" aria-hidden="true"></li>`;if(r&&r.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const A=a(r.url||"#"),L=e(r.text||""),q=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(p).join("")}</ul>`:"",N=ae(r),Z=[N.hover&&` data-menu-item-hover="${a(N.hover)}"`,N.active&&` data-menu-item-active="${a(N.active)}"`].filter(Boolean).join(""),ee=N.accent?` style="--dm-menu-highlight:${a(N.accent)}"`:"",V=W(r.class).join(" ");return`<li${V?` class="${a(V)}"`:""}><a href="${A}"${Z}${ee}>${L}</a>${q}</li>`},g=function(){if(!l.footer)return"";const r=Object.keys(o).filter(L=>n[L]);if(!r.length)return"";const A=W(l.footer.socialClass).join(" ");return`<div class="footer-social">${r.map(L=>{const{label:q,svg:N}=o[L];return`<a href="${n[L]}" target="_blank" rel="noopener noreferrer" aria-label="${q}" class="footer-social-link${A?` ${a(A)}`:""}">${N}</a>`}).join("")}</div>`},b=function(r){if(!r||r.type==="separator"||r.type==="spacer")return"";if(r.type==="theme-switcher")return`<li class="cms-footer-col">${p(r).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const A=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(p).join("")}</ul>`:"",L=h(r)?`<span class="cms-footer-col-title">${e(r.text||"")}</span>`:`<a class="cms-footer-col-title" href="${a(r.url||"#")}">${e(r.text||"")}</a>`,q=["cms-footer-col",...W(r.class)].join(" ");return`<li class="${a(q)}">${L}${A}</li>`},m=function(r,A,L=!1){return(r||[]).forEach(q=>{q&&(q.type==="separator"||q.type==="spacer"||q.type==="theme-switcher")||(L&&h(q)||A.push(q),Array.isArray(q.items)&&q.items.length&&m(q.items,A))}),A};const n=l.social||{},o={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let s='<div class="footer-inner container">';const t=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""},i=["columns","row"].includes(l.footer?.layout)?l.footer.layout:"auto",c=i==="columns"||i==="auto"&&t.primary.some(r=>Array.isArray(r?.items)&&r.items.length),h=r=>c&&!r.url&&Array.isArray(r.items)&&r.items.length>0;if(c){const r=f.brand||{},A=r.text||l.title||"",L=r.logo?`<img src="${a(r.logo)}" class="cms-footer-brand-logo" alt="">`:r.icon?'<span class="cms-footer-brand-icon"></span>':"",q=String(l.footer?.blurb||"").trim();s='<div class="footer-inner footer-inner--columns container"><div class="cms-footer-top">',s+=`<div class="cms-footer-brand">${A||L?`<a class="cms-footer-brand-name" href="${a(r.url||"/")}">${L}<span>${e(A)}</span></a>`:""}${q?`<p class="cms-footer-blurb">${e(q)}</p>`:""}${g()}</div>`,t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${t.primary.map(b).join("")}</ul></nav>`),s+='</div><div class="cms-footer-bottom">',t.copyright&&(s+=`<p class="cms-footer-copyright">${e(t.copyright)}</p>`),t.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${t.legal.map(p).join("")}</ul></nav>`),s+="</div>"}else t.copyright&&(s+=`<p>${e(t.copyright)}</p>`),t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${t.primary.map(p).join("")}</ul></nav>`),t.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(p).join("")}</ul></nav>`),s+=g();s+="</div>",v.html(s),window.CMSThemeSwitcher?.scan(v.get(0));const d=v.get(0).querySelector(".cms-footer-brand-icon");d&&f.brand?.icon&&d.setAttribute("data-icon",f.brand.icon),O(v.get(0),l.footer?.class);const u=[...m(t.primary,[],!0),...m(t.legal,[])];v.get(0).querySelectorAll(".footer-nav a").forEach((r,A)=>{const L=u[A];if(!L)return;L.colour&&J(r,L.colour);const q=Y(L.badge);q&&r.appendChild(q),X(L.url,location.pathname)&&(r.classList.add("is-active"),r.setAttribute("aria-current","page"))}),R(v.get(0).querySelector(".footer-nav--primary"),{highlight:t.primaryHighlight}),R(v.get(0).querySelector(".footer-nav--legal"),{highlight:t.legalHighlight}),O(v.get(0).querySelector(".footer-nav--primary"),t.primaryClass),O(v.get(0).querySelector(".footer-nav--legal"),t.legalClass);const C=l.footer?.showMotionToggle!==!1,M=S.get("reduced_motion"),D=l.footer?.motionDefault,z=M!=null?!!M:D==="on"?!0:D==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),B=l.footer?.motionControl==="checkbox",I=v.get(0).querySelector(".cms-footer-bottom")||v.get(0).querySelector(".footer-inner");if(C&&I){const r=document.createElement("input");r.type="checkbox",r.className=B?"footer-motion-checkbox":"form-switch-input",r.id="dm-motion-switch",r.checked=z,r.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const A=document.createElement("span");A.className=B?"footer-motion-label":"form-switch-label",A.textContent="Reduce motion";const L=document.createElement("label");L.className=B?"footer-motion-check":"form-switch footer-motion-switch",L.title="Reduce motion",L.appendChild(r),L.appendChild(A),I.appendChild(L)}}const k=document.getElementById("site-sidebar");k&&ue(k),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const w=$(".page-body");if(w.length){w.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),w.find(".tabs").each(function(){Domma.elements.tabs(this)}),w.find(".carousel").each(function(){const n={primary:"--dm-primary",secondary:"--dm-secondary",accent:"--dm-primary",text:"--dm-text",surface:"--dm-surface",background:"--dm-background",success:"--dm-success",warning:"--dm-warning",danger:"--dm-danger",info:"--dm-info"},o=g=>{const i=String(g||"").trim();return i?n[i.toLowerCase()]?`var(${n[i.toLowerCase()]})`:CSS.supports("color",i)?i:"":""},s=o(this.dataset.background),t=o(this.dataset.arrowBackground),e=o(this.dataset.arrowColor);s&&(this.style.setProperty("--dm-carousel-background",s),this.classList.add("dm-carousel-custom-background")),t&&this.style.setProperty("--dm-carousel-arrow-background",t),e&&this.style.setProperty("--dm-carousel-arrow-color",e);const a=["top","middle","bottom"].includes(this.dataset.arrowPosition)?this.dataset.arrowPosition:"middle";this.classList.add(`dm-carousel-arrows-${a}`);const p={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide"};if(this.dataset.animationDuration){const g=parseInt(this.dataset.animationDuration,10);Number.isFinite(g)&&(p.animationDuration=g)}this.dataset.animationEasing&&(p.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(g=>g.cloneNode(!0)),this._dmCarouselOpts=p),this._dmCarousel=Domma.elements.carousel(this,p)}),w.find(".dm-countdown").each(function(){const n={autoStart:!0},o=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),t=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=t?new Date(+t[1],+t[2]-1,+t[3]).getTime():s?new Date(s).getTime():NaN;Number.isFinite(e)?n.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(n.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const a=/^dd:/i.test(o);n.format=a?"hh:mm:ss":o||(n.duration>=36e5?"hh:mm:ss":"mm:ss");const p=this,g=c=>String(c).padStart(2,"0"),i=c=>{const h=Math.max(0,Math.floor(c/1e3)),b=p.querySelector(".dm-timer-time");b&&(b.textContent=`${g(Math.floor(h/86400))}:${g(Math.floor(h%86400/3600))}:${g(Math.floor(h%3600/60))}:${g(h%60)}`)};a&&(n.onTick=i),Domma.elements.timer(this,n),a&&i(n.duration||0)}),w.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),w.find(".dm-progression").each(function(){const n=Array.from(this.querySelectorAll(".dm-progression-item")).map(t=>{const e={title:t.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:t.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return t.dataset.date&&(e.date=t.dataset.date),t.dataset.status&&(e.status=t.dataset.status),t.dataset.eventIcon&&(e.icon=t.dataset.eventIcon),t.dataset.entryId&&(e._entryId=t.dataset.entryId),e}),o=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:o,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...n.length?{items:n}:{}});if((this.dataset.mode||"timeline")==="timeline"){const t={completed:{label:"Completed",icon:"check-circle",tone:"success"},planned:{label:"Planned",icon:"circle",tone:"secondary"},"in-progress":{label:"In progress",icon:"clock",tone:"primary"},blocked:{label:"Blocked",icon:"alert-circle",tone:"danger"}};this.querySelectorAll(".dm-progression-item").forEach((e,a)=>{const p=n[a];if(!p)return;const g=e.querySelector(".dm-progression-title");if(p.icon&&g){const c=document.createElement("span");c.dataset.icon=p.icon,c.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",c.setAttribute("aria-hidden","true"),g.prepend(c)}const i=t[p.status];if(i&&g){const c=document.createElement("span");c.className=`badge badge-${i.tone} cms-timeline-status`,c.style.cssText="display:inline-flex;align-items:center;gap:.35em;margin:.4rem 0";const h=document.createElement("span");h.dataset.icon=i.icon,h.dataset.iconSize="14",h.setAttribute("aria-hidden","true"),c.append(h,document.createTextNode(i.label)),g.after(c)}})}Domma.icons.scan(this),n.length&&(this._dmCtx={instance:s,items:n})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}document.querySelectorAll(".page-body .row[data-reveal]").forEach(n=>{const o=n.dataset.revealMode||"stagger",s=n.dataset.revealAnimation||"slide-up",t=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,a=parseInt(n.dataset.revealDelay,10)||0,p=n.dataset.revealDirection==="rtl",g=Array.from(n.children),i={"slide-up":"translateY(30px)","slide-down":"translateY(-30px)","slide-left":"translateX(30px)","slide-right":"translateX(-30px)",zoom:"scale(0.85)",flip:"perspective(600px) rotateX(15deg)"};g.forEach((c,h)=>{c.style.opacity="0",c.style.transform=i[s]||"",c.style.transition=`opacity ${t}ms ease, transform ${t}ms ease`;const b=p?g.length-1-h:h;c.style.transitionDelay=o==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const c=new IntersectionObserver(h=>{h.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",c.unobserve(b.target))})},{threshold:.1});g.forEach(h=>c.observe(h))}))}),w.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),w.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,o=document.getElementById(n);if(!o)return;const s=E.slideover({title:o.dataset.soTitle||"",size:o.dataset.soSize||"md",position:o.dataset.soPosition||"right"});o.style.display="",s.setContent(o),s.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(o=>{try{const s=P(o.dataset.config);Object.assign(n,s)}catch{}}),Object.keys(n).length>0){const o={};for(const[s,t]of Object.entries(n)){const e=t?.events?.click,{confirm:a,toast:p,alert:g,prompt:i,...c}=e||{};a||p||g||i?($(s).on("click",async function(b){if(b.preventDefault(),a&&!await E.confirm(a))return;let d=null;if(!(i&&(d=await E.prompt(i,{inputPlaceholder:c.promptPlaceholder||"",inputValue:c.promptDefault||""}),d===null))){if(c.target){const m=$(c.target);c.toggleClass&&m.toggleClass(c.toggleClass),c.addClass&&m.addClass(c.addClass),c.removeClass&&m.removeClass(c.removeClass),d!==null&&(c.setText&&m.text(d),c.setVal&&m.val(d),c.setAttr&&m.attr(c.setAttr,d))}c.href&&(window.location.href=c.href),p&&E.toast(p,{type:c.toastType||"success"}),g&&E.alert(g)}}),Object.keys(c).length&&(o[s]={...t,events:{...t.events,click:c}})):o[s]=t}$.setup(o)}}w.length&&j(w.get(0))}),window.wireCTAButtons=j;function j(f){f.querySelectorAll(".dm-cta-trigger").forEach(l=>{l.__ctaWired||(l.__ctaWired=!0,l.addEventListener("click",async()=>{const x=l.dataset.action,v=l.dataset.entry,k=l.dataset.confirm;let w=S.get("auth_token");if(!w){E.toast("Please log in to perform this action.",{type:"warning"});return}if(k&&!await E.confirm(k))return;const n=Array.from(l.childNodes).map(s=>s.cloneNode(!0));l.disabled=!0,l.textContent="Running\u2026";const o=s=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(l.dataset.scope?{entryId:v,scope:l.dataset.scope}:{entryId:v})});try{let s=await o(w);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const a=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(a.ok){const{token:p}=await a.json();S.set("auth_token",p),w=p,s=await o(w)}}}const t=await s.json().catch(()=>({}));if(!s.ok||t.success===!1)throw new Error(t.error||t.message||`Error ${s.status}`);E.toast(t.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{l.disabled=!1,l.textContent="",n.forEach(s=>l.appendChild(s)),Domma.icons.scan(l)}}))})}(function(){const l=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!l.length)return;const x=S.get("auth_token");function v(w,n){const o=document.createElement("div");o.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(n){s.appendChild(document.createTextNode("Please "));const t=document.createElement("a");t.href="/login",t.textContent="sign in",s.appendChild(t),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=w;return o.appendChild(s),o}function k(w,n){const o=document.createElement("template");o.innerHTML=n||"",w.replaceWith(o.content)}l.forEach(async w=>{const n=w.dataset.collectionAttrs;if(!n)return;if(!x){w.replaceChildren(v("",!0));return}const o=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:n})});try{let s=x,t=await o(s);if(t.status===401){const a=S.get("auth_refresh_token");if(a){const p=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(p.ok){const{token:g}=await p.json();S.set("auth_token",g),s=g,t=await o(s)}}}if(!t.ok){const{error:a}=await t.json().catch(()=>({}));w.replaceChildren(v(a||"Unable to load entries.",!1));return}const{html:e}=await t.json();k(w,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{w.replaceChildren(v("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),j(document.body)},100)})(),(function(){const l=document.querySelectorAll("[data-collection-table]");!l.length||typeof T>"u"||!T.create||l.forEach(x=>{let v;try{v=P(x.dataset.payload)}catch{return}const{columns:k,rows:w,search:n,sortable:o,exportable:s,pageSize:t,empty:e,ctaConfig:a,slug:p,exportCfg:g}=v;if(!k?.length)return;if(a){const h=k.findIndex(b=>b.key==="_cta");h!==-1&&(k[h]={key:"_cta",title:"",render:(b,d)=>{const m=document.createElement("button");if(m.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,m.dataset.action=a.action||"",a.scope&&(m.dataset.scope=a.scope),m.dataset.entry=d._entryId||"",a.confirm&&(m.dataset.confirm=a.confirm),a.icon){const u=document.createElement("span");u.dataset.icon=a.icon,m.appendChild(u),m.appendChild(document.createTextNode(" "))}return m.appendChild(document.createTextNode(a.label||"Run")),m}})}const i="col-table-"+Math.random().toString(36).slice(2,7),c=document.createElement("div");c.id=i,x.replaceChildren(c),T.create("#"+i,{data:w,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&j(c),x._dmTable={columns:k,rows:w,slug:p,exportCfg:g,render(h){T.create("#"+i,{data:h,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&j(c)}}})})(),(function(){const l=document.querySelectorAll("[data-form-inline]");if(!l.length||typeof F>"u")return;function x(o){const s=o&&o.logic;return!!(s&&(s.visibility||s.requirement))}function v(o,s){const t={};return(o||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const a=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),p=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,g={...e.formConfig||{}};g.span==="full"&&s&&(g.span=s);const i={type:p,required:x(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...e.tooltip&&{tooltip:e.tooltip},...g}};e.minLength!==void 0&&(i.minLength=e.minLength),e.maxLength!==void 0&&(i.maxLength=e.maxLength),e.min!==void 0&&(i.min=e.min),e.max!==void 0&&(i.max=e.max),a&&(e.variant&&(i.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(i.multiple=!0),e.density&&(i.density=e.density),e.columns&&(i.columns=e.columns),e.accent&&(i.accent=e.accent),e.accentStyle&&(i.accentStyle=e.accentStyle),e.glow&&(i.glow=!0),e.glowColour&&(i.glowColour=e.glowColour),e.shadow&&(i.shadow=e.shadow),e.shadowColour&&(i.shadowColour=e.shadowColour)),t[e.name]=i}),t}function k(o){const s={};return(o||[]).forEach(t=>{if(!(!t.name||t.type==="page-break"||t.type==="spacer")&&(t.type==="select"||t.type==="multiselect")&&t.required){const e=(t.options||[])[0];e&&(s[t.name]=typeof e=="object"?e.value:e)}}),s}function w(o,s){(s||[]).forEach(t=>{if(t.type!=="date"||!t.name)return;const e=o.querySelector(`[name="${t.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(o,s,t){let e=o.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",o.appendChild(e)),e.textContent=s,e.style.cssText=t?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}l.forEach(o=>{let s;try{s=P(o.dataset.formInline)}catch{return}const t=s.fields||[],e=s.settings||{},a=Number(e.columns)===3?3:2,p=e.layout||"grid",g=t.some(m=>m.type==="page-break"),i=async m=>{if(o.dataset.triggerBlocked==="true"){const u=o._formLogicRuntime,y=u&&u.submitBlock?u.submitBlock().reasons:[];n(o,y.join(" ")||"This form cannot be submitted yet.",!0);const C=new Error("Blocked by a form trigger");throw C.formSubmitFailed=!0,C}try{const u=o.querySelector(".fb-form-honeypot"),y=u?.querySelector('input[type="text"]')||null,C=u?.querySelector('[name="_t"]')||null,M=Object.assign({},m);delete M._t,u?.dataset.hpName&&delete M[u.dataset.hpName],y!==null&&(M._hp=y.value),C!==null&&(M._t=C.value);const D=await H.post(`/api/forms/submit/${s.slug}`,M);if(D&&D.error){n(o,D.error,!0);const z=new Error(D.error);throw z.formSubmitFailed=!0,z}if(D?.redirect){window.location.href=D.redirect;return}for(;o.firstChild;)o.removeChild(o.firstChild);n(o,D?.message||e.successMessage||"Thank you for your submission.",!1)}catch(u){throw u.formSubmitFailed||n(o,u.message||"Submission failed. Please try again.",!0),u}},c=Date.now();function h(m){const u=m.querySelector("form");if(!u||u.querySelector(".fb-form-honeypot"))return;const y=document.createElement("div");y.className="fb-form-honeypot",y.setAttribute("aria-hidden","true");const C=document.createElement("input");C.name=u.querySelector('[name="website"]')?"_website":"website",y.dataset.hpName=C.name,C.type="text",C.tabIndex=-1,C.autocomplete="url",C.placeholder="https://",y.appendChild(C);const M=document.createElement("input");M.name="_t",M.type="hidden",M.value=c,y.appendChild(M),u.appendChild(y)}function b(m){if(!window.FormLogicEngine||!t.some(y=>y.logic||Array.isArray(y.triggers)&&y.triggers.length))return null;const u=new window.FormLogicEngine.FormLogicRuntime(s,o,m?{baseValues:m}:void 0);if(u.init(),o._formLogicRuntime=u,o._formLogicObserved)return u;if(o._formLogicObserved=!0,o.parentNode&&typeof MutationObserver<"u"){const y=new MutationObserver(function(C){for(const M of C)for(const D of M.removedNodes)if(D===o||D.nodeType===1&&D.contains&&D.contains(o)){o._formLogicRuntime&&o._formLogicRuntime.destroy(),y.disconnect();return}});y.observe(o.parentNode,{childList:!0,subtree:!1}),o._formLogicObserver=y}return u}let d=!1;if(o.addEventListener("dm-form-end",m=>{const u=m.detail?.validation;if(u&&!u.valid){G(o,u.missing),K(u);return}d||(d=!0,i(m.detail?.values||{}).catch(()=>{}).finally(()=>{d=!1}))}),g&&F.wizard){const m=re(t,s.title),u=m.map(I=>({title:I.title,description:I.description,fields:v(I.fields,a)})),y=m.map(I=>I.fields);let C=0,M=null,D=null;const z=I=>{const r=window.FormLogicEngine;if(!r||!r.validateFields)return;const A=r.validateFields(o,y[C]||[]);A.valid||(I.preventDefault(),I.stopImmediatePropagation(),G(o,A.missing),K(A))},B=()=>{const I=y[C]||t;le(o,e),Q(o,e,I),w(o,I),U(o,I),e.honeypot!==!1&&h(o),M&&M.destroy(),M=b(D?.getStepData?D.getStepData():void 0),o.querySelectorAll(".wizard-next, .wizard-submit").forEach(r=>r.addEventListener("click",z,!0))};D=F.wizard(o,{schema:{steps:u},layout:p,columns:a,finishText:e.submitText||"Finish",onSubmit:i,onStepChange:I=>{C=I,setTimeout(B,0)}}),o.addEventListener("dm-form-trigger",I=>{if(I.detail?.type!=="goto-step"||!D?.goToStep)return;const r=Number(I.detail.action.step);!Number.isFinite(r)||r<0||r>=u.length||r!==C&&D.goToStep(r)}),Promise.resolve(D).then(B)}else if(F.render){const m=F.render(o,v(t,a),k(t),{submitText:e.submitText||"Submit",layout:p,columns:a,onSubmit:i});Promise.resolve(m).then(function(){ce(o,e,p),Q(o,e,t),w(o,t),U(o,t),e.honeypot!==!1&&h(o),b()})}})})();function de(f,l){const x=document.body,v=x.classList,k=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),w=()=>window.matchMedia("(min-width: 993px)").matches;f.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const p=a.textContent.trim();p&&(a.setAttribute("title",p),a.querySelector("svg, [data-icon]")||(a.dataset.initial=p.charAt(0).toUpperCase()))});const n=document.createElement("button");n.type="button",n.className="dm-nav-collapse-toggle";const o=()=>{const a=v.contains("dm-vnav-collapsed");n.setAttribute("aria-pressed",String(a)),n.setAttribute("aria-label",a?"Expand menu":"Collapse menu"),n.title=n.getAttribute("aria-label");const p=l?"chevrons-left":"chevrons-right",g=l?"chevrons-right":"chevrons-left";n.innerHTML=`<span data-icon="${a?p:g}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(n)},s=a=>{v.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),o()};n.addEventListener("click",()=>s(!v.contains("dm-vnav-collapsed"))),o();const t=f.querySelector(".navbar-container")||f;t.insertBefore(n,t.firstChild?.nextSibling||null);const e=a=>{v.toggle("dm-vnav-autohide",a),v.toggle("dm-vnav-collapsible",!a&&k.has("collapsible")),a?v.remove("dm-vnav-collapsed"):k.has("collapsible")&&S.get("nav_collapsed")===!0&&v.add("dm-vnav-collapsed"),f.classList.remove("dm-nav-revealed"),o()};f.addEventListener("pointerdown",a=>{v.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!f.classList.contains("dm-nav-revealed")&&(f.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{f.contains(a.target)||f.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(f,{items:()=>{const a=v.contains("dm-vnav-autohide"),p=v.contains("dm-vnav-collapsed"),g=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:p?"Expand menu":"Collapse to icons",icon:p?"maximize":"minimize",disabled:!w()||a||!k.has("collapsible"),action:()=>s(!p)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!w(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!g,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(k.has("autohide")),v.toggle("dm-vnav-collapsed",!k.has("autohide")&&k.has("start-collapsed")),o()}}]}})}function me(f){f.classList.add("dm-nav-scroll-hide");let l=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const v=window.scrollY,k=f.matches(":focus-within")||f.querySelector(".navbar-dropdown.open, .navbar-collapse.show");v>l&&v>f.offsetHeight&&!k?f.classList.add("dm-nav-scrolled-away"):v<l&&f.classList.remove("dm-nav-scrolled-away"),l=v}))},{passive:!0})}function ue(f){let l={};try{l=JSON.parse(f.dataset.contents||"{}")}catch{}const x=f.closest(".site-main"),v=document.querySelector(".site-content"),k=new Set(String(l.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),w=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=i=>(v?[...v.querySelectorAll(i)]:[]).filter(w).filter(c=>!k.has(c.textContent.trim().toLowerCase())),o=l.headings==="h2"?n("h2"):l.headings==="h3"?n("h3"):l.headings==="h2h3"?n("h2, h3"):n("h2").length?n("h2"):n("h3");if(!o.length){x?.classList.add("no-sidebar-items");return}const s=new Set([...document.querySelectorAll("[id]")].map(i=>i.id)),t=document.createElement("nav");t.className="dm-contents",t.setAttribute("aria-label",l.title||"Page contents"),l.hover&&(t.dataset.menuHover=l.hover),l.selected&&(t.dataset.menuActive=l.selected),l.accent&&t.style.setProperty("--dm-menu-highlight",l.accent.startsWith("#")?l.accent:`var(--dm-${l.accent})`),l.font&&(t.style.fontFamily=l.font),l.fontSize&&(t.style.fontSize=l.fontSize),l.fontWeight&&(t.style.fontWeight=l.fontWeight);const e=document.createElement("div");e.className="dm-contents-head";const a=document.createElement("p");a.className="dm-contents-title",a.textContent=l.title||"",e.appendChild(a),t.appendChild(e);const p=document.createElement("ol");p.className="dm-contents-list"+(l.numbered?" is-numbered":"");const g=[];for(const i of o){if(!i.id){const b=(i.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let d=b;for(let m=2;s.has(d);m++)d=`${b}-${m}`;s.add(d),i.id=d}const c=document.createElement("li");c.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const h=document.createElement("a");h.href=`#${i.id}`,h.textContent=i.textContent.trim(),c.appendChild(h),p.appendChild(c),g.push([i,h])}if(t.appendChild(p),f.replaceChildren(t),l.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,c=()=>{const b=g.find(([d])=>i.get(d))||null;g.forEach(([,d])=>{const m=b&&d===b[1];d.classList.toggle("is-active",!!m),m?d.setAttribute("aria-current","location"):d.removeAttribute("aria-current")})},h=new IntersectionObserver(b=>{b.forEach(d=>i.set(d.target,d.isIntersecting)),c()},{rootMargin:"-80px 0px -60% 0px"});g.forEach(([b])=>h.observe(b))}if(x?.classList.contains("contents-collapsible")){const i=document.createElement("button");i.type="button",i.className="dm-contents-toggle";const c=()=>{const h=x.classList.contains("contents-collapsed");i.setAttribute("aria-expanded",String(!h)),i.setAttribute("aria-label",h?"Show the contents":"Hide the contents"),i.title=i.getAttribute("aria-label"),i.innerHTML=`<span data-icon="${h?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(i)};i.addEventListener("click",()=>{S.set("contents_collapsed",x.classList.toggle("contents-collapsed")),c()}),c(),e.insertBefore(i,e.firstChild)}if(x?.classList.contains("contents-floating")&&l.movable){t.classList.add("is-movable");const i=S.get("contents_position");i&&Number.isFinite(i.x)&&Object.assign(f.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",c=>{if(c.target.closest("button"))return;const h=f.getBoundingClientRect(),b=c.clientX-h.left,d=c.clientY-h.top,m=y=>{const C=Math.max(0,Math.min(window.innerWidth-h.width,y.clientX-b)),M=Math.max(0,Math.min(window.innerHeight-40,y.clientY-d));Object.assign(f.style,{left:`${C}px`,top:`${M}px`,right:"auto",bottom:"auto"})},u=()=>{document.removeEventListener("pointermove",m),S.set("contents_position",{x:parseInt(f.style.left,10),y:parseInt(f.style.top,10)})};document.addEventListener("pointermove",m),document.addEventListener("pointerup",u,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(f,{items:()=>{const i=x?.classList.contains("contents-collapsible"),c=x?.classList.contains("contents-collapsed"),h=g.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:c?"Show the contents":"Collapse to a button",icon:c?"list":"minimize",action:()=>f.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!h,action:async()=>{const b=`${location.origin}${location.pathname}#${h[0].id}`;try{await _.copyToClipboard(b),E.toast("Link copied.",{type:"success"})}catch{E.toast("Could not copy the link.",{type:"error"})}}},{label:"Back to the top",icon:"arrow-up",action:()=>window.scrollTo({top:0,behavior:"smooth"})}]}}),x?.classList.contains("contents-autohide")&&(f.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!f.classList.contains("is-revealed")&&(f.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{f.contains(i.target)||f.classList.remove("is-revealed")}))}
|
package/public/js/text-style.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SIZE_MAP={xs:".75rem",sm:".875rem",base:"1rem",lg:"1.125rem",xl:"1.25rem","2xl":"1.5rem","3xl":"1.875rem","4xl":"2.25rem"},WEIGHT_MAP={thin:"100",light:"300",normal:"400",medium:"500",semibold:"600",bold:"700",extrabold:"800",black:"900"},SPACING_MAP={tight:"-0.05em",normal:"0em",wide:"0.05em",wider:"0.1em"},COLOUR_TOKENS={primary:"var(--dm-color-primary)",secondary:"var(--dm-color-secondary)",muted:"var(--dm-text-muted)",danger:"var(--dm-color-danger)",success:"var(--dm-color-success)",warning:"var(--dm-color-warning)",info:"var(--dm-color-info)"},FONT_MAP={Georgia:"Georgia,serif",Arial:"Arial,sans-serif",Verdana:"Verdana,sans-serif","Courier New":"'Courier New',monospace","Times New Roman":"'Times New Roman',serif","Trebuchet MS":"'Trebuchet MS',sans-serif"},TRANSFORM_MAP={upper:"uppercase",lower:"lowercase",capitalize:"capitalize",none:"none"},DECORATION_MAP={underline:"underline","line-through":"line-through",none:"none"};function
|
|
1
|
+
export const SIZE_MAP={xs:".75rem",sm:".875rem",base:"1rem",lg:"1.125rem",xl:"1.25rem","2xl":"1.5rem","3xl":"1.875rem","4xl":"2.25rem"},WEIGHT_MAP={thin:"100",light:"300",normal:"400",medium:"500",semibold:"600",bold:"700",extrabold:"800",black:"900"},SPACING_MAP={tight:"-0.05em",normal:"0em",wide:"0.05em",wider:"0.1em"},COLOUR_TOKENS={primary:"var(--dm-color-primary)",secondary:"var(--dm-color-secondary)",muted:"var(--dm-text-muted)",danger:"var(--dm-color-danger)",success:"var(--dm-color-success)",warning:"var(--dm-color-warning)",info:"var(--dm-color-info)"},FONT_MAP={Georgia:"Georgia,serif",Arial:"Arial,sans-serif",Verdana:"Verdana,sans-serif","Courier New":"'Courier New',monospace","Times New Roman":"'Times New Roman',serif","Trebuchet MS":"'Trebuchet MS',sans-serif",Roboto:"'Roboto',sans-serif",Inter:"'Inter',sans-serif","Open Sans":"'Open Sans',sans-serif",Lato:"'Lato',sans-serif",Nunito:"'Nunito',sans-serif",Poppins:"'Poppins',sans-serif","Source Sans 3":"'Source Sans 3',sans-serif",Montserrat:"'Montserrat',sans-serif",Raleway:"'Raleway',sans-serif","DM Sans":"'DM Sans',sans-serif","Work Sans":"'Work Sans',sans-serif",Merriweather:"'Merriweather',serif","Playfair Display":"'Playfair Display',serif",Lora:"'Lora',serif","Roboto Slab":"'Roboto Slab',serif",Lexend:"'Lexend',sans-serif","Atkinson Hyperlegible":"'Atkinson Hyperlegible',sans-serif",Andika:"'Andika',sans-serif"},CMS_WEB_FONTS=Object.freeze(["Roboto","Inter","Open Sans","Lato","Nunito","Poppins","Source Sans 3","Montserrat","Raleway","DM Sans","Work Sans","Merriweather","Playfair Display","Lora","Roboto Slab","Lexend","Atkinson Hyperlegible","Andika"]),TRANSFORM_MAP={upper:"uppercase",lower:"lowercase",capitalize:"capitalize",none:"none"},DECORATION_MAP={underline:"underline","line-through":"line-through",none:"none"};function r(e){return e!=null&&e!==!1&&e!=="false"&&e!=="0"}function o(e){return String(e).replace(/["<>]/g,"").trim()}export function buildTextStyle(e={}){const n=[];if(e.size&&n.push(`font-size:${SIZE_MAP[e.size]||o(e.size)}`),e["point-size"]){const s=parseFloat(e["point-size"]);!isNaN(s)&&s>0&&n.push(`font-size:${s}pt`)}r(e.bold)?n.push("font-weight:700"):e.weight&&WEIGHT_MAP[e.weight]&&n.push(`font-weight:${WEIGHT_MAP[e.weight]}`),r(e.italic)&&n.push("font-style:italic");const i=e.colour||e.color;return i&&n.push(`color:${COLOUR_TOKENS[i]||o(i)}`),e.font&&FONT_MAP[e.font]&&n.push(`font-family:${FONT_MAP[e.font]}`),e.transform&&TRANSFORM_MAP[e.transform]&&n.push(`text-transform:${TRANSFORM_MAP[e.transform]}`),e.decoration&&DECORATION_MAP[e.decoration]&&n.push(`text-decoration:${DECORATION_MAP[e.decoration]}`),e.spacing&&n.push(`letter-spacing:${SPACING_MAP[e.spacing]||o(e.spacing)}`),e.align&&(n.push("display:block"),n.push(`text-align:${o(e.align)}`)),e.style&&n.push(o(e.style).replace(/;+$/,"")),n.join(";")}
|
|
@@ -108,9 +108,19 @@ export async function themeRoutes(fastify, opts = {}) {
|
|
|
108
108
|
|
|
109
109
|
// The admin applies its theme before anything else loads. A custom admin
|
|
110
110
|
// theme is its base, its class, and the CSS behind that class.
|
|
111
|
-
|
|
111
|
+
// `?id=` asks how a CANDIDATE theme would apply - Content > Theme previews
|
|
112
|
+
// a pick live with it before anything is saved. A candidate other than the
|
|
113
|
+
// saved theme needs theme.read: an unused Theme Roller draft's tokens are
|
|
114
|
+
// deliberately not readable by every signed-in user (see /theme/catalog).
|
|
115
|
+
fastify.get('/theme/admin', {preHandler: [authenticate]}, async (request, reply) => {
|
|
112
116
|
const cfg = await loadThemeConfig();
|
|
113
|
-
const
|
|
117
|
+
const saved = cfg.adminTheme || 'charcoal-dark';
|
|
118
|
+
const asked = String(request.query?.id || '');
|
|
119
|
+
if (asked && asked !== saved) {
|
|
120
|
+
await requirePermission('theme', 'read')(request, reply);
|
|
121
|
+
if (reply.sent) return reply;
|
|
122
|
+
}
|
|
123
|
+
const id = asked || saved;
|
|
114
124
|
const {dommaTheme, customClass} = resolveThemeClasses(id, cfg);
|
|
115
125
|
return {theme: id, dommaTheme, customClass, css: customClass ? compileCustomThemeCss([id]) : ''};
|
|
116
126
|
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one-click unsubscribe page every outreach email links to:
|
|
3
|
+
*
|
|
4
|
+
* GET /campaign-unsubscribe?e=<base64url address>&k=<HMAC>
|
|
5
|
+
*
|
|
6
|
+
* Core's, not the sending Tool's, so the link in an email already sent keeps
|
|
7
|
+
* working when that Tool lapses or is removed (PECR). The address goes on the
|
|
8
|
+
* shared suppression list (`outreach-unsubscribes`), which every outreach
|
|
9
|
+
* Tool checks before it sends.
|
|
10
|
+
*
|
|
11
|
+
* A Campaigns Pro older than this engine registers the same path itself, and
|
|
12
|
+
* plugins register before core: then core stands aside rather than collide.
|
|
13
|
+
*
|
|
14
|
+
* @module routes/outreach-public
|
|
15
|
+
*/
|
|
16
|
+
import {createCollection, createEntry, deleteEntry, getCollection, listEntries} from '../services/collections.js';
|
|
17
|
+
import {createSuppression} from '../../plugins/_lib/outreach/suppression.js';
|
|
18
|
+
import {UNSUBSCRIBE_PATH, verifyUnsubscribe} from '../../plugins/_lib/outreach/unsubscribe.js';
|
|
19
|
+
import {getUnsubscribeSecret} from '../services/outreachUnsubscribe.js';
|
|
20
|
+
|
|
21
|
+
const SLUG = 'outreach-unsubscribes';
|
|
22
|
+
|
|
23
|
+
const esc = (s) => String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
24
|
+
|
|
25
|
+
function page(title, bodyHtml) {
|
|
26
|
+
return `<!DOCTYPE html><html lang="en-GB"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="robots" content="noindex"><title>${esc(title)}</title><link rel="stylesheet" href="/dist/domma/domma.css"><style>main{max-width:36rem;margin:4rem auto;padding:0 1.25rem;line-height:1.6}</style></head><body><main>${bodyHtml}</main></body></html>`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The suppression list's collection - made if no outreach Tool ever made it. */
|
|
30
|
+
async function ensureList() {
|
|
31
|
+
try {
|
|
32
|
+
await getCollection(SLUG);
|
|
33
|
+
} catch {
|
|
34
|
+
await createCollection({
|
|
35
|
+
title: 'Outreach Unsubscribes', slug: SLUG,
|
|
36
|
+
description: 'The shared suppression list - addresses no outreach Tool may email.',
|
|
37
|
+
fields: [
|
|
38
|
+
{name: 'email', label: 'Email', type: 'text', required: true},
|
|
39
|
+
{name: 'at', label: 'At', type: 'text', required: false},
|
|
40
|
+
{name: 'source', label: 'Source', type: 'text', required: false}
|
|
41
|
+
]
|
|
42
|
+
}).catch(() => {});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {import('fastify').FastifyInstance} fastify
|
|
48
|
+
* @param {{col?: object, secret?: string}} [opts] - tests inject a collections service and a secret
|
|
49
|
+
*/
|
|
50
|
+
export async function outreachPublicRoutes(fastify, opts = {}) {
|
|
51
|
+
if (fastify.hasRoute({method: 'GET', url: UNSUBSCRIBE_PATH})) return;
|
|
52
|
+
const col = opts.col || {createEntry, deleteEntry, listEntries};
|
|
53
|
+
const suppression = createSuppression(col);
|
|
54
|
+
|
|
55
|
+
fastify.get(UNSUBSCRIBE_PATH, async (req, reply) => {
|
|
56
|
+
reply.header('Cache-Control', 'no-store').header('Referrer-Policy', 'no-referrer');
|
|
57
|
+
let email = null;
|
|
58
|
+
try { email = verifyUnsubscribe(opts.secret || getUnsubscribeSecret(), req.query?.e, req.query?.k); } catch { email = null; }
|
|
59
|
+
if (!email) {
|
|
60
|
+
return reply.code(400).type('text/html; charset=utf-8')
|
|
61
|
+
.send(page('Unsubscribe', '<h1>That link is not right.</h1><p>Use the unsubscribe link from one of our emails.</p>'));
|
|
62
|
+
}
|
|
63
|
+
if (!opts.col) await ensureList();
|
|
64
|
+
await suppression.add(email, 'unsubscribe-link');
|
|
65
|
+
return reply.type('text/html; charset=utf-8').send(page('Unsubscribed',
|
|
66
|
+
`<h1>You are unsubscribed.</h1><p>${esc(email)} will get no more campaign or survey emails from this site.</p>`));
|
|
67
|
+
});
|
|
68
|
+
}
|
package/server/routes/public.js
CHANGED
|
@@ -12,6 +12,7 @@ import {renderPage} from '../services/renderer.js';
|
|
|
12
12
|
import {resolvePublishedPage} from '../services/pagePublishing.js';
|
|
13
13
|
import {buildRobotsTxt, generate as generateSitemap} from '../services/sitemap.js';
|
|
14
14
|
import {resolveViewer} from '../services/viewerSession.js';
|
|
15
|
+
import {validSessionCookie, newSessionCookie} from '../services/shortcodeExpiry.js';
|
|
15
16
|
import {touchPreviewLink, verifyPreviewToken} from '../services/previewLinks.js';
|
|
16
17
|
import {getEffectiveRoles} from '../services/userRoles.js';
|
|
17
18
|
import {hooks} from '../services/hooks.js';
|
|
@@ -220,7 +221,21 @@ export async function publicRoutes(fastify) {
|
|
|
220
221
|
// visitor's role for visibility filtering. The first getPage() above
|
|
221
222
|
// was anonymous - it only needed the page's metadata.
|
|
222
223
|
const render = async () => {
|
|
223
|
-
|
|
224
|
+
let expiry = null;
|
|
225
|
+
if (!isDraft && page.hasExpiry) {
|
|
226
|
+
const rawCookies = Object.fromEntries(String(request.headers.cookie || '').split(';').map(part => {
|
|
227
|
+
const at = part.indexOf('=');
|
|
228
|
+
return at < 0 ? ['', ''] : [part.slice(0, at).trim(), part.slice(at + 1).trim()];
|
|
229
|
+
}));
|
|
230
|
+
let sessionId = validSessionCookie(rawCookies.dm_shortcode_session, process.env.JWT_SECRET);
|
|
231
|
+
if (!sessionId) {
|
|
232
|
+
const fresh = newSessionCookie(process.env.JWT_SECRET);
|
|
233
|
+
sessionId = fresh.id;
|
|
234
|
+
reply.header('set-cookie', `dm_shortcode_session=${fresh.value}; Path=/; HttpOnly; SameSite=Lax${request.protocol === 'https' ? '; Secure' : ''}`);
|
|
235
|
+
}
|
|
236
|
+
expiry = {ip:request.ip || '', sessionId, secret:process.env.JWT_SECRET};
|
|
237
|
+
}
|
|
238
|
+
const pageForRole = await getPage(page.urlPath, {user: userObj, ...(expiry && {expiry})}) || page;
|
|
224
239
|
return renderPage(pageForRole, {
|
|
225
240
|
baseUrl,
|
|
226
241
|
user: userObj,
|
|
@@ -238,6 +253,14 @@ export async function publicRoutes(fastify) {
|
|
|
238
253
|
return reply.type('text/html; charset=utf-8').send(await render());
|
|
239
254
|
}
|
|
240
255
|
|
|
256
|
+
// Shortcode expiry depends on the visitor and changes on each view,
|
|
257
|
+
// so these pages must render against the expiry ledger on every request.
|
|
258
|
+
if (page.hasExpiry) {
|
|
259
|
+
reply.header('cache-control', 'no-store, private');
|
|
260
|
+
hooks.emit('content:pageViewed', {urlPath, title: page.title || ''});
|
|
261
|
+
return reply.type('text/html; charset=utf-8').send(await render());
|
|
262
|
+
}
|
|
263
|
+
|
|
241
264
|
// A row-scoped [view] shows each signed-in user their OWN rows, so the
|
|
242
265
|
// render differs per user, not per role set - two users sharing a role
|
|
243
266
|
// must not share an entry. Anonymous viewers of such a view get no rows
|
package/server/server.js
CHANGED
|
@@ -723,6 +723,11 @@ await app.register(docsPublicRoutes);
|
|
|
723
723
|
const { callPublicRoutes } = await import('./routes/call-public.js');
|
|
724
724
|
await app.register(callPublicRoutes);
|
|
725
725
|
|
|
726
|
+
// The outreach unsubscribe link (/campaign-unsubscribe) - core's, so it outlives the Tool that
|
|
727
|
+
// sent the email. After the plugins: an older Campaigns Pro that registers it itself wins.
|
|
728
|
+
const { outreachPublicRoutes } = await import('./routes/outreach-public.js');
|
|
729
|
+
await app.register(outreachPublicRoutes);
|
|
730
|
+
|
|
726
731
|
// Before the pages: /favicon.* must never reach the page router.
|
|
727
732
|
const { faviconRoutes } = await import('./routes/favicon.js');
|
|
728
733
|
await app.register(faviconRoutes);
|
|
@@ -70,7 +70,7 @@ export async function getPageMtime(urlPath) {
|
|
|
70
70
|
export async function getPage(urlPath, opts = {}) {
|
|
71
71
|
const filePath = await resolveExistingFilePath(urlPath);
|
|
72
72
|
try {
|
|
73
|
-
return await readPageFile(filePath, {user: opts.user || null});
|
|
73
|
+
return await readPageFile(filePath, {user: opts.user || null, expiry:opts.expiry});
|
|
74
74
|
} catch {
|
|
75
75
|
return null;
|
|
76
76
|
}
|
|
@@ -98,9 +98,9 @@ export async function getPage(urlPath, opts = {}) {
|
|
|
98
98
|
*/
|
|
99
99
|
export async function buildPreviewPage(urlPath, frontmatter, body, opts = {}) {
|
|
100
100
|
const raw = serialiseMarkdown({...(frontmatter || {})}, body || '');
|
|
101
|
-
const {data, content, html, usedComponents, tags, perViewer} =
|
|
102
|
-
await parseMarkdown(raw, {user: opts.user || null, urlPath});
|
|
103
|
-
return {...data, urlPath, content, html, usedComponents, cacheTags: tags, perViewer};
|
|
101
|
+
const {data, content, html, usedComponents, tags, perViewer, hasExpiry} =
|
|
102
|
+
await parseMarkdown(raw, {user: opts.user || null, urlPath, expiry:{preview:true}});
|
|
103
|
+
return {...data, urlPath, content, html, usedComponents, cacheTags: tags, perViewer, hasExpiry};
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
/**
|
|
@@ -411,8 +411,8 @@ async function readPageFile(filePath, opts = {}) {
|
|
|
411
411
|
const urlPath = filePathToUrlPath(filePath);
|
|
412
412
|
// urlPath is passed through so `[menu location="…"]` in the body resolves
|
|
413
413
|
// against menus bound to this page or its project, exactly as the navbar does.
|
|
414
|
-
const { data, content, html, usedComponents, tags, perViewer } =
|
|
415
|
-
await parseMarkdown(raw, {user: opts.user || null, urlPath});
|
|
414
|
+
const { data, content, html, usedComponents, tags, perViewer, hasExpiry } =
|
|
415
|
+
await parseMarkdown(raw, {user: opts.user || null, urlPath, expiry:opts.expiry || (opts.user ? {preview:true} : {})});
|
|
416
416
|
// parseMarkdown's `tags` are CACHE tags (`block:x`, `collection:y`), not
|
|
417
417
|
// the page's editorial tags. Spreading them last used to shadow the
|
|
418
418
|
// frontmatter's `tags`, so the editor loaded `block:feedback-card` into
|
|
@@ -420,7 +420,7 @@ async function readPageFile(filePath, opts = {}) {
|
|
|
420
420
|
// cache internals in its Tags column. Keep them apart.
|
|
421
421
|
// perViewer: a row-scoped [view] renders per user - the public route keeps
|
|
422
422
|
// such a page out of the role-keyed response cache for signed-in viewers.
|
|
423
|
-
return { ...data, urlPath, content, html, usedComponents, cacheTags: tags, perViewer };
|
|
423
|
+
return { ...data, urlPath, content, html, usedComponents, cacheTags: tags, perViewer, hasExpiry };
|
|
424
424
|
}
|
|
425
425
|
|
|
426
426
|
/**
|
|
@@ -21,6 +21,7 @@ import {buildTextStyle} from '../../public/js/text-style.mjs';
|
|
|
21
21
|
import {buildScribeStyle} from '../../public/js/scribe-style.mjs';
|
|
22
22
|
import {CHARACTERS, resolveCharacterSize} from '../../public/js/celebrations/character-catalog.mjs';
|
|
23
23
|
import {publicButtonSettings} from '../../public/js/form-submit.mjs';
|
|
24
|
+
import {applyShortcodeExpiry} from './shortcodeExpiry.js';
|
|
24
25
|
|
|
25
26
|
const __dirname_md = path.dirname(fileURLToPath(import.meta.url));
|
|
26
27
|
const BLOCKS_DIR = path.resolve(__dirname_md, '../../content/blocks');
|
|
@@ -776,7 +777,8 @@ function renderCollectionCarousel(entries, opts, ctxOpts = {}) {
|
|
|
776
777
|
return `<div class="dm-collection-display dm-collection-empty"><p>${escapeHtmlText(emptyMsg)}</p></div>`;
|
|
777
778
|
}
|
|
778
779
|
|
|
779
|
-
const passthrough = ['autoplay', 'interval', 'loop', 'animation', 'animation-duration', 'animation-easing'
|
|
780
|
+
const passthrough = ['autoplay', 'interval', 'loop', 'animation', 'animation-duration', 'animation-easing', 'arrow-position',
|
|
781
|
+
'background', 'arrow-background', 'arrow-color']
|
|
780
782
|
.map(k => dataAttrs[k] ? ` data-${k}="${escapeAttr(String(dataAttrs[k]))}"` : '')
|
|
781
783
|
.join('');
|
|
782
784
|
|
|
@@ -3156,7 +3158,11 @@ function processCarouselBlocks(markdown) {
|
|
|
3156
3158
|
attrs.loop ? ` data-loop="${escapeAttr(attrs.loop)}"` : '',
|
|
3157
3159
|
attrs.animation ? ` data-animation="${escapeAttr(attrs.animation)}"` : '',
|
|
3158
3160
|
attrs['animation-duration'] ? ` data-animation-duration="${escapeAttr(attrs['animation-duration'])}"` : '',
|
|
3159
|
-
attrs['animation-easing'] ? ` data-animation-easing="${escapeAttr(attrs['animation-easing'])}"` : ''
|
|
3161
|
+
attrs['animation-easing'] ? ` data-animation-easing="${escapeAttr(attrs['animation-easing'])}"` : '',
|
|
3162
|
+
['top', 'middle', 'bottom'].includes(attrs['arrow-position']) ? ` data-arrow-position="${escapeAttr(attrs['arrow-position'])}"` : '',
|
|
3163
|
+
attrs.background ? ` data-background="${escapeAttr(attrs.background)}"` : '',
|
|
3164
|
+
attrs['arrow-background'] ? ` data-arrow-background="${escapeAttr(attrs['arrow-background'])}"` : '',
|
|
3165
|
+
attrs['arrow-color'] ? ` data-arrow-color="${escapeAttr(attrs['arrow-color'])}"` : ''
|
|
3160
3166
|
].join('');
|
|
3161
3167
|
const idAttr = attrs.id ? ` id="${escapeAttr(attrs.id)}"` : '';
|
|
3162
3168
|
|
|
@@ -4553,10 +4559,14 @@ export async function parseMarkdown(raw, opts = {}) {
|
|
|
4553
4559
|
const uncommented = comments.restore(comments.scrubbed
|
|
4554
4560
|
.replace(/<!--[\s\S]*?(?:-->|$)/g, '')
|
|
4555
4561
|
.replace(/^[ ]{0,3}\/\/[^\r\n]*/gm, ''));
|
|
4556
|
-
const
|
|
4562
|
+
const expirySafe = scrubCodeRegions(uncommented, true);
|
|
4563
|
+
const expiry = await applyShortcodeExpiry(expirySafe.scrubbed, {
|
|
4564
|
+
...(opts.expiry || {}), urlPath: opts.urlPath || '/', parseAttrs: parseShortcodeAttrs
|
|
4565
|
+
});
|
|
4566
|
+
const preprocessed = applyTransforms('markdown:beforeParse', expirySafe.restore(expiry.markdown));
|
|
4557
4567
|
const {output: withComponents, used: usedComponents} = collectAndRewriteComponents(preprocessed);
|
|
4558
4568
|
const tagSet = new Set();
|
|
4559
|
-
const flags = {perViewer: false};
|
|
4569
|
+
const flags = {perViewer: false, hasExpiry:expiry.hasExpiry};
|
|
4560
4570
|
const withCollection = await processCollectionBlocks(withComponents, tagSet);
|
|
4561
4571
|
const withView = await processViewBlocks(withCollection, tagSet, opts.user || null, flags);
|
|
4562
4572
|
const withStaticBlock = await processStaticBlocks(withView, tagSet);
|
|
@@ -4670,7 +4680,7 @@ export async function parseMarkdown(raw, opts = {}) {
|
|
|
4670
4680
|
const allowed = new Set(_dmTagAllowlist.map(t => t.replace(/^dm-/, '')));
|
|
4671
4681
|
const filteredUsed = [...usedComponents].filter(name => allowed.has(name));
|
|
4672
4682
|
|
|
4673
|
-
return {data, content, html, usedComponents: filteredUsed, tags: [...tagSet], perViewer: flags.perViewer};
|
|
4683
|
+
return {data, content, html, usedComponents: filteredUsed, tags: [...tagSet], perViewer: flags.perViewer, hasExpiry:flags.hasExpiry};
|
|
4674
4684
|
}
|
|
4675
4685
|
|
|
4676
4686
|
/**
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The site's outreach unsubscribe secret - the key every emailed unsubscribe
|
|
3
|
+
* link is signed with (plugins/_lib/outreach/unsubscribe.js).
|
|
4
|
+
*
|
|
5
|
+
* It belongs to the engine, not to the Tool that sends, so the links in emails
|
|
6
|
+
* already sent keep verifying after that Tool's licence lapses or it is
|
|
7
|
+
* removed: UK marketing email (PECR) needs the opt-out to keep working.
|
|
8
|
+
* Kept in `config/outreach.json`.
|
|
9
|
+
*
|
|
10
|
+
* Before the engine owned it a sending Tool kept it in its own
|
|
11
|
+
* config/plugins.json entry as `unsubscribeSecret`. The first read adopts such
|
|
12
|
+
* a secret (any entry - core does not name Tools), so links signed with it
|
|
13
|
+
* still verify; only a site that never had one gets a fresh secret.
|
|
14
|
+
*
|
|
15
|
+
* @module services/outreachUnsubscribe
|
|
16
|
+
*/
|
|
17
|
+
import {getConfig, saveConfig} from '../config.js';
|
|
18
|
+
import {mintSecret} from '../../plugins/_lib/outreach/unsubscribe.js';
|
|
19
|
+
import {getPluginStates} from './plugins.js';
|
|
20
|
+
|
|
21
|
+
let cached = null;
|
|
22
|
+
|
|
23
|
+
/** A secret a Tool left in config/plugins.json before the engine kept one, or null. */
|
|
24
|
+
export function legacySecret(states = getPluginStates()) {
|
|
25
|
+
for (const state of Object.values(states || {})) {
|
|
26
|
+
const s = state && typeof state === 'object' ? state.unsubscribeSecret : null;
|
|
27
|
+
if (typeof s === 'string' && s.length >= 32) return s;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The secret, minted (or adopted) and stored on first use.
|
|
34
|
+
*
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
export function getUnsubscribeSecret() {
|
|
38
|
+
if (cached) return cached;
|
|
39
|
+
let stored = null;
|
|
40
|
+
try { stored = getConfig('outreach'); } catch { /* no file yet */ }
|
|
41
|
+
if (typeof stored?.unsubscribeSecret === 'string' && stored.unsubscribeSecret) {
|
|
42
|
+
cached = stored.unsubscribeSecret;
|
|
43
|
+
return cached;
|
|
44
|
+
}
|
|
45
|
+
const secret = legacySecret() || mintSecret();
|
|
46
|
+
saveConfig('outreach', {...(stored || {}), unsubscribeSecret: secret});
|
|
47
|
+
cached = secret;
|
|
48
|
+
return cached;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Tests only: forget the cached secret. */
|
|
52
|
+
export function _resetUnsubscribeSecretForTest() {
|
|
53
|
+
cached = null;
|
|
54
|
+
}
|