domma-cms 1.8.6 → 1.8.7
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/CHANGELOG.md +6 -0
- package/CLAUDE.md +13 -1
- package/admin/js/lib/menu-editor-arrange.js +2 -2
- package/admin/js/lib/shortcode-registry.js +2 -2
- package/admin/js/lib/wordpress-import-arrange.js +1 -1
- package/admin/js/templates/docs/usage-editions.html +0 -1
- package/admin/js/templates/menu-editor.html +6 -0
- package/admin/js/templates/wordpress-import.html +23 -1
- package/admin/js/views/menu-editor.js +5 -5
- package/admin/js/views/wordpress-import.js +30 -7
- package/config/cache.json.example +0 -4
- package/docs/DommaFeatures.md +1 -1
- package/docs/cache.md +3 -3
- package/docs/editions-and-licences.md +0 -3
- package/docs/plugin-development.md +1 -1
- package/package.json +1 -1
- package/public/css/site.css +1 -1
- package/public/js/mobile-ui.mjs +1 -0
- package/public/js/site.js +1 -1
- package/server/routes/api/wordpress-import.js +81 -4
- package/server/server.js +1 -6
- package/server/services/cache/index.js +3 -11
- package/server/services/markdown.js +2 -1
- package/server/services/menus.js +21 -0
- package/server/services/passwordReset.js +71 -3
- package/server/services/renderer.js +2 -0
- package/server/services/wpImport/index.js +183 -20
- package/server/services/wpImport/people.js +202 -0
- package/server/services/wpImport/plan.js +13 -2
- package/server/services/wpImport/wxr.js +5 -2
- package/server/templates/page.html +2 -2
package/public/js/site.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{installSmoothCarets as oe}from"/public/js/smooth-caret.js";import{decodeBase64Json as P}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as J,applyColour as X,applyPill as ne,applyFloat as ae,applyHighlight as R,applyItemHighlight as se,itemHighlightAttrs as ie,isCurrentItem as U,menuAlign as re,applyClasses as O,cssClasses as W,applyEntrance as V}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{initFloatingMenus as ce}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as G,markInvalidFields as K,showValidationPopup as Q}from"/public/js/forms.js?v=20261015-smooth-caret";import{buildWizardSteps as le}from"/public/js/form-steps.mjs";import{applyFormClasses as Z,applySubmitButtons as de,applyWizardButtons as me}from"/public/js/form-submit.mjs";document.addEventListener("click",h=>{const c=h.target?.closest?.(".dm-banner__dismiss");c&&(h.preventDefault(),c.closest(".dm-banner")?.remove())}),$(()=>{oe(document);const h=window.__CMS_NAV__||{},c=window.__CMS_SITE__||{};if(c.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 d of[...document.body.classList])d.startsWith("dm-custom-")&&document.body.classList.remove(d);a.custom&&document.body.classList.add(a.custom)};const n=c.autoTheme;t(s()),setInterval(()=>t(s()),6e4)}const x=$("#site-navbar");if(x.length&&h.brand){let s=function(u,p=0){return Array.isArray(u)?u.filter(m=>!(m&&(m.type==="spacer"||m.type==="theme-switcher"))).filter(m=>!(m&&m.type==="separator"&&p===0)).map(m=>m&&m.type==="separator"?{divider:!0}:Array.isArray(m?.items)?{...m,items:s(m.items,p+1)}:m):u};const n={...h.brand},o=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let u="";n.logo?u+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(u+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(u+=`<span class="navbar-brand-text${o}">${n.text}</span>`),n.tagline&&(u+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=u}else o&&n.text&&(n.html=`<span class="navbar-brand-text${o}">${n.text}</span>`);const t=s(h.items||[]),e=h.position==="floating",a=h.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:t,variant:h.variant||"dark",position:e?"static":h.position||"sticky",collapsible:!0,appearOnHover:!!h.appearOnHover}),Domma.icons.scan("#site-navbar");const d=x.get(0),f=()=>{if(!a)return;const p=d.querySelector('a[aria-current="page"]')?.closest(".navbar-dropdown");p&&!p.classList.contains("open")&&p.querySelector(":scope > .navbar-dropdown-toggle")?.click()};d.classList.add(`dm-nav-align-${re(h)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",h.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const i=h.dock||{};a&&!e&&ue(d,h.side==="right"),!a&&i.hideOnScroll&&pe(d),R(d,h),V(d,h),O(d,h.class),e&&(ae(x.get(0),h),ce(x.get(0).parentNode||document));const l=d.querySelector(".navbar-toggle"),g=d.querySelector(".navbar-collapse");if(l&&g){let u=null;const p=()=>{g.classList.contains("show")&&l.click()},m=()=>{u&&(u.remove(),u=null),document.body.classList.remove("dm-drawer-open")},v=()=>{u||(u=document.createElement("div"),u.className="dm-navbar-backdrop",u.addEventListener("click",p),document.body.appendChild(u),document.body.classList.add("dm-drawer-open"))};l.addEventListener("click",()=>{requestAnimationFrame(()=>{g.classList.contains("show")?(f(),v()):m()})}),g.addEventListener("click",C=>{C.target.closest("a")&&requestAnimationFrame(m)}),document.addEventListener("keydown",C=>{C.key==="Escape"&&g.classList.contains("show")&&p()})}const b=d.querySelector(".navbar-nav");if(b){const u=Array.from(b.children);let p=0;for(const m of h.items||[])if(!(m&&m.type==="separator")){if(m&&m.type==="spacer"){const v=document.createElement("li"),C=["xs","sm","md","lg","xl","flex"].includes(m.size)?m.size:"md";v.className=`dm-menu-spacer dm-menu-spacer--${C}`,v.setAttribute("aria-hidden","true"),b.insertBefore(v,u[p]||null);continue}if(m&&m.type==="theme-switcher"){const v=document.createElement("li");v.className="dm-menu-theme-switcher";const C=document.createElement("span");C.dataset.themeSwitcher="menu",C.dataset.position="inline",v.appendChild(C),b.insertBefore(v,u[p]||null);continue}p++}window.CMSThemeSwitcher?.scan(b)}d.querySelectorAll("[data-path]").forEach(u=>{const p=u.dataset.path.split(".").map(Number),m=p.length>1,v=p.reduce((A,q)=>A?.items?.[q],{items:t});if(!v)return;v.colour&&X(u,v.colour),v.pill&&!m&&ne(u,v.pill),se(u,v),O(u.closest("li"),v.class);const C=J(v.badge);C&&u.appendChild(C),U(v.url,location.pathname)&&(u.classList.add("active"),u.setAttribute("aria-current","page"))}),f()}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,"'")},d=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 M=a(r.url||"#"),L=e(r.text||""),D=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(d).join("")}</ul>`:"",N=ie(r),ee=[N.hover&&` data-menu-item-hover="${a(N.hover)}"`,N.active&&` data-menu-item-active="${a(N.active)}"`].filter(Boolean).join(""),te=N.accent?` style="--dm-menu-highlight:${a(N.accent)}"`:"",Y=W(r.class).join(" ");return`<li${Y?` class="${a(Y)}"`:""}><a href="${M}"${ee}${te}>${L}</a>${D}</li>`},f=function(){if(!c.footer)return"";const r=Object.keys(o).filter(L=>n[L]);if(!r.length)return"";const M=W(c.footer.socialClass).join(" ");return`<div class="footer-social">${r.map(L=>{const{label:D,svg:N}=o[L];return`<a href="${n[L]}" target="_blank" rel="noopener noreferrer" aria-label="${D}" class="footer-social-link${M?` ${a(M)}`:""}">${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">${d(r).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const M=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(d).join("")}</ul>`:"",L=g(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>`,D=["cms-footer-col",...W(r.class)].join(" ");return`<li class="${a(D)}">${L}${M}</li>`},p=function(r,M,L=!1){return(r||[]).forEach(D=>{D&&(D.type==="separator"||D.type==="spacer"||D.type==="theme-switcher")||(L&&g(D)||M.push(D),Array.isArray(D.items)&&D.items.length&&p(D.items,M))}),M};const n=c.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(c.footer?.layout)?c.footer.layout:"auto",l=i==="columns"||i==="auto"&&t.primary.some(r=>Array.isArray(r?.items)&&r.items.length),g=r=>l&&!r.url&&Array.isArray(r.items)&&r.items.length>0;if(l){const r=h.brand||{},M=r.text||c.title||"",L=r.logo?`<img src="${a(r.logo)}" class="cms-footer-brand-logo" alt="">`:r.icon?'<span class="cms-footer-brand-icon"></span>':"",D=String(c.footer?.blurb||"").trim();s='<div class="footer-inner footer-inner--columns container"><div class="cms-footer-top">',s+=`<div class="cms-footer-brand">${M||L?`<a class="cms-footer-brand-name" href="${a(r.url||"/")}">${L}<span>${e(M)}</span></a>`:""}${D?`<p class="cms-footer-blurb">${e(D)}</p>`:""}${f()}</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(d).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(d).join("")}</ul></nav>`),t.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(d).join("")}</ul></nav>`),s+=f();s+="</div>",y.html(s),window.CMSThemeSwitcher?.scan(y.get(0));const u=y.get(0).querySelector(".cms-footer-brand-icon");u&&h.brand?.icon&&u.setAttribute("data-icon",h.brand.icon),O(y.get(0),c.footer?.class);const m=[...p(t.primary,[],!0),...p(t.legal,[])];y.get(0).querySelectorAll(".footer-nav a").forEach((r,M)=>{const L=m[M];if(!L)return;L.colour&&X(r,L.colour);const D=J(L.badge);D&&r.appendChild(D),U(L.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}),V(y.get(0).querySelector(".footer-nav--primary"),{entrance:t.primaryEntrance}),V(y.get(0).querySelector(".footer-nav--legal"),{entrance:t.legalEntrance}),O(y.get(0).querySelector(".footer-nav--primary"),t.primaryClass),O(y.get(0).querySelector(".footer-nav--legal"),t.legalClass);const C=c.footer?.showMotionToggle!==!1,A=S.get("reduced_motion"),q=c.footer?.motionDefault,z=A!=null?!!A:q==="on"?!0:q==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),B=c.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 M=document.createElement("span");M.className=B?"footer-motion-label":"form-switch-label",M.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(M),I.appendChild(L)}}const k=document.getElementById("site-sidebar");k&&he(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=f=>{const i=String(f||"").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 d={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide",heightMode:this.dataset.heightMode==="average"?"average":"crop"};if(this.dataset.height){const f=Number(this.dataset.height);Number.isFinite(f)&&f>0&&(d.height=f)}if(this.dataset.animationDuration){const f=parseInt(this.dataset.animationDuration,10);Number.isFinite(f)&&(d.animationDuration=f)}this.dataset.animationEasing&&(d.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(f=>f.cloneNode(!0)),this._dmCarouselOpts=d),this._dmCarousel=Domma.elements.carousel(this,d)}),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 d=this,f=l=>String(l).padStart(2,"0"),i=l=>{const g=Math.max(0,Math.floor(l/1e3)),b=d.querySelector(".dm-timer-time");b&&(b.textContent=`${f(Math.floor(g/86400))}:${f(Math.floor(g%86400/3600))}:${f(Math.floor(g%3600/60))}:${f(g%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 d=n[a];if(!d)return;const f=e.querySelector(".dm-progression-title");if(d.icon&&f){const l=document.createElement("span");l.dataset.icon=d.icon,l.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",l.setAttribute("aria-hidden","true"),f.prepend(l)}const i=t[d.status];if(i&&f){const l=document.createElement("span");l.className=`badge badge-${i.tone} cms-timeline-status`,l.style.cssText="display:inline-flex;align-items:center;gap:.35em;margin:.4rem 0";const g=document.createElement("span");g.dataset.icon=i.icon,g.dataset.iconSize="14",g.setAttribute("aria-hidden","true"),l.append(g,document.createTextNode(i.label)),f.after(l)}})}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,d=n.dataset.revealDirection==="rtl",f=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)"};f.forEach((l,g)=>{l.style.opacity="0",l.style.transform=i[s]||"",l.style.transition=`opacity ${t}ms ease, transform ${t}ms ease`;const b=d?f.length-1-g:g;l.style.transitionDelay=o==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const l=new IntersectionObserver(g=>{g.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",l.unobserve(b.target))})},{threshold:.1});f.forEach(g=>l.observe(g))}))}),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:d,alert:f,prompt:i,...l}=e||{};a||d||f||i?($(s).on("click",async function(b){if(b.preventDefault(),a&&!await E.confirm(a))return;let u=null;if(!(i&&(u=await E.prompt(i,{inputPlaceholder:l.promptPlaceholder||"",inputValue:l.promptDefault||""}),u===null))){if(l.target){const p=$(l.target);l.toggleClass&&p.toggleClass(l.toggleClass),l.addClass&&p.addClass(l.addClass),l.removeClass&&p.removeClass(l.removeClass),u!==null&&(l.setText&&p.text(u),l.setVal&&p.val(u),l.setAttr&&p.attr(l.setAttr,u))}l.href&&(window.location.href=l.href),d&&E.toast(d,{type:l.toastType||"success"}),f&&E.alert(f)}}),Object.keys(l).length&&(o[s]={...t,events:{...t.events,click:l}})):o[s]=t}$.setup(o)}}w.length&&j(w.get(0))}),window.wireCTAButtons=j;function j(h){h.querySelectorAll(".dm-cta-trigger").forEach(c=>{c.__ctaWired||(c.__ctaWired=!0,c.addEventListener("click",async()=>{const x=c.dataset.action,y=c.dataset.entry,k=c.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(c.childNodes).map(s=>s.cloneNode(!0));c.disabled=!0,c.textContent="Running\u2026";const o=s=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(c.dataset.scope?{entryId:y,scope:c.dataset.scope}:{entryId:y})});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:d}=await a.json();S.set("auth_token",d),w=d,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{c.disabled=!1,c.textContent="",n.forEach(s=>c.appendChild(s)),Domma.icons.scan(c)}}))})}(function(){const c=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!c.length)return;const x=S.get("auth_token");function y(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)}c.forEach(async w=>{const n=w.dataset.collectionAttrs;if(!n)return;if(!x){w.replaceChildren(y("",!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 d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(d.ok){const{token:f}=await d.json();S.set("auth_token",f),s=f,t=await o(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();k(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 c=document.querySelectorAll("[data-collection-table]");!c.length||typeof T>"u"||!T.create||c.forEach(x=>{let y;try{y=P(x.dataset.payload)}catch{return}const{columns:k,rows:w,search:n,sortable:o,exportable:s,pageSize:t,empty:e,ctaConfig:a,slug:d,exportCfg:f}=y;if(!k?.length)return;if(a){const g=k.findIndex(b=>b.key==="_cta");g!==-1&&(k[g]={key:"_cta",title:"",render:(b,u)=>{const p=document.createElement("button");if(p.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,p.dataset.action=a.action||"",a.scope&&(p.dataset.scope=a.scope),p.dataset.entry=u._entryId||"",a.confirm&&(p.dataset.confirm=a.confirm),a.icon){const m=document.createElement("span");m.dataset.icon=a.icon,p.appendChild(m),p.appendChild(document.createTextNode(" "))}return p.appendChild(document.createTextNode(a.label||"Run")),p}})}const i="col-table-"+Math.random().toString(36).slice(2,7),l=document.createElement("div");l.id=i,x.replaceChildren(l),T.create("#"+i,{data:w,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&j(l),x._dmTable={columns:k,rows:w,slug:d,exportCfg:f,render(g){T.create("#"+i,{data:g,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&j(l)}}})})(),(function(){const c=document.querySelectorAll("[data-form-inline]");if(!c.length||typeof F>"u")return;function x(o){const s=o&&o.logic;return!!(s&&(s.visibility||s.requirement))}function y(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)),d=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,f={...e.formConfig||{}};f.span==="full"&&s&&(f.span=s);const i={type:d,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},...f}};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;"}c.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,d=e.layout||"grid",f=t.some(p=>p.type==="page-break"),i=async p=>{if(o.dataset.triggerBlocked==="true"){const m=o._formLogicRuntime,v=m&&m.submitBlock?m.submitBlock().reasons:[];n(o,v.join(" ")||"This form cannot be submitted yet.",!0);const C=new Error("Blocked by a form trigger");throw C.formSubmitFailed=!0,C}try{const m=o.querySelector(".fb-form-honeypot"),v=m?.querySelector('input[type="text"]')||null,C=m?.querySelector('[name="_t"]')||null,A=Object.assign({},p);delete A._t,m?.dataset.hpName&&delete A[m.dataset.hpName],v!==null&&(A._hp=v.value),C!==null&&(A._t=C.value);const q=await H.post(`/api/forms/submit/${s.slug}`,A);if(q&&q.error){n(o,q.error,!0);const z=new Error(q.error);throw z.formSubmitFailed=!0,z}if(q?.redirect){window.location.href=q.redirect;return}for(;o.firstChild;)o.removeChild(o.firstChild);n(o,q?.message||e.successMessage||"Thank you for your submission.",!1)}catch(m){throw m.formSubmitFailed||n(o,m.message||"Submission failed. Please try again.",!0),m}},l=Date.now();function g(p){const m=p.querySelector("form");if(!m||m.querySelector(".fb-form-honeypot"))return;const v=document.createElement("div");v.className="fb-form-honeypot",v.setAttribute("aria-hidden","true");const C=document.createElement("input");C.name=m.querySelector('[name="website"]')?"_website":"website",v.dataset.hpName=C.name,C.type="text",C.tabIndex=-1,C.autocomplete="url",C.placeholder="https://",v.appendChild(C);const A=document.createElement("input");A.name="_t",A.type="hidden",A.value=l,v.appendChild(A),m.appendChild(v)}function b(p){if(!window.FormLogicEngine||!t.some(v=>v.logic||Array.isArray(v.triggers)&&v.triggers.length))return null;const m=new window.FormLogicEngine.FormLogicRuntime(s,o,p?{baseValues:p}:void 0);if(m.init(),o._formLogicRuntime=m,o._formLogicObserved)return m;if(o._formLogicObserved=!0,o.parentNode&&typeof MutationObserver<"u"){const v=new MutationObserver(function(C){for(const A of C)for(const q of A.removedNodes)if(q===o||q.nodeType===1&&q.contains&&q.contains(o)){o._formLogicRuntime&&o._formLogicRuntime.destroy(),v.disconnect();return}});v.observe(o.parentNode,{childList:!0,subtree:!1}),o._formLogicObserver=v}return m}let u=!1;if(o.addEventListener("dm-form-end",p=>{const m=p.detail?.validation;if(m&&!m.valid){K(o,m.missing),Q(m);return}u||(u=!0,i(p.detail?.values||{}).catch(()=>{}).finally(()=>{u=!1}))}),f&&F.wizard){const p=le(t,s.title),m=p.map(I=>({title:I.title,description:I.description,fields:y(I.fields,a)})),v=p.map(I=>I.fields);let C=0,A=null,q=null;const z=I=>{const r=window.FormLogicEngine;if(!r||!r.validateFields)return;const M=r.validateFields(o,v[C]||[]);M.valid||(I.preventDefault(),I.stopImmediatePropagation(),K(o,M.missing),Q(M))},B=()=>{const I=v[C]||t;me(o,e),Z(o,e,I),w(o,I),G(o,I),e.honeypot!==!1&&g(o),A&&A.destroy(),A=b(q?.getStepData?q.getStepData():void 0),o.querySelectorAll(".wizard-next, .wizard-submit").forEach(r=>r.addEventListener("click",z,!0))};q=F.wizard(o,{schema:{steps:m},layout:d,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"||!q?.goToStep)return;const r=Number(I.detail.action.step);!Number.isFinite(r)||r<0||r>=m.length||r!==C&&q.goToStep(r)}),Promise.resolve(q).then(B)}else if(F.render){const p=F.render(o,y(t,a),k(t),{submitText:e.submitText||"Submit",layout:d,columns:a,onSubmit:i});Promise.resolve(p).then(function(){de(o,e,d),Z(o,e,t),w(o,t),G(o,t),e.honeypot!==!1&&g(o),b()})}})})();function ue(h,c){const x=document.body,y=x.classList,k=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),w=()=>window.matchMedia("(min-width: 993px)").matches;h.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const d=a.textContent.trim();d&&(a.setAttribute("title",d),a.querySelector("svg, [data-icon]")||(a.dataset.initial=d.charAt(0).toUpperCase()))});const n=document.createElement("button");n.type="button",n.className="dm-nav-collapse-toggle";const o=()=>{const a=y.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 d=c?"chevrons-left":"chevrons-right",f=c?"chevrons-right":"chevrons-left";n.innerHTML=`<span data-icon="${a?d:f}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(n)},s=a=>{y.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),o()};n.addEventListener("click",()=>s(!y.contains("dm-vnav-collapsed"))),o();const t=h.querySelector(".navbar-container")||h;t.insertBefore(n,t.firstChild?.nextSibling||null);const e=a=>{y.toggle("dm-vnav-autohide",a),y.toggle("dm-vnav-collapsible",!a&&k.has("collapsible")),a?y.remove("dm-vnav-collapsed"):k.has("collapsible")&&S.get("nav_collapsed")===!0&&y.add("dm-vnav-collapsed"),h.classList.remove("dm-nav-revealed"),o()};h.addEventListener("pointerdown",a=>{y.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!h.classList.contains("dm-nav-revealed")&&(h.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{h.contains(a.target)||h.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(h,{items:()=>{const a=y.contains("dm-vnav-autohide"),d=y.contains("dm-vnav-collapsed"),f=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:d?"Expand menu":"Collapse to icons",icon:d?"maximize":"minimize",disabled:!w()||a||!k.has("collapsible"),action:()=>s(!d)},{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:!f,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(k.has("autohide")),y.toggle("dm-vnav-collapsed",!k.has("autohide")&&k.has("start-collapsed")),o()}}]}})}function pe(h){h.classList.add("dm-nav-scroll-hide");let c=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const y=window.scrollY,k=h.matches(":focus-within")||h.querySelector(".navbar-dropdown.open, .navbar-collapse.show");y>c&&y>h.offsetHeight&&!k?h.classList.add("dm-nav-scrolled-away"):y<c&&h.classList.remove("dm-nav-scrolled-away"),c=y}))},{passive:!0})}function he(h){let c={};try{c=JSON.parse(h.dataset.contents||"{}")}catch{}const x=h.closest(".site-main"),y=document.querySelector(".site-content"),k=new Set(String(c.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),w=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=i=>(y?[...y.querySelectorAll(i)]:[]).filter(w).filter(l=>!k.has(l.textContent.trim().toLowerCase())),o=c.headings==="h2"?n("h2"):c.headings==="h3"?n("h3"):c.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",c.title||"Page contents"),c.hover&&(t.dataset.menuHover=c.hover),c.selected&&(t.dataset.menuActive=c.selected),c.accent&&t.style.setProperty("--dm-menu-highlight",c.accent.startsWith("#")?c.accent:`var(--dm-${c.accent})`),c.font&&(t.style.fontFamily=c.font),c.fontSize&&(t.style.fontSize=c.fontSize),c.fontWeight&&(t.style.fontWeight=c.fontWeight);const e=document.createElement("div");e.className="dm-contents-head";const a=document.createElement("p");a.className="dm-contents-title",a.textContent=c.title||"",e.appendChild(a),t.appendChild(e);const d=document.createElement("ol");d.className="dm-contents-list"+(c.numbered?" is-numbered":"");const f=[];for(const i of o){if(!i.id){const b=(i.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let u=b;for(let p=2;s.has(u);p++)u=`${b}-${p}`;s.add(u),i.id=u}const l=document.createElement("li");l.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const g=document.createElement("a");g.href=`#${i.id}`,g.textContent=i.textContent.trim(),l.appendChild(g),d.appendChild(l),f.push([i,g])}if(t.appendChild(d),h.replaceChildren(t),c.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,l=()=>{const b=f.find(([u])=>i.get(u))||null;f.forEach(([,u])=>{const p=b&&u===b[1];u.classList.toggle("is-active",!!p),p?u.setAttribute("aria-current","location"):u.removeAttribute("aria-current")})},g=new IntersectionObserver(b=>{b.forEach(u=>i.set(u.target,u.isIntersecting)),l()},{rootMargin:"-80px 0px -60% 0px"});f.forEach(([b])=>g.observe(b))}if(x?.classList.contains("contents-collapsible")){const i=document.createElement("button");i.type="button",i.className="dm-contents-toggle";const l=()=>{const g=x.classList.contains("contents-collapsed");i.setAttribute("aria-expanded",String(!g)),i.setAttribute("aria-label",g?"Show the contents":"Hide the contents"),i.title=i.getAttribute("aria-label"),i.innerHTML=`<span data-icon="${g?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(i)};i.addEventListener("click",()=>{S.set("contents_collapsed",x.classList.toggle("contents-collapsed")),l()}),l(),e.insertBefore(i,e.firstChild)}if(x?.classList.contains("contents-floating")&&c.movable){t.classList.add("is-movable");const i=S.get("contents_position");i&&Number.isFinite(i.x)&&Object.assign(h.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",l=>{if(l.target.closest("button"))return;const g=h.getBoundingClientRect(),b=l.clientX-g.left,u=l.clientY-g.top,p=v=>{const C=Math.max(0,Math.min(window.innerWidth-g.width,v.clientX-b)),A=Math.max(0,Math.min(window.innerHeight-40,v.clientY-u));Object.assign(h.style,{left:`${C}px`,top:`${A}px`,right:"auto",bottom:"auto"})},m=()=>{document.removeEventListener("pointermove",p),S.set("contents_position",{x:parseInt(h.style.left,10),y:parseInt(h.style.top,10)})};document.addEventListener("pointermove",p),document.addEventListener("pointerup",m,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(h,{items:()=>{const i=x?.classList.contains("contents-collapsible"),l=x?.classList.contains("contents-collapsed"),g=f.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:l?"Show the contents":"Collapse to a button",icon:l?"list":"minimize",action:()=>h.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!g,action:async()=>{const b=`${location.origin}${location.pathname}#${g[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")&&(h.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!h.classList.contains("is-revealed")&&(h.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{h.contains(i.target)||h.classList.remove("is-revealed")}))}
|
|
1
|
+
import{applyMobileMenu as oe,initCarouselPopup as ne}from"/public/js/mobile-ui.mjs?v=20261016-mobile-options";import{installSmoothCarets as ae}from"/public/js/smooth-caret.js";import{decodeBase64Json as P}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as J,applyColour as X,applyPill as se,applyFloat as ie,applyHighlight as R,applyItemHighlight as re,itemHighlightAttrs as ce,isCurrentItem as U,menuAlign as le,applyClasses as O,cssClasses as W,applyEntrance as V}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{initFloatingMenus as de}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as G,markInvalidFields as K,showValidationPopup as Q}from"/public/js/forms.js?v=20261015-smooth-caret";import{buildWizardSteps as me}from"/public/js/form-steps.mjs";import{applyFormClasses as Z,applySubmitButtons as ue,applyWizardButtons as pe}from"/public/js/form-submit.mjs";document.addEventListener("click",h=>{const c=h.target?.closest?.(".dm-banner__dismiss");c&&(h.preventDefault(),c.closest(".dm-banner")?.remove())}),$(()=>{ae(document);const h=window.__CMS_NAV__||{},c=window.__CMS_SITE__||{};if(c.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 d of[...document.body.classList])d.startsWith("dm-custom-")&&document.body.classList.remove(d);a.custom&&document.body.classList.add(a.custom)};const n=c.autoTheme;t(s()),setInterval(()=>t(s()),6e4)}const x=$("#site-navbar");if(x.length&&h.brand){let s=function(u,p=0){return Array.isArray(u)?u.filter(m=>!(m&&(m.type==="spacer"||m.type==="theme-switcher"))).filter(m=>!(m&&m.type==="separator"&&p===0)).map(m=>m&&m.type==="separator"?{divider:!0}:Array.isArray(m?.items)?{...m,items:s(m.items,p+1)}:m):u};const n={...h.brand},o=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let u="";n.logo?u+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(u+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(u+=`<span class="navbar-brand-text${o}">${n.text}</span>`),n.tagline&&(u+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=u}else o&&n.text&&(n.html=`<span class="navbar-brand-text${o}">${n.text}</span>`);const t=s(h.items||[]),e=h.position==="floating",a=h.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:t,variant:h.variant||"dark",position:e?"static":h.position||"sticky",collapsible:!0,appearOnHover:!!h.appearOnHover}),Domma.icons.scan("#site-navbar");const d=x.get(0);oe(d,h.mobile);const f=()=>{if(!a)return;const p=d.querySelector('a[aria-current="page"]')?.closest(".navbar-dropdown");p&&!p.classList.contains("open")&&p.querySelector(":scope > .navbar-dropdown-toggle")?.click()};d.classList.add(`dm-nav-align-${le(h)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",h.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const i=h.dock||{};a&&!e&&he(d,h.side==="right"),!a&&i.hideOnScroll&&fe(d),R(d,h),V(d,h),O(d,h.class),e&&(ie(x.get(0),h),de(x.get(0).parentNode||document));const l=d.querySelector(".navbar-toggle"),g=d.querySelector(".navbar-collapse");if(l&&g){let u=null;const p=()=>{g.classList.contains("show")&&l.click()},m=()=>{u&&(u.remove(),u=null),document.body.classList.remove("dm-drawer-open")},v=()=>{u||(u=document.createElement("div"),u.className="dm-navbar-backdrop",u.addEventListener("click",p),document.body.appendChild(u),document.body.classList.add("dm-drawer-open"))};l.addEventListener("click",()=>{requestAnimationFrame(()=>{g.classList.contains("show")?(f(),v()):m()})}),g.addEventListener("click",C=>{C.target.closest("a")&&requestAnimationFrame(m)}),document.addEventListener("keydown",C=>{C.key==="Escape"&&g.classList.contains("show")&&p()})}const b=d.querySelector(".navbar-nav");if(b){const u=Array.from(b.children);let p=0;for(const m of h.items||[])if(!(m&&m.type==="separator")){if(m&&m.type==="spacer"){const v=document.createElement("li"),C=["xs","sm","md","lg","xl","flex"].includes(m.size)?m.size:"md";v.className=`dm-menu-spacer dm-menu-spacer--${C}`,v.setAttribute("aria-hidden","true"),b.insertBefore(v,u[p]||null);continue}if(m&&m.type==="theme-switcher"){const v=document.createElement("li");v.className="dm-menu-theme-switcher";const C=document.createElement("span");C.dataset.themeSwitcher="menu",C.dataset.position="inline",v.appendChild(C),b.insertBefore(v,u[p]||null);continue}p++}window.CMSThemeSwitcher?.scan(b)}d.querySelectorAll("[data-path]").forEach(u=>{const p=u.dataset.path.split(".").map(Number),m=p.length>1,v=p.reduce((A,M)=>A?.items?.[M],{items:t});if(!v)return;v.colour&&X(u,v.colour),v.pill&&!m&&se(u,v.pill),re(u,v),O(u.closest("li"),v.class);const C=J(v.badge);C&&u.appendChild(C),U(v.url,location.pathname)&&(u.classList.add("active"),u.setAttribute("aria-current","page"))}),f()}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,"'")},d=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 q=a(r.url||"#"),L=e(r.text||""),D=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(d).join("")}</ul>`:"",N=ce(r),ee=[N.hover&&` data-menu-item-hover="${a(N.hover)}"`,N.active&&` data-menu-item-active="${a(N.active)}"`].filter(Boolean).join(""),te=N.accent?` style="--dm-menu-highlight:${a(N.accent)}"`:"",Y=W(r.class).join(" ");return`<li${Y?` class="${a(Y)}"`:""}><a href="${q}"${ee}${te}>${L}</a>${D}</li>`},f=function(){if(!c.footer)return"";const r=Object.keys(o).filter(L=>n[L]);if(!r.length)return"";const q=W(c.footer.socialClass).join(" ");return`<div class="footer-social">${r.map(L=>{const{label:D,svg:N}=o[L];return`<a href="${n[L]}" target="_blank" rel="noopener noreferrer" aria-label="${D}" class="footer-social-link${q?` ${a(q)}`:""}">${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">${d(r).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const q=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(d).join("")}</ul>`:"",L=g(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>`,D=["cms-footer-col",...W(r.class)].join(" ");return`<li class="${a(D)}">${L}${q}</li>`},p=function(r,q,L=!1){return(r||[]).forEach(D=>{D&&(D.type==="separator"||D.type==="spacer"||D.type==="theme-switcher")||(L&&g(D)||q.push(D),Array.isArray(D.items)&&D.items.length&&p(D.items,q))}),q};const n=c.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(c.footer?.layout)?c.footer.layout:"auto",l=i==="columns"||i==="auto"&&t.primary.some(r=>Array.isArray(r?.items)&&r.items.length),g=r=>l&&!r.url&&Array.isArray(r.items)&&r.items.length>0;if(l){const r=h.brand||{},q=r.text||c.title||"",L=r.logo?`<img src="${a(r.logo)}" class="cms-footer-brand-logo" alt="">`:r.icon?'<span class="cms-footer-brand-icon"></span>':"",D=String(c.footer?.blurb||"").trim();s='<div class="footer-inner footer-inner--columns container"><div class="cms-footer-top">',s+=`<div class="cms-footer-brand">${q||L?`<a class="cms-footer-brand-name" href="${a(r.url||"/")}">${L}<span>${e(q)}</span></a>`:""}${D?`<p class="cms-footer-blurb">${e(D)}</p>`:""}${f()}</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(d).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(d).join("")}</ul></nav>`),t.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(d).join("")}</ul></nav>`),s+=f();s+="</div>",y.html(s),window.CMSThemeSwitcher?.scan(y.get(0));const u=y.get(0).querySelector(".cms-footer-brand-icon");u&&h.brand?.icon&&u.setAttribute("data-icon",h.brand.icon),O(y.get(0),c.footer?.class);const m=[...p(t.primary,[],!0),...p(t.legal,[])];y.get(0).querySelectorAll(".footer-nav a").forEach((r,q)=>{const L=m[q];if(!L)return;L.colour&&X(r,L.colour);const D=J(L.badge);D&&r.appendChild(D),U(L.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}),V(y.get(0).querySelector(".footer-nav--primary"),{entrance:t.primaryEntrance}),V(y.get(0).querySelector(".footer-nav--legal"),{entrance:t.legalEntrance}),O(y.get(0).querySelector(".footer-nav--primary"),t.primaryClass),O(y.get(0).querySelector(".footer-nav--legal"),t.legalClass);const C=c.footer?.showMotionToggle!==!1,A=S.get("reduced_motion"),M=c.footer?.motionDefault,z=A!=null?!!A:M==="on"?!0:M==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),B=c.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 q=document.createElement("span");q.className=B?"footer-motion-label":"form-switch-label",q.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(q),I.appendChild(L)}}const k=document.getElementById("site-sidebar");k&&ge(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=f=>{const i=String(f||"").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 d={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide",heightMode:this.dataset.heightMode==="average"?"average":"crop"};if(this.dataset.height){const f=Number(this.dataset.height);Number.isFinite(f)&&f>0&&(d.height=f)}if(this.dataset.animationDuration){const f=parseInt(this.dataset.animationDuration,10);Number.isFinite(f)&&(d.animationDuration=f)}this.dataset.animationEasing&&(d.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(f=>f.cloneNode(!0)),this._dmCarouselOpts=d),this._dmCarousel=Domma.elements.carousel(this,d),ne(this)}),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 d=this,f=l=>String(l).padStart(2,"0"),i=l=>{const g=Math.max(0,Math.floor(l/1e3)),b=d.querySelector(".dm-timer-time");b&&(b.textContent=`${f(Math.floor(g/86400))}:${f(Math.floor(g%86400/3600))}:${f(Math.floor(g%3600/60))}:${f(g%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 d=n[a];if(!d)return;const f=e.querySelector(".dm-progression-title");if(d.icon&&f){const l=document.createElement("span");l.dataset.icon=d.icon,l.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",l.setAttribute("aria-hidden","true"),f.prepend(l)}const i=t[d.status];if(i&&f){const l=document.createElement("span");l.className=`badge badge-${i.tone} cms-timeline-status`,l.style.cssText="display:inline-flex;align-items:center;gap:.35em;margin:.4rem 0";const g=document.createElement("span");g.dataset.icon=i.icon,g.dataset.iconSize="14",g.setAttribute("aria-hidden","true"),l.append(g,document.createTextNode(i.label)),f.after(l)}})}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,d=n.dataset.revealDirection==="rtl",f=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)"};f.forEach((l,g)=>{l.style.opacity="0",l.style.transform=i[s]||"",l.style.transition=`opacity ${t}ms ease, transform ${t}ms ease`;const b=d?f.length-1-g:g;l.style.transitionDelay=o==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const l=new IntersectionObserver(g=>{g.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",l.unobserve(b.target))})},{threshold:.1});f.forEach(g=>l.observe(g))}))}),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:d,alert:f,prompt:i,...l}=e||{};a||d||f||i?($(s).on("click",async function(b){if(b.preventDefault(),a&&!await E.confirm(a))return;let u=null;if(!(i&&(u=await E.prompt(i,{inputPlaceholder:l.promptPlaceholder||"",inputValue:l.promptDefault||""}),u===null))){if(l.target){const p=$(l.target);l.toggleClass&&p.toggleClass(l.toggleClass),l.addClass&&p.addClass(l.addClass),l.removeClass&&p.removeClass(l.removeClass),u!==null&&(l.setText&&p.text(u),l.setVal&&p.val(u),l.setAttr&&p.attr(l.setAttr,u))}l.href&&(window.location.href=l.href),d&&E.toast(d,{type:l.toastType||"success"}),f&&E.alert(f)}}),Object.keys(l).length&&(o[s]={...t,events:{...t.events,click:l}})):o[s]=t}$.setup(o)}}w.length&&j(w.get(0))}),window.wireCTAButtons=j;function j(h){h.querySelectorAll(".dm-cta-trigger").forEach(c=>{c.__ctaWired||(c.__ctaWired=!0,c.addEventListener("click",async()=>{const x=c.dataset.action,y=c.dataset.entry,k=c.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(c.childNodes).map(s=>s.cloneNode(!0));c.disabled=!0,c.textContent="Running\u2026";const o=s=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(c.dataset.scope?{entryId:y,scope:c.dataset.scope}:{entryId:y})});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:d}=await a.json();S.set("auth_token",d),w=d,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{c.disabled=!1,c.textContent="",n.forEach(s=>c.appendChild(s)),Domma.icons.scan(c)}}))})}(function(){const c=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!c.length)return;const x=S.get("auth_token");function y(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)}c.forEach(async w=>{const n=w.dataset.collectionAttrs;if(!n)return;if(!x){w.replaceChildren(y("",!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 d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(d.ok){const{token:f}=await d.json();S.set("auth_token",f),s=f,t=await o(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();k(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 c=document.querySelectorAll("[data-collection-table]");!c.length||typeof T>"u"||!T.create||c.forEach(x=>{let y;try{y=P(x.dataset.payload)}catch{return}const{columns:k,rows:w,search:n,sortable:o,exportable:s,pageSize:t,empty:e,ctaConfig:a,slug:d,exportCfg:f}=y;if(!k?.length)return;if(a){const g=k.findIndex(b=>b.key==="_cta");g!==-1&&(k[g]={key:"_cta",title:"",render:(b,u)=>{const p=document.createElement("button");if(p.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,p.dataset.action=a.action||"",a.scope&&(p.dataset.scope=a.scope),p.dataset.entry=u._entryId||"",a.confirm&&(p.dataset.confirm=a.confirm),a.icon){const m=document.createElement("span");m.dataset.icon=a.icon,p.appendChild(m),p.appendChild(document.createTextNode(" "))}return p.appendChild(document.createTextNode(a.label||"Run")),p}})}const i="col-table-"+Math.random().toString(36).slice(2,7),l=document.createElement("div");l.id=i,x.replaceChildren(l),T.create("#"+i,{data:w,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&j(l),x._dmTable={columns:k,rows:w,slug:d,exportCfg:f,render(g){T.create("#"+i,{data:g,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&j(l)}}})})(),(function(){const c=document.querySelectorAll("[data-form-inline]");if(!c.length||typeof F>"u")return;function x(o){const s=o&&o.logic;return!!(s&&(s.visibility||s.requirement))}function y(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)),d=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,f={...e.formConfig||{}};f.span==="full"&&s&&(f.span=s);const i={type:d,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},...f}};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;"}c.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,d=e.layout||"grid",f=t.some(p=>p.type==="page-break"),i=async p=>{if(o.dataset.triggerBlocked==="true"){const m=o._formLogicRuntime,v=m&&m.submitBlock?m.submitBlock().reasons:[];n(o,v.join(" ")||"This form cannot be submitted yet.",!0);const C=new Error("Blocked by a form trigger");throw C.formSubmitFailed=!0,C}try{const m=o.querySelector(".fb-form-honeypot"),v=m?.querySelector('input[type="text"]')||null,C=m?.querySelector('[name="_t"]')||null,A=Object.assign({},p);delete A._t,m?.dataset.hpName&&delete A[m.dataset.hpName],v!==null&&(A._hp=v.value),C!==null&&(A._t=C.value);const M=await H.post(`/api/forms/submit/${s.slug}`,A);if(M&&M.error){n(o,M.error,!0);const z=new Error(M.error);throw z.formSubmitFailed=!0,z}if(M?.redirect){window.location.href=M.redirect;return}for(;o.firstChild;)o.removeChild(o.firstChild);n(o,M?.message||e.successMessage||"Thank you for your submission.",!1)}catch(m){throw m.formSubmitFailed||n(o,m.message||"Submission failed. Please try again.",!0),m}},l=Date.now();function g(p){const m=p.querySelector("form");if(!m||m.querySelector(".fb-form-honeypot"))return;const v=document.createElement("div");v.className="fb-form-honeypot",v.setAttribute("aria-hidden","true");const C=document.createElement("input");C.name=m.querySelector('[name="website"]')?"_website":"website",v.dataset.hpName=C.name,C.type="text",C.tabIndex=-1,C.autocomplete="url",C.placeholder="https://",v.appendChild(C);const A=document.createElement("input");A.name="_t",A.type="hidden",A.value=l,v.appendChild(A),m.appendChild(v)}function b(p){if(!window.FormLogicEngine||!t.some(v=>v.logic||Array.isArray(v.triggers)&&v.triggers.length))return null;const m=new window.FormLogicEngine.FormLogicRuntime(s,o,p?{baseValues:p}:void 0);if(m.init(),o._formLogicRuntime=m,o._formLogicObserved)return m;if(o._formLogicObserved=!0,o.parentNode&&typeof MutationObserver<"u"){const v=new MutationObserver(function(C){for(const A of C)for(const M of A.removedNodes)if(M===o||M.nodeType===1&&M.contains&&M.contains(o)){o._formLogicRuntime&&o._formLogicRuntime.destroy(),v.disconnect();return}});v.observe(o.parentNode,{childList:!0,subtree:!1}),o._formLogicObserver=v}return m}let u=!1;if(o.addEventListener("dm-form-end",p=>{const m=p.detail?.validation;if(m&&!m.valid){K(o,m.missing),Q(m);return}u||(u=!0,i(p.detail?.values||{}).catch(()=>{}).finally(()=>{u=!1}))}),f&&F.wizard){const p=me(t,s.title),m=p.map(I=>({title:I.title,description:I.description,fields:y(I.fields,a)})),v=p.map(I=>I.fields);let C=0,A=null,M=null;const z=I=>{const r=window.FormLogicEngine;if(!r||!r.validateFields)return;const q=r.validateFields(o,v[C]||[]);q.valid||(I.preventDefault(),I.stopImmediatePropagation(),K(o,q.missing),Q(q))},B=()=>{const I=v[C]||t;pe(o,e),Z(o,e,I),w(o,I),G(o,I),e.honeypot!==!1&&g(o),A&&A.destroy(),A=b(M?.getStepData?M.getStepData():void 0),o.querySelectorAll(".wizard-next, .wizard-submit").forEach(r=>r.addEventListener("click",z,!0))};M=F.wizard(o,{schema:{steps:m},layout:d,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"||!M?.goToStep)return;const r=Number(I.detail.action.step);!Number.isFinite(r)||r<0||r>=m.length||r!==C&&M.goToStep(r)}),Promise.resolve(M).then(B)}else if(F.render){const p=F.render(o,y(t,a),k(t),{submitText:e.submitText||"Submit",layout:d,columns:a,onSubmit:i});Promise.resolve(p).then(function(){ue(o,e,d),Z(o,e,t),w(o,t),G(o,t),e.honeypot!==!1&&g(o),b()})}})})();function he(h,c){const x=document.body,y=x.classList,k=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),w=()=>window.matchMedia("(min-width: 993px)").matches;h.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const d=a.textContent.trim();d&&(a.setAttribute("title",d),a.querySelector("svg, [data-icon]")||(a.dataset.initial=d.charAt(0).toUpperCase()))});const n=document.createElement("button");n.type="button",n.className="dm-nav-collapse-toggle";const o=()=>{const a=y.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 d=c?"chevrons-left":"chevrons-right",f=c?"chevrons-right":"chevrons-left";n.innerHTML=`<span data-icon="${a?d:f}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(n)},s=a=>{y.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),o()};n.addEventListener("click",()=>s(!y.contains("dm-vnav-collapsed"))),o();const t=h.querySelector(".navbar-container")||h;t.insertBefore(n,t.firstChild?.nextSibling||null);const e=a=>{y.toggle("dm-vnav-autohide",a),y.toggle("dm-vnav-collapsible",!a&&k.has("collapsible")),a?y.remove("dm-vnav-collapsed"):k.has("collapsible")&&S.get("nav_collapsed")===!0&&y.add("dm-vnav-collapsed"),h.classList.remove("dm-nav-revealed"),o()};h.addEventListener("pointerdown",a=>{y.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!h.classList.contains("dm-nav-revealed")&&(h.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{h.contains(a.target)||h.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(h,{items:()=>{const a=y.contains("dm-vnav-autohide"),d=y.contains("dm-vnav-collapsed"),f=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:d?"Expand menu":"Collapse to icons",icon:d?"maximize":"minimize",disabled:!w()||a||!k.has("collapsible"),action:()=>s(!d)},{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:!f,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(k.has("autohide")),y.toggle("dm-vnav-collapsed",!k.has("autohide")&&k.has("start-collapsed")),o()}}]}})}function fe(h){h.classList.add("dm-nav-scroll-hide");let c=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const y=window.scrollY,k=h.matches(":focus-within")||h.querySelector(".navbar-dropdown.open, .navbar-collapse.show");y>c&&y>h.offsetHeight&&!k?h.classList.add("dm-nav-scrolled-away"):y<c&&h.classList.remove("dm-nav-scrolled-away"),c=y}))},{passive:!0})}function ge(h){let c={};try{c=JSON.parse(h.dataset.contents||"{}")}catch{}const x=h.closest(".site-main"),y=document.querySelector(".site-content"),k=new Set(String(c.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),w=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=i=>(y?[...y.querySelectorAll(i)]:[]).filter(w).filter(l=>!k.has(l.textContent.trim().toLowerCase())),o=c.headings==="h2"?n("h2"):c.headings==="h3"?n("h3"):c.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",c.title||"Page contents"),c.hover&&(t.dataset.menuHover=c.hover),c.selected&&(t.dataset.menuActive=c.selected),c.accent&&t.style.setProperty("--dm-menu-highlight",c.accent.startsWith("#")?c.accent:`var(--dm-${c.accent})`),c.font&&(t.style.fontFamily=c.font),c.fontSize&&(t.style.fontSize=c.fontSize),c.fontWeight&&(t.style.fontWeight=c.fontWeight);const e=document.createElement("div");e.className="dm-contents-head";const a=document.createElement("p");a.className="dm-contents-title",a.textContent=c.title||"",e.appendChild(a),t.appendChild(e);const d=document.createElement("ol");d.className="dm-contents-list"+(c.numbered?" is-numbered":"");const f=[];for(const i of o){if(!i.id){const b=(i.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let u=b;for(let p=2;s.has(u);p++)u=`${b}-${p}`;s.add(u),i.id=u}const l=document.createElement("li");l.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const g=document.createElement("a");g.href=`#${i.id}`,g.textContent=i.textContent.trim(),l.appendChild(g),d.appendChild(l),f.push([i,g])}if(t.appendChild(d),h.replaceChildren(t),c.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,l=()=>{const b=f.find(([u])=>i.get(u))||null;f.forEach(([,u])=>{const p=b&&u===b[1];u.classList.toggle("is-active",!!p),p?u.setAttribute("aria-current","location"):u.removeAttribute("aria-current")})},g=new IntersectionObserver(b=>{b.forEach(u=>i.set(u.target,u.isIntersecting)),l()},{rootMargin:"-80px 0px -60% 0px"});f.forEach(([b])=>g.observe(b))}if(x?.classList.contains("contents-collapsible")){const i=document.createElement("button");i.type="button",i.className="dm-contents-toggle";const l=()=>{const g=x.classList.contains("contents-collapsed");i.setAttribute("aria-expanded",String(!g)),i.setAttribute("aria-label",g?"Show the contents":"Hide the contents"),i.title=i.getAttribute("aria-label"),i.innerHTML=`<span data-icon="${g?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(i)};i.addEventListener("click",()=>{S.set("contents_collapsed",x.classList.toggle("contents-collapsed")),l()}),l(),e.insertBefore(i,e.firstChild)}if(x?.classList.contains("contents-floating")&&c.movable){t.classList.add("is-movable");const i=S.get("contents_position");i&&Number.isFinite(i.x)&&Object.assign(h.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",l=>{if(l.target.closest("button"))return;const g=h.getBoundingClientRect(),b=l.clientX-g.left,u=l.clientY-g.top,p=v=>{const C=Math.max(0,Math.min(window.innerWidth-g.width,v.clientX-b)),A=Math.max(0,Math.min(window.innerHeight-40,v.clientY-u));Object.assign(h.style,{left:`${C}px`,top:`${A}px`,right:"auto",bottom:"auto"})},m=()=>{document.removeEventListener("pointermove",p),S.set("contents_position",{x:parseInt(h.style.left,10),y:parseInt(h.style.top,10)})};document.addEventListener("pointermove",p),document.addEventListener("pointerup",m,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(h,{items:()=>{const i=x?.classList.contains("contents-collapsible"),l=x?.classList.contains("contents-collapsed"),g=f.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:l?"Show the contents":"Collapse to a button",icon:l?"list":"minimize",action:()=>h.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!g,action:async()=>{const b=`${location.origin}${location.pathname}#${g[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")&&(h.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!h.classList.contains("is-revealed")&&(h.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{h.contains(i.target)||h.classList.remove("is-revealed")}))}
|
|
@@ -13,11 +13,24 @@
|
|
|
13
13
|
* DELETE /staged/:id discard an upload
|
|
14
14
|
* GET /jobs/:id a job's progress
|
|
15
15
|
* GET /history · GET|DELETE /history/:id
|
|
16
|
+
*
|
|
17
|
+
* Users: the preview and the run take {options.users, options.userRole,
|
|
18
|
+
* options.invite}. Accounts are made only for someone who holds users.create,
|
|
19
|
+
* with a role they could give through Users (canManageUser - strictly junior to
|
|
20
|
+
* their own, never level 0); a role they may not give is refused here, not only
|
|
21
|
+
* hidden on the screen. The "set your password" email is core's administrator
|
|
22
|
+
* reset (services/passwordReset.js), to the address adminOrigin() trusts.
|
|
16
23
|
*/
|
|
17
24
|
import fs from 'node:fs';
|
|
18
25
|
import {pipeline} from 'node:stream/promises';
|
|
19
26
|
import {Transform} from 'node:stream';
|
|
20
|
-
import {authenticate as defaultAuthenticate, requirePermission as defaultRequirePermission} from '../../middleware/auth.js';
|
|
27
|
+
import {authenticate as defaultAuthenticate, canManageUser, requirePermission as defaultRequirePermission} from '../../middleware/auth.js';
|
|
28
|
+
import {getPermissionsFor, getRoleHierarchy, getRoleLevel, getRoleMap} from '../../services/roles.js';
|
|
29
|
+
import {getEffectiveRoles} from '../../services/userRoles.js';
|
|
30
|
+
import {getUserById} from '../../services/users.js';
|
|
31
|
+
import {isSmtpConfigured} from '../../services/email.js';
|
|
32
|
+
import {adminOrigin} from '../../services/passwordReset.js';
|
|
33
|
+
import {getConfig} from '../../config.js';
|
|
21
34
|
import {
|
|
22
35
|
blogTarget, shopTarget, cleanOptions, createStaging, discardStaged, getJob, inspectStaged, previewImport, runningJob, stagingMeta,
|
|
23
36
|
stagingPath, startRun, updateStagingMeta, UPLOADS_ZIP_MAX_BYTES, WXR_MAX_BYTES, zipRel
|
|
@@ -48,6 +61,58 @@ async function saveUpload(file, dest, max) {
|
|
|
48
61
|
|
|
49
62
|
const mb = (n) => `${Math.round(n / 1048576)} MB`;
|
|
50
63
|
|
|
64
|
+
/**
|
|
65
|
+
* What the person running the import may do with users: whether they may add
|
|
66
|
+
* accounts at all (users.create), and the roles they may give - every role on
|
|
67
|
+
* the site strictly junior to their own (canManageUser, as Users › New user),
|
|
68
|
+
* never level 0. The default is the least-privileged role. Their own record is
|
|
69
|
+
* read fresh, so a role taken away since they signed in counts at once.
|
|
70
|
+
*
|
|
71
|
+
* @param {object} requestUser - request.user
|
|
72
|
+
* @returns {Promise<{canCreate: boolean, why: string, roles: {name: string, label: string, level: number}[], defaultRole: string, mailReady: boolean}>}
|
|
73
|
+
*/
|
|
74
|
+
export async function peopleContext(requestUser) {
|
|
75
|
+
const fresh = requestUser?.id ? await getUserById(requestUser.id).catch(() => null) : null;
|
|
76
|
+
const actor = fresh || requestUser || null;
|
|
77
|
+
const allowed = getPermissionsFor('users', 'create');
|
|
78
|
+
const mayAdd = getEffectiveRoles(actor).some(r => allowed.includes(r));
|
|
79
|
+
const map = getRoleMap();
|
|
80
|
+
const hierarchy = getRoleHierarchy();
|
|
81
|
+
const roles = hierarchy
|
|
82
|
+
.filter(name => { const level = getRoleLevel(name); return Number.isFinite(level) && level > 0 && canManageUser(actor, name); })
|
|
83
|
+
.map(name => ({name, label: String(map.get(name)?.label || name), level: getRoleLevel(name)}));
|
|
84
|
+
const least = hierarchy.at(-1);
|
|
85
|
+
const defaultRole = roles.some(r => r.name === least) ? least : (roles.at(-1)?.name || '');
|
|
86
|
+
const canCreate = Boolean(mayAdd && roles.length);
|
|
87
|
+
const why = !mayAdd ? 'You do not have permission to add users, so no accounts are made.'
|
|
88
|
+
: !roles.length ? 'There is no role junior to yours to give new accounts, so none are made.' : '';
|
|
89
|
+
return {canCreate, why, roles, defaultRole, mailReady: isSmtpConfigured(getConfig('site')?.smtp)};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The role asked for new accounts, checked: null when it may be given ('' = the
|
|
94
|
+
* default), else the refusal {code, error}.
|
|
95
|
+
*/
|
|
96
|
+
export function roleRefusal(role, ctx) {
|
|
97
|
+
if (!role) return null;
|
|
98
|
+
if (!Number.isFinite(getRoleLevel(role))) return {code: 400, error: `There is no role called "${String(role).slice(0, 60)}" on this site.`};
|
|
99
|
+
if (!ctx.canCreate) return {code: 403, error: ctx.why || 'You cannot add users.'};
|
|
100
|
+
if (!ctx.roles.some(r => r.name === role)) return {code: 403, error: `You cannot give new accounts the "${String(role).slice(0, 60)}" role - only a role junior to your own.`};
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** What the screen shows of the authors - with their emails, which only the preview ever carries. */
|
|
105
|
+
function peopleForScreen(people) {
|
|
106
|
+
if (!people) return null;
|
|
107
|
+
return {
|
|
108
|
+
role: people.role, roles: people.roles, canCreate: people.canCreate, why: people.why, invite: people.invite, mailReady: people.mailReady, counts: people.counts,
|
|
109
|
+
rows: people.rows.map(r => ({
|
|
110
|
+
key: r.key, wpId: r.wpId, name: r.name, email: r.email || r.rawEmail || '', items: r.items, total: r.total, action: r.action,
|
|
111
|
+
userName: r.userName || '', role: r.role || '', reason: r.reason || '', problem: r.problem || '', choosable: Boolean(r.choosable), choice: r.choice || ''
|
|
112
|
+
}))
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
51
116
|
/** What the screen shows of a plan - the plan minus internals. */
|
|
52
117
|
export function planForScreen(plan, blog, shop = {}) {
|
|
53
118
|
return {
|
|
@@ -59,6 +124,7 @@ export function planForScreen(plan, blog, shop = {}) {
|
|
|
59
124
|
shop: shopForScreen(shop),
|
|
60
125
|
options: plan.options,
|
|
61
126
|
summary: plan.summary,
|
|
127
|
+
people: peopleForScreen(plan.people),
|
|
62
128
|
notes: plan.notes,
|
|
63
129
|
items: plan.items.map(r => ({
|
|
64
130
|
key: r.key, kind: r.kind, wpId: r.wpId, title: r.title, action: r.action, reason: r.reason || '',
|
|
@@ -82,7 +148,7 @@ export async function wordpressImportRoutes(fastify, opts = {}) {
|
|
|
82
148
|
const may = {preHandler: [authenticate, requirePermission('import', 'wordpress')]};
|
|
83
149
|
const fail = (reply, err, fallback = 400) => reply.code(err.statusCode || fallback).send({error: err.message || String(err)});
|
|
84
150
|
|
|
85
|
-
fastify.get('/status', may, async () => {
|
|
151
|
+
fastify.get('/status', may, async (request) => {
|
|
86
152
|
const [blog, shop] = await Promise.all([blogTarget(), shopTarget()]);
|
|
87
153
|
const job = runningJob();
|
|
88
154
|
return {
|
|
@@ -92,6 +158,7 @@ export async function wordpressImportRoutes(fastify, opts = {}) {
|
|
|
92
158
|
history: (await listHistory()).slice(0, 50),
|
|
93
159
|
blog: {available: blog.available, basePath: blog.basePath, comments: Boolean(blog.comments), reason: blog.reason || ''},
|
|
94
160
|
redirects: Boolean(seoEngine()),
|
|
161
|
+
people: await peopleContext(request.user),
|
|
95
162
|
limits: {wxr: WXR_MAX_BYTES, uploads: UPLOADS_ZIP_MAX_BYTES}
|
|
96
163
|
};
|
|
97
164
|
});
|
|
@@ -184,7 +251,11 @@ export async function wordpressImportRoutes(fastify, opts = {}) {
|
|
|
184
251
|
const {id} = request.params;
|
|
185
252
|
if (!isId(id)) return reply.code(404).send({error: 'That upload has gone.'});
|
|
186
253
|
try {
|
|
187
|
-
const
|
|
254
|
+
const options = cleanOptions(request.body?.options);
|
|
255
|
+
const people = await peopleContext(request.user);
|
|
256
|
+
const refused = roleRefusal(options.userRole, people);
|
|
257
|
+
if (refused) return reply.code(refused.code).send({error: refused.error});
|
|
258
|
+
const {plan, state, meta} = await previewImport(id, {...options, people});
|
|
188
259
|
return {meta, plan: planForScreen(plan, state.blog, state.shop), redirectsAvailable: state.redirectsAvailable};
|
|
189
260
|
} catch (err) {
|
|
190
261
|
return fail(reply, err);
|
|
@@ -195,7 +266,13 @@ export async function wordpressImportRoutes(fastify, opts = {}) {
|
|
|
195
266
|
const {id} = request.params;
|
|
196
267
|
if (!isId(id)) return reply.code(404).send({error: 'That upload has gone.'});
|
|
197
268
|
try {
|
|
198
|
-
const
|
|
269
|
+
const options = cleanOptions(request.body?.options);
|
|
270
|
+
const people = await peopleContext(request.user);
|
|
271
|
+
const refused = roleRefusal(options.userRole, people);
|
|
272
|
+
if (refused) return reply.code(refused.code).send({error: refused.error});
|
|
273
|
+
const actor = await getUserById(request.user?.id).catch(() => null);
|
|
274
|
+
const inviteOrigin = options.invite && people.canCreate ? await adminOrigin(request) : '';
|
|
275
|
+
const job = await startRun(id, {...options, people, inviteOrigin}, actor || request.user, fastify.log);
|
|
199
276
|
return reply.code(202).send({job: {...job}});
|
|
200
277
|
} catch (err) {
|
|
201
278
|
return fail(reply, err);
|
package/server/server.js
CHANGED
|
@@ -338,7 +338,7 @@ try {
|
|
|
338
338
|
app.log.warn(`[admin-sidebar] Migration skipped: ${err.message}`);
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
// Initialise response cache (Memory/None
|
|
341
|
+
// Initialise response cache (Memory/None driver per config.cache).
|
|
342
342
|
await cache.initCache(config.cache);
|
|
343
343
|
|
|
344
344
|
// Number fields saved as text by the pre-0.77.1 entry form - one-shot repair.
|
|
@@ -406,11 +406,6 @@ app.addHook('onResponse', async (request, reply) => {
|
|
|
406
406
|
}
|
|
407
407
|
console.log(`[cache] driver=${cache.isEnabled() ? config.cache.driver : 'disabled'}`);
|
|
408
408
|
|
|
409
|
-
// Docs are rendered at request time from the shipped admin templates; clear any
|
|
410
|
-
// previously-cached docs HTML so a version bump (new templates) never serves
|
|
411
|
-
// stale pages. No-op on a cold memory cache; matters for the Redis driver.
|
|
412
|
-
await cache.invalidateTags(['docs']);
|
|
413
|
-
|
|
414
409
|
// Serve uploaded media files - nosniff prevents browsers rendering spoofed content types
|
|
415
410
|
await app.register(staticPlugin, {
|
|
416
411
|
root: mediaDir,
|
|
@@ -22,9 +22,8 @@ const _stats = {
|
|
|
22
22
|
/**
|
|
23
23
|
* @param {{
|
|
24
24
|
* enabled?: boolean,
|
|
25
|
-
* driver?: 'memory' | 'none'
|
|
26
|
-
* memory?: {maxItems?: number, defaultTtlSeconds?: number}
|
|
27
|
-
* redis?: {url: string, keyPrefix?: string}
|
|
25
|
+
* driver?: 'memory' | 'none',
|
|
26
|
+
* memory?: {maxItems?: number, defaultTtlSeconds?: number}
|
|
28
27
|
* }} config
|
|
29
28
|
*/
|
|
30
29
|
export async function initCache(config = {}) {
|
|
@@ -46,13 +45,6 @@ export async function initCache(config = {}) {
|
|
|
46
45
|
_enabled = false;
|
|
47
46
|
return;
|
|
48
47
|
}
|
|
49
|
-
if (name === 'redis') {
|
|
50
|
-
const {RedisDriver} = await import('./drivers/RedisDriver.js');
|
|
51
|
-
_driver = new RedisDriver(config.redis ?? {});
|
|
52
|
-
await _driver.connect();
|
|
53
|
-
_enabled = true;
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
48
|
throw new Error(`Unknown cache driver: ${name}`);
|
|
57
49
|
}
|
|
58
50
|
|
|
@@ -74,7 +66,7 @@ export async function setEnabled(enabled) {
|
|
|
74
66
|
if (!cfg.driver || cfg.driver === 'none') cfg.driver = 'memory';
|
|
75
67
|
await initCache(cfg);
|
|
76
68
|
} else {
|
|
77
|
-
// Memory
|
|
69
|
+
// Memory driver still live - flip the flag and preserve data.
|
|
78
70
|
_enabled = true;
|
|
79
71
|
}
|
|
80
72
|
} else {
|
|
@@ -816,7 +816,7 @@ function renderCollectionCarousel(entries, opts, ctxOpts = {}) {
|
|
|
816
816
|
}
|
|
817
817
|
|
|
818
818
|
const passthrough = ['autoplay', 'interval', 'loop', 'animation', 'animation-duration', 'animation-easing', 'arrow-position',
|
|
819
|
-
'background', 'arrow-background', 'arrow-color', 'height-mode', 'height']
|
|
819
|
+
'background', 'arrow-background', 'arrow-color', 'height-mode', 'height', 'mobile-popup']
|
|
820
820
|
.map(k => dataAttrs[k] ? ` data-${k}="${escapeAttr(String(dataAttrs[k]))}"` : '')
|
|
821
821
|
.join('');
|
|
822
822
|
|
|
@@ -3197,6 +3197,7 @@ function processCarouselBlocks(markdown) {
|
|
|
3197
3197
|
(_, attrStr, body) => {
|
|
3198
3198
|
const attrs = parseShortcodeAttrs(attrStr);
|
|
3199
3199
|
const dataAttrs = [
|
|
3200
|
+
attrs['mobile-popup'] === 'true' ? ' data-mobile-popup="true"' : '',
|
|
3200
3201
|
attrs.autoplay ? ` data-autoplay="${escapeAttr(attrs.autoplay)}"` : '',
|
|
3201
3202
|
attrs.interval ? ` data-interval="${escapeAttr(attrs.interval)}"` : '',
|
|
3202
3203
|
attrs.loop ? ` data-loop="${escapeAttr(attrs.loop)}"` : '',
|
package/server/services/menus.js
CHANGED
|
@@ -284,6 +284,14 @@ export function normaliseDock(dock) {
|
|
|
284
284
|
return Object.keys(out).length ? out : null;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
/** Mobile navbar choices are independent and opt in. */
|
|
288
|
+
export function normaliseMobile(mobile) {
|
|
289
|
+
if (!mobile || typeof mobile !== 'object' || Array.isArray(mobile)) return null;
|
|
290
|
+
const out = Object.fromEntries(['hamburgerOnly', 'hideBrand']
|
|
291
|
+
.filter(key => mobile[key] === true).map(key => [key, true]));
|
|
292
|
+
return Object.keys(out).length ? out : null;
|
|
293
|
+
}
|
|
294
|
+
|
|
287
295
|
// ---------------------------------------------------------------------------
|
|
288
296
|
// Per-page overrides - the `[navbar ... /]` tag.
|
|
289
297
|
//
|
|
@@ -610,6 +618,7 @@ export async function createMenu(input) {
|
|
|
610
618
|
...(input.class != null && input.class !== '' && {class: input.class}),
|
|
611
619
|
...(input.style != null && {style: input.style}),
|
|
612
620
|
...(normaliseDock(input.dock) && {dock: normaliseDock(input.dock)}),
|
|
621
|
+
...(normaliseMobile(input.mobile) && {mobile: normaliseMobile(input.mobile)}),
|
|
613
622
|
...(binding != null && {binding}),
|
|
614
623
|
items: Array.isArray(input.items) ? input.items : [],
|
|
615
624
|
meta: {
|
|
@@ -658,6 +667,7 @@ export async function updateMenu(slug, input) {
|
|
|
658
667
|
...(input.class != null && input.class !== '' && {class: input.class}),
|
|
659
668
|
...(input.style != null && {style: input.style}),
|
|
660
669
|
...(normaliseDock(input.dock) && {dock: normaliseDock(input.dock)}),
|
|
670
|
+
...(normaliseMobile(input.mobile) && {mobile: normaliseMobile(input.mobile)}),
|
|
661
671
|
...(binding != null && {binding}),
|
|
662
672
|
items: Array.isArray(input.items) ? input.items : [],
|
|
663
673
|
meta: {
|
|
@@ -864,6 +874,17 @@ export function validateMenu(menu) {
|
|
|
864
874
|
if (menu.align != null && menu.align !== '' && !MENU_ALIGN.includes(menu.align)) {
|
|
865
875
|
errors.push(`align must be one of: ${MENU_ALIGN.join(', ')}`);
|
|
866
876
|
}
|
|
877
|
+
if (menu.mobile != null) {
|
|
878
|
+
if (typeof menu.mobile !== 'object' || Array.isArray(menu.mobile)) {
|
|
879
|
+
errors.push('mobile must be an object');
|
|
880
|
+
} else {
|
|
881
|
+
for (const key of ['hamburgerOnly', 'hideBrand']) {
|
|
882
|
+
if (menu.mobile[key] != null && typeof menu.mobile[key] !== 'boolean') {
|
|
883
|
+
errors.push(`mobile.${key} must be true or false`);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
867
888
|
if (menu.highlight != null) {
|
|
868
889
|
if (typeof menu.highlight !== 'object' || Array.isArray(menu.highlight)) {
|
|
869
890
|
errors.push('highlight must be an object');
|
|
@@ -28,6 +28,12 @@
|
|
|
28
28
|
* - an administrator's reset (Users › ⋮ › Send password reset / Copy a
|
|
29
29
|
* reset link) - adminReset(); the admin's own request is signed in, so its
|
|
30
30
|
* origin is trusted as it stands;
|
|
31
|
+
* - an invitation (adminInvite) - "an account has been made for you, set your
|
|
32
|
+
* password": the same token store, link and set-password screen as a reset,
|
|
33
|
+
* but the link lives INVITE_LIFETIME (7 days), not resetTokenExpiry. The
|
|
34
|
+
* expiry is stored with the token, so the screen and the reset route check
|
|
35
|
+
* either the same way; whichever link was issued last replaces the other,
|
|
36
|
+
* and the reset route clears it on use - one token per account, once;
|
|
31
37
|
* - checkResetToken() for the reset screen, which checks the link on
|
|
32
38
|
* opening rather than after a new password has been typed twice;
|
|
33
39
|
* - the "your password was changed" email on every auth:passwordChanged
|
|
@@ -80,6 +86,9 @@ export function durationText(ms) {
|
|
|
80
86
|
return `${n} minute${n === 1 ? '' : 's'}`;
|
|
81
87
|
}
|
|
82
88
|
|
|
89
|
+
/** How long an invitation's set-your-password link lives - fixed, whatever resetTokenExpiry says. */
|
|
90
|
+
export const INVITE_LIFETIME = {ms: 7 * 86_400_000, text: '7 days'};
|
|
91
|
+
|
|
83
92
|
/** How long a reset link lives, from config/auth.json `resetTokenExpiry`. */
|
|
84
93
|
export function resetLifetime() {
|
|
85
94
|
const ms = durationMs(getConfig('auth')?.resetTokenExpiry || '1h');
|
|
@@ -328,15 +337,17 @@ function fromOf(site) {
|
|
|
328
337
|
}
|
|
329
338
|
|
|
330
339
|
/**
|
|
331
|
-
* A new reset token for a user; replaces any earlier one.
|
|
340
|
+
* A new reset token for a user; replaces any earlier one (a reset's or an invitation's).
|
|
332
341
|
*
|
|
333
342
|
* @param {string} userId
|
|
343
|
+
* @param {{ms?: number}} [o] - its lifetime; a reset's (resetTokenExpiry) unless given
|
|
334
344
|
* @returns {Promise<{token: string, expiresAt: string}>}
|
|
335
345
|
*/
|
|
336
|
-
export async function issueResetToken(userId) {
|
|
346
|
+
export async function issueResetToken(userId, {ms} = {}) {
|
|
337
347
|
const token = crypto.randomBytes(32).toString('hex');
|
|
338
348
|
const hash = crypto.createHash('sha256').update(token).digest('hex');
|
|
339
|
-
const
|
|
349
|
+
const life = Number.isFinite(ms) && ms > 0 ? ms : resetLifetime().ms;
|
|
350
|
+
const expiresAt = new Date(Date.now() + life).toISOString();
|
|
340
351
|
await setResetToken(userId, hash, expiresAt);
|
|
341
352
|
return {token, expiresAt};
|
|
342
353
|
}
|
|
@@ -444,6 +455,63 @@ export async function adminReset({user, origin, via, by}) {
|
|
|
444
455
|
return {sent: true, expiresAt, expiresIn};
|
|
445
456
|
}
|
|
446
457
|
|
|
458
|
+
/**
|
|
459
|
+
* The invitation email: an account was made for them, choose a password.
|
|
460
|
+
*
|
|
461
|
+
* @param {{name: string, email: string, link: string, lifetime: string, siteTitle: string, invitedBy?: string}} o
|
|
462
|
+
* @returns {{subject: string, html: string, text: string}}
|
|
463
|
+
*/
|
|
464
|
+
export function buildInviteEmail({name, email: address, link, lifetime, siteTitle, invitedBy = ''}) {
|
|
465
|
+
const site = siteTitle || 'the site';
|
|
466
|
+
const hi = name ? `Hi ${name},` : 'Hello,';
|
|
467
|
+
const who = invitedBy ? `${invitedBy}, an administrator of ${site},` : `An administrator of ${site}`;
|
|
468
|
+
const line = `An account has been made for you on ${site}${address ? ` (you sign in with ${address})` : ''}. ${who} invites you to set your password and sign in.`;
|
|
469
|
+
return {
|
|
470
|
+
subject: `You have an account on ${site} - set your password`,
|
|
471
|
+
html: `
|
|
472
|
+
<!DOCTYPE html>
|
|
473
|
+
<html>
|
|
474
|
+
<body style="font-family:sans-serif;max-width:560px;margin:0 auto;padding:24px;">
|
|
475
|
+
<h2 style="color:#333;">Your account on ${esc(site)}</h2>
|
|
476
|
+
<p>${esc(hi)}</p>
|
|
477
|
+
<p>${esc(line)}</p>
|
|
478
|
+
<p style="margin:24px 0;">
|
|
479
|
+
<a href="${esc(link)}" style="background:#5b8cff;color:#fff;padding:12px 24px;border-radius:6px;text-decoration:none;display:inline-block;">Set your password</a>
|
|
480
|
+
</p>
|
|
481
|
+
<p style="color:#888;font-size:.85rem;">The link works once and expires in ${esc(lifetime)}. Not expecting this? Ignore it - nobody can sign in to the account until a password is set.</p>
|
|
482
|
+
<p style="color:#bbb;font-size:.8rem;">${esc(link)}</p>
|
|
483
|
+
</body>
|
|
484
|
+
</html>`.trim(),
|
|
485
|
+
text: `${hi}\n\n${line}\n\nSet your password here (the link works once and expires in ${lifetime}):\n${link}\n\n`
|
|
486
|
+
+ 'Not expecting this? Ignore it - nobody can sign in to the account until a password is set.'
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* An invitation to an account an administrator (or an import) made: a
|
|
492
|
+
* set-your-password link valid INVITE_LIFETIME, emailed. It replaces any
|
|
493
|
+
* earlier link for the account. The caller has already decided this account
|
|
494
|
+
* may be invited.
|
|
495
|
+
*
|
|
496
|
+
* @param {{user: object, origin: string, by?: {id?: string, name?: string, email?: string}|true}} o
|
|
497
|
+
* @returns {Promise<{sent: true, expiresAt: string, expiresIn: string}>}
|
|
498
|
+
* @throws {Error} code 'NO_SMTP' | 'INACTIVE' | 'NO_ORIGIN'
|
|
499
|
+
*/
|
|
500
|
+
export async function adminInvite({user, origin, by}) {
|
|
501
|
+
const fail = (code, message) => Object.assign(new Error(message), {code});
|
|
502
|
+
if (!user.isActive) throw fail('INACTIVE', 'This account is inactive - make it active first, or it cannot sign in.');
|
|
503
|
+
const site = getConfig('site') || {};
|
|
504
|
+
if (!mailer.isSmtpConfigured(site.smtp)) throw fail('NO_SMTP', 'No mail server is set up (Settings › Email), so the invitation cannot be emailed.');
|
|
505
|
+
if (!origin) throw fail('NO_ORIGIN', 'The site does not know its own address. Set the Site URL in Settings › General.');
|
|
506
|
+
const {token, expiresAt} = await issueResetToken(user.id, {ms: INVITE_LIFETIME.ms});
|
|
507
|
+
hooks.emit('auth:resetIssued', {userId: user.id, by: by?.id ?? null, via: 'invite'});
|
|
508
|
+
const message = buildInviteEmail({name: user.name, email: user.email, link: resetLink(origin, token), lifetime: INVITE_LIFETIME.text,
|
|
509
|
+
siteTitle: site.title, invitedBy: by === true ? '' : (by?.name || by?.email || '')});
|
|
510
|
+
const transport = await mailer.createTransport(site.smtp);
|
|
511
|
+
await mailer.sendEmail(transport, {...fromOf(site), to: user.email, ...message});
|
|
512
|
+
return {sent: true, expiresAt, expiresIn: INVITE_LIFETIME.text};
|
|
513
|
+
}
|
|
514
|
+
|
|
447
515
|
// ---------------------------------------------------------------------------
|
|
448
516
|
// The reset screen
|
|
449
517
|
// ---------------------------------------------------------------------------
|
|
@@ -305,6 +305,7 @@ export async function renderPage(page, opts = {}) {
|
|
|
305
305
|
// Docked behaviour (rail width, collapse, autohide, hide on scroll) - site.js
|
|
306
306
|
// wires it; width and autohide also shape the body below, from the first paint.
|
|
307
307
|
...(navMenu.dock && {dock: navMenu.dock}),
|
|
308
|
+
...(navMenu.mobile && {mobile: navMenu.mobile}),
|
|
308
309
|
// Item highlighting: the navbar is built in the browser, so the
|
|
309
310
|
// config has to travel to site.js rather than being rendered here.
|
|
310
311
|
...(navMenu.highlight && {highlight: navMenu.highlight}),
|
|
@@ -1076,6 +1077,7 @@ export async function renderBlogPage(templatePath, data = {}, seoMeta = {}) {
|
|
|
1076
1077
|
// Docked behaviour (rail width, collapse, autohide, hide on scroll) - site.js
|
|
1077
1078
|
// wires it; width and autohide also shape the body below, from the first paint.
|
|
1078
1079
|
...(navMenu.dock && {dock: navMenu.dock}),
|
|
1080
|
+
...(navMenu.mobile && {mobile: navMenu.mobile}),
|
|
1079
1081
|
// Item highlighting: the navbar is built in the browser, so the
|
|
1080
1082
|
// config has to travel to site.js rather than being rendered here.
|
|
1081
1083
|
...(navMenu.highlight && {highlight: navMenu.highlight}),
|