domma-cms 1.6.14 → 1.6.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/js/app.js +1 -1
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/layouts-arrange.js +1 -1
- package/admin/js/lib/project-page-arrange.js +1 -1
- package/admin/js/lib/scribe-composer.js +1 -4
- package/admin/js/lib/scribe-model.js +4 -0
- package/admin/js/lib/shortcode-registry.js +1 -1
- package/admin/js/lib/sidebar-grouping.js +1 -1
- package/admin/js/lib/sidebar-renderer.js +2 -2
- package/admin/js/templates/docs/usage-navigation.html +4 -2
- package/admin/js/templates/layouts.html +7 -1
- package/admin/js/views/layouts.js +57 -15
- package/package.json +1 -1
- package/public/css/site.css +1 -1
- package/public/js/site.js +1 -1
- package/server/routes/api/layouts.js +3 -0
- package/server/services/markdown.js +33 -6
- package/server/services/pageContents.js +122 -0
- package/server/services/permissionRegistry.js +1 -1
- package/server/services/renderer.js +14 -2
- package/server/services/sidebar-migration.js +14 -1
- package/server/templates/page.html +14 -4
package/public/js/site.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{decodeBase64Json as N}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as O,applyColour as j,applyPill as U,applyFloat as X,applyHighlight as B,applyItemHighlight as G,itemHighlightAttrs as K,isCurrentItem as R,menuAlign as Q,applyClasses as I,cssClasses as P}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{initFloatingMenus as Z}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as V,markInvalidFields as W,showValidationPopup as J}from"/public/js/forms.js?v=20261009-form-classes";import{buildWizardSteps as ee}from"/public/js/form-steps.mjs";import{applyFormClasses as Y,applySubmitButtons as te,applyWizardButtons as oe}from"/public/js/form-submit.mjs";$(()=>{const g=window.__CMS_NAV__||{},f=window.__CMS_SITE__||{};if(f.autoTheme?.enabled){let t=function(e){const a=(e||"07:00").split(":");return+a[0]*60+(+a[1]||0)},n=function(){const e=new Date,a=e.getHours()*60+e.getMinutes();return a>=t(o.dayStart)&&a<t(o.nightStart)?o.dayTheme:o.nightTheme},s=function(e){const a=o.apply&&o.apply[e]||{theme:e,custom:""};Domma.theme.set(a.theme);for(const c of[...document.body.classList])c.startsWith("dm-custom-")&&document.body.classList.remove(c);a.custom&&document.body.classList.add(a.custom)};const o=f.autoTheme;s(n()),setInterval(()=>s(n()),6e4)}const k=$("#site-navbar");if(k.length&&g.brand){let n=function(d,l=0){return Array.isArray(d)?d.filter(r=>!(r&&(r.type==="spacer"||r.type==="theme-switcher"))).filter(r=>!(r&&r.type==="separator"&&l===0)).map(r=>r&&r.type==="separator"?{divider:!0}:Array.isArray(r?.items)?{...r,items:n(r.items,l+1)}:r):d},s=function(d){return Array.isArray(d)?d.filter(l=>!(l&&(l.type==="separator"||l.type==="spacer"||l.type==="theme-switcher"))):[]};const o={...g.brand},t=o.size&&o.size!=="md"?` navbar-brand-${o.size}`:"";if(o.logo||o.icon||o.tagline){let d="";o.logo?d+=`<img src="${o.logo}" class="navbar-brand-logo" alt="${o.text||""}">`:o.icon&&(d+=`<span data-icon="${o.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),o.text&&(d+=`<span class="navbar-brand-text${t}">${o.text}</span>`),o.tagline&&(d+=`<small class="navbar-brand-tagline">${o.tagline}</small>`),o.html=d}else t&&o.text&&(o.html=`<span class="navbar-brand-text${t}">${o.text}</span>`);const e=g.position==="floating",a=g.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:o,items:n(g.items||[]),variant:g.variant||"dark",position:e?"static":g.position||"sticky",collapsible:!0,appearOnHover:!!g.appearOnHover}),Domma.icons.scan("#site-navbar");const c=k.get(0);c.classList.add(`dm-nav-align-${Q(g)||"centre"}`),a&&k.get(0).classList.add("dm-nav-vertical",g.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const v=g.dock||{};a&&!e&&ae(c,g.side==="right"),!a&&v.hideOnScroll&&ne(c),B(c,g),I(c,g.class),e&&(X(k.get(0),g),Z(k.get(0).parentNode||document));const h=c.querySelector(".navbar-toggle"),m=c.querySelector(".navbar-collapse");if(h&&m){let d=null;const l=()=>{m.classList.contains("show")&&h.click()},r=()=>{d&&(d.remove(),d=null),document.body.classList.remove("dm-drawer-open")},p=()=>{d||(d=document.createElement("div"),d.className="dm-navbar-backdrop",d.addEventListener("click",l),document.body.appendChild(d),document.body.classList.add("dm-drawer-open"))};h.addEventListener("click",()=>{requestAnimationFrame(()=>{m.classList.contains("show")?p():r()})}),m.addEventListener("click",i=>{i.target.closest("a")&&requestAnimationFrame(r)}),document.addEventListener("keydown",i=>{i.key==="Escape"&&m.classList.contains("show")&&l()})}const C=c.querySelector(".navbar-nav");if(C){const d=Array.from(C.children);let l=0;for(const r of g.items||[])if(!(r&&r.type==="separator")){if(r&&r.type==="spacer"){const p=document.createElement("li"),i=["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md";p.className=`dm-menu-spacer dm-menu-spacer--${i}`,p.setAttribute("aria-hidden","true"),C.insertBefore(p,d[l]||null);continue}if(r&&r.type==="theme-switcher"){const p=document.createElement("li");p.className="dm-menu-theme-switcher";const i=document.createElement("span");i.dataset.themeSwitcher="menu",i.dataset.position="inline",p.appendChild(i),C.insertBefore(p,d[l]||null);continue}l++}window.CMSThemeSwitcher?.scan(C)}const x=s(g.items||[]);c.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(d=>{const l=x[parseInt(d.dataset.index,10)];if(!l)return;const r=d.dataset.subindex!=null,p=r&&Array.isArray(l.items)?l.items[parseInt(d.dataset.subindex,10)]:l;if(!p)return;p.colour&&j(d,p.colour),p.pill&&!r&&U(d,p.pill),G(d,p),I(d.closest("li"),p.class);const i=O(p.badge);i&&d.appendChild(i),R(p.url,location.pathname)&&(d.classList.add("active"),d.setAttribute("aria-current","page"))})}const y=$("#site-footer");if(y.length){let e=function(i){return String(i??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},a=function(i){return e(i).replace(/'/g,"'")},c=function(i){if(i&&i.type==="separator")return'<li class="footer-sep"><hr></li>';if(i&&i.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(i.size)?i.size:"md"}" aria-hidden="true"></li>`;if(i&&i.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const w=a(i.url||"#"),u=e(i.text||""),q=Array.isArray(i.items)&&i.items.length?`<ul>${i.items.map(c).join("")}</ul>`:"",D=K(i),L=[D.hover&&` data-menu-item-hover="${a(D.hover)}"`,D.active&&` data-menu-item-active="${a(D.active)}"`].filter(Boolean).join(""),_=D.accent?` style="--dm-menu-highlight:${a(D.accent)}"`:"",M=P(i.class).join(" ");return`<li${M?` class="${a(M)}"`:""}><a href="${w}"${L}${_}>${u}</a>${q}</li>`},v=function(i,w){return(i||[]).forEach(u=>{u&&(u.type==="separator"||u.type==="spacer"||u.type==="theme-switcher")||(w.push(u),Array.isArray(u.items)&&u.items.length&&v(u.items,w))}),w};const o=f.social||{},t={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 n='<div class="footer-inner container">';const s=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(s.copyright&&(n+=`<p>${e(s.copyright)}</p>`),s.primary.length&&(n+=`<nav class="footer-nav footer-nav--primary"><ul>${s.primary.map(c).join("")}</ul></nav>`),s.legal.length&&(n+=`<nav class="footer-nav footer-nav--legal"><ul>${s.legal.map(c).join("")}</ul></nav>`),f.footer){const i=Object.keys(t).filter(u=>o[u]),w=P(f.footer.socialClass).join(" ");i.length&&(n+='<div class="footer-social">',i.forEach(u=>{const{label:q,svg:D}=t[u];n+=`<a href="${o[u]}" target="_blank" rel="noopener noreferrer" aria-label="${q}" class="footer-social-link${w?` ${a(w)}`:""}">${D}</a>`}),n+="</div>")}n+="</div>",y.html(n),window.CMSThemeSwitcher?.scan(y.get(0)),I(y.get(0),f.footer?.class);const h=[...v(s.primary,[]),...v(s.legal,[])];y.get(0).querySelectorAll(".footer-nav a").forEach((i,w)=>{const u=h[w];if(!u)return;u.colour&&j(i,u.colour);const q=O(u.badge);q&&i.appendChild(q),R(u.url,location.pathname)&&(i.classList.add("is-active"),i.setAttribute("aria-current","page"))}),B(y.get(0).querySelector(".footer-nav--primary"),{highlight:s.primaryHighlight}),B(y.get(0).querySelector(".footer-nav--legal"),{highlight:s.legalHighlight}),I(y.get(0).querySelector(".footer-nav--primary"),s.primaryClass),I(y.get(0).querySelector(".footer-nav--legal"),s.legalClass);const C=f.footer?.showMotionToggle!==!1,x=S.get("reduced_motion"),d=f.footer?.motionDefault,l=x!=null?!!x:d==="on"?!0:d==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),r=f.footer?.motionControl==="checkbox",p=y.get(0).querySelector(".footer-inner");if(C&&p){const i=document.createElement("input");i.type="checkbox",i.className=r?"footer-motion-checkbox":"form-switch-input",i.id="dm-motion-switch",i.checked=l,i.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const w=document.createElement("span");w.className=r?"footer-motion-label":"form-switch-label",w.textContent="Reduce motion";const u=document.createElement("label");u.className=r?"footer-motion-check":"form-switch footer-motion-switch",u.title="Reduce motion",u.appendChild(i),u.appendChild(w),p.appendChild(u)}}if($("#site-sidebar").length){const o=document.querySelector(".site-content"),t=c=>!c.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=c=>o?[...o.querySelectorAll(c)].filter(t):[],s=n("h2").length?n("h2"):n("h3"),e=new Set([...document.querySelectorAll("[id]")].map(c=>c.id)),a=[];for(const c of s){if(!c.id){const v=(c.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let h=v;for(let m=2;e.has(h);m++)h=`${v}-${m}`;e.add(h),c.id=h}a.push({text:c.textContent.trim(),url:`#${c.id}`,section:c.id})}a.length?Domma.elements.sidebar("#site-sidebar",{items:a,fixed:!1,push:!1,collapsible:!1,variant:"theme",scrollSpy:!0,position:document.querySelector(".site-main.sidebar-right")?"right":"left"}):document.querySelector(".site-main.with-sidebar")?.classList.add("no-sidebar-items")}Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const b=$(".page-body");if(b.length){b.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),b.find(".tabs").each(function(){Domma.elements.tabs(this)}),b.find(".carousel").each(function(){const o={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide"};if(this.dataset.animationDuration){const t=parseInt(this.dataset.animationDuration,10);Number.isFinite(t)&&(o.animationDuration=t)}this.dataset.animationEasing&&(o.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(t=>t.cloneNode(!0)),this._dmCarouselOpts=o),this._dmCarousel=Domma.elements.carousel(this,o)}),b.find(".dm-countdown").each(function(){const o={autoStart:!0},t=String(this.dataset.format||"").trim(),n=String(this.dataset.to||"").trim(),s=/^(\d{4})-(\d{2})-(\d{2})$/.exec(n),e=s?new Date(+s[1],+s[2]-1,+s[3]).getTime():n?new Date(n).getTime():NaN;Number.isFinite(e)?o.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(o.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const a=/^dd:/i.test(t);o.format=a?"hh:mm:ss":t||(o.duration>=36e5?"hh:mm:ss":"mm:ss");const c=this,v=m=>String(m).padStart(2,"0"),h=m=>{const C=Math.max(0,Math.floor(m/1e3)),x=c.querySelector(".dm-timer-time");x&&(x.textContent=`${v(Math.floor(C/86400))}:${v(Math.floor(C%86400/3600))}:${v(Math.floor(C%3600/60))}:${v(C%60)}`)};a&&(o.onTick=h),Domma.elements.timer(this,o),a&&h(o.duration||0)}),b.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),b.find(".dm-progression").each(function(){const o=Array.from(this.querySelectorAll(".dm-progression-item")).map(s=>{const e={title:s.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:s.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return s.dataset.date&&(e.date=s.dataset.date),s.dataset.status&&(e.status=s.dataset.status),s.dataset.entryId&&(e._entryId=s.dataset.entryId),e}),t=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",n=Domma.elements.progression(this,{layout:t,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...o.length?{items:o}:{}});o.length&&(this._dmCtx={instance:n,items:o})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}document.querySelectorAll(".page-body .row[data-reveal]").forEach(o=>{const t=o.dataset.revealMode||"stagger",n=o.dataset.revealAnimation||"slide-up",s=parseInt(o.dataset.revealDuration,10)||400,e=parseInt(o.dataset.revealStagger,10)||60,a=parseInt(o.dataset.revealDelay,10)||0,c=o.dataset.revealDirection==="rtl",v=Array.from(o.children),h={"slide-up":"translateY(30px)","slide-down":"translateY(-30px)","slide-left":"translateX(30px)","slide-right":"translateX(-30px)",zoom:"scale(0.85)",flip:"perspective(600px) rotateX(15deg)"};v.forEach((m,C)=>{m.style.opacity="0",m.style.transform=h[n]||"",m.style.transition=`opacity ${s}ms ease, transform ${s}ms ease`;const x=c?v.length-1-C:C;m.style.transitionDelay=t==="stagger"?`${a+x*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const m=new IntersectionObserver(C=>{C.forEach(x=>{x.isIntersecting&&(x.target.offsetWidth,x.target.style.opacity="1",x.target.style.transform="none",m.unobserve(x.target))})},{threshold:.1});v.forEach(C=>m.observe(C))}))}),b.find(".card[data-collapsible]").each(function(){const o=this.querySelector(".card-header");o&&o.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),b.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const o=this.dataset.soTarget,t=document.getElementById(o);if(!t)return;const n=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",n.setContent(t),n.open()})})}if(typeof $.setup=="function"){const o=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const n=N(t.dataset.config);Object.assign(o,n)}catch{}}),Object.keys(o).length>0){const t={};for(const[n,s]of Object.entries(o)){const e=s?.events?.click,{confirm:a,toast:c,alert:v,prompt:h,...m}=e||{};a||c||v||h?($(n).on("click",async function(x){if(x.preventDefault(),a&&!await E.confirm(a))return;let d=null;if(!(h&&(d=await E.prompt(h,{inputPlaceholder:m.promptPlaceholder||"",inputValue:m.promptDefault||""}),d===null))){if(m.target){const l=$(m.target);m.toggleClass&&l.toggleClass(m.toggleClass),m.addClass&&l.addClass(m.addClass),m.removeClass&&l.removeClass(m.removeClass),d!==null&&(m.setText&&l.text(d),m.setVal&&l.val(d),m.setAttr&&l.attr(m.setAttr,d))}m.href&&(window.location.href=m.href),c&&E.toast(c,{type:m.toastType||"success"}),v&&E.alert(v)}}),Object.keys(m).length&&(t[n]={...s,events:{...s.events,click:m}})):t[n]=s}$.setup(t)}}b.length&&z(b.get(0))}),window.wireCTAButtons=z;function z(g){g.querySelectorAll(".dm-cta-trigger").forEach(f=>{f.__ctaWired||(f.__ctaWired=!0,f.addEventListener("click",async()=>{const k=f.dataset.action,y=f.dataset.entry,A=f.dataset.confirm;let b=S.get("auth_token");if(!b){E.toast("Please log in to perform this action.",{type:"warning"});return}if(A&&!await E.confirm(A))return;const o=Array.from(f.childNodes).map(n=>n.cloneNode(!0));f.disabled=!0,f.textContent="Running\u2026";const t=n=>fetch(`/api/actions/${k}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify(f.dataset.scope?{entryId:y,scope:f.dataset.scope}:{entryId:y})});try{let n=await t(b);if(n.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:c}=await a.json();S.set("auth_token",c),b=c,n=await t(b)}}}const s=await n.json().catch(()=>({}));if(!n.ok)throw new Error(s.error||s.message||`Error ${n.status}`);E.toast(s.message||"Action completed.",{type:"success"})}catch(n){E.toast(n.message||"Action failed.",{type:"error"})}finally{f.disabled=!1,f.textContent="",o.forEach(n=>f.appendChild(n)),Domma.icons.scan(f)}}))})}(function(){const f=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!f.length)return;const k=S.get("auth_token");function y(b,o){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const n=document.createElement("p");if(o){n.appendChild(document.createTextNode("Please "));const s=document.createElement("a");s.href="/login",s.textContent="sign in",n.appendChild(s),n.appendChild(document.createTextNode(" to view your entries."))}else n.textContent=b;return t.appendChild(n),t}function A(b,o){const t=document.createElement("template");t.innerHTML=o||"",b.replaceWith(t.content)}f.forEach(async b=>{const o=b.dataset.collectionAttrs;if(!o)return;if(!k){b.replaceChildren(y("",!0));return}const t=n=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify({attrs:o})});try{let n=k,s=await t(n);if(s.status===401){const a=S.get("auth_refresh_token");if(a){const c=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(c.ok){const{token:v}=await c.json();S.set("auth_token",v),n=v,s=await t(n)}}}if(!s.ok){const{error:a}=await s.json().catch(()=>({}));b.replaceChildren(y(a||"Unable to load entries.",!1));return}const{html:e}=await s.json();A(b,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{b.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(),z(document.body)},100)})(),(function(){const f=document.querySelectorAll("[data-collection-table]");!f.length||typeof T>"u"||!T.create||f.forEach(k=>{let y;try{y=N(k.dataset.payload)}catch{return}const{columns:A,rows:b,search:o,sortable:t,exportable:n,pageSize:s,empty:e,ctaConfig:a,slug:c,exportCfg:v}=y;if(!A?.length)return;if(a){const C=A.findIndex(x=>x.key==="_cta");C!==-1&&(A[C]={key:"_cta",title:"",render:(x,d)=>{const l=document.createElement("button");if(l.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,l.dataset.action=a.action||"",a.scope&&(l.dataset.scope=a.scope),l.dataset.entry=d._entryId||"",a.confirm&&(l.dataset.confirm=a.confirm),a.icon){const r=document.createElement("span");r.dataset.icon=a.icon,l.appendChild(r),l.appendChild(document.createTextNode(" "))}return l.appendChild(document.createTextNode(a.label||"Run")),l}})}const h="col-table-"+Math.random().toString(36).slice(2,7),m=document.createElement("div");m.id=h,k.replaceChildren(m),T.create("#"+h,{data:b,columns:A,search:o,sortable:t,exportable:n,pageSize:s,emptyMessage:e}),a&&z(m),k._dmTable={columns:A,rows:b,slug:c,exportCfg:v,render(C){T.create("#"+h,{data:C,columns:A,search:o,sortable:t,exportable:n,pageSize:s,emptyMessage:e}),a&&z(m)}}})})(),(function(){const f=document.querySelectorAll("[data-form-inline]");if(!f.length||typeof F>"u")return;function k(t){const n=t&&t.logic;return!!(n&&(n.visibility||n.requirement))}function y(t,n){const s={};return(t||[]).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)),c=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,v={...e.formConfig||{}};v.span==="full"&&n&&(v.span=n);const h={type:c,required:k(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...e.tooltip&&{tooltip:e.tooltip},...v}};e.minLength!==void 0&&(h.minLength=e.minLength),e.maxLength!==void 0&&(h.maxLength=e.maxLength),e.min!==void 0&&(h.min=e.min),e.max!==void 0&&(h.max=e.max),a&&(e.variant&&(h.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(h.multiple=!0),e.density&&(h.density=e.density),e.columns&&(h.columns=e.columns),e.accent&&(h.accent=e.accent),e.accentStyle&&(h.accentStyle=e.accentStyle),e.glow&&(h.glow=!0),e.glowColour&&(h.glowColour=e.glowColour),e.shadow&&(h.shadow=e.shadow),e.shadowColour&&(h.shadowColour=e.shadowColour)),s[e.name]=h}),s}function A(t){const n={};return(t||[]).forEach(s=>{if(!(!s.name||s.type==="page-break"||s.type==="spacer")&&(s.type==="select"||s.type==="multiselect")&&s.required){const e=(s.options||[])[0];e&&(n[s.name]=typeof e=="object"?e.value:e)}}),n}function b(t,n){(n||[]).forEach(s=>{if(s.type!=="date"||!s.name)return;const e=t.querySelector(`[name="${s.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function o(t,n,s){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=n,e.style.cssText=s?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}f.forEach(t=>{let n;try{n=N(t.dataset.formInline)}catch{return}const s=n.fields||[],e=n.settings||{},a=Number(e.columns)===3?3:2,c=e.layout||"grid",v=s.some(l=>l.type==="page-break"),h=async l=>{if(t.dataset.triggerBlocked==="true"){const r=t._formLogicRuntime,p=r&&r.submitBlock?r.submitBlock().reasons:[];o(t,p.join(" ")||"This form cannot be submitted yet.",!0);const i=new Error("Blocked by a form trigger");throw i.formSubmitFailed=!0,i}try{const r=t.querySelector(".fb-form-honeypot"),p=r?.querySelector('input[type="text"]')||null,i=r?.querySelector('[name="_t"]')||null,w=Object.assign({},l);delete w._t,r?.dataset.hpName&&delete w[r.dataset.hpName],p!==null&&(w._hp=p.value),i!==null&&(w._t=i.value);const u=await H.post(`/api/forms/submit/${n.slug}`,w);if(u&&u.error){o(t,u.error,!0);const q=new Error(u.error);throw q.formSubmitFailed=!0,q}if(u?.redirect){window.location.href=u.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);o(t,u?.message||e.successMessage||"Thank you for your submission.",!1)}catch(r){throw r.formSubmitFailed||o(t,r.message||"Submission failed. Please try again.",!0),r}},m=Date.now();function C(l){const r=l.querySelector("form");if(!r||r.querySelector(".fb-form-honeypot"))return;const p=document.createElement("div");p.className="fb-form-honeypot",p.setAttribute("aria-hidden","true");const i=document.createElement("input");i.name=r.querySelector('[name="website"]')?"_website":"website",p.dataset.hpName=i.name,i.type="text",i.tabIndex=-1,i.autocomplete="url",i.placeholder="https://",p.appendChild(i);const w=document.createElement("input");w.name="_t",w.type="hidden",w.value=m,p.appendChild(w),r.appendChild(p)}function x(l){if(!window.FormLogicEngine||!s.some(p=>p.logic||Array.isArray(p.triggers)&&p.triggers.length))return null;const r=new window.FormLogicEngine.FormLogicRuntime(n,t,l?{baseValues:l}:void 0);if(r.init(),t._formLogicRuntime=r,t._formLogicObserved)return r;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const p=new MutationObserver(function(i){for(const w of i)for(const u of w.removedNodes)if(u===t||u.nodeType===1&&u.contains&&u.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),p.disconnect();return}});p.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=p}return r}let d=!1;if(t.addEventListener("dm-form-end",l=>{const r=l.detail?.validation;if(r&&!r.valid){W(t,r.missing),J(r);return}d||(d=!0,h(l.detail?.values||{}).catch(()=>{}).finally(()=>{d=!1}))}),v&&F.wizard){const l=ee(s,n.title),r=l.map(L=>({title:L.title,description:L.description,fields:y(L.fields,a)})),p=l.map(L=>L.fields);let i=0,w=null,u=null;const q=L=>{const _=window.FormLogicEngine;if(!_||!_.validateFields)return;const M=_.validateFields(t,p[i]||[]);M.valid||(L.preventDefault(),L.stopImmediatePropagation(),W(t,M.missing),J(M))},D=()=>{const L=p[i]||s;oe(t,e),Y(t,e,L),b(t,L),V(t,L),e.honeypot!==!1&&C(t),w&&w.destroy(),w=x(u?.getStepData?u.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(_=>_.addEventListener("click",q,!0))};u=F.wizard(t,{schema:{steps:r},layout:c,columns:a,finishText:e.submitText||"Finish",onSubmit:h,onStepChange:L=>{i=L,setTimeout(D,0)}}),t.addEventListener("dm-form-trigger",L=>{if(L.detail?.type!=="goto-step"||!u?.goToStep)return;const _=Number(L.detail.action.step);!Number.isFinite(_)||_<0||_>=r.length||_!==i&&u.goToStep(_)}),Promise.resolve(u).then(D)}else if(F.render){const l=F.render(t,y(s,a),A(s),{submitText:e.submitText||"Submit",layout:c,columns:a,onSubmit:h});Promise.resolve(l).then(function(){te(t,e,c),Y(t,e,s),b(t,s),V(t,s),e.honeypot!==!1&&C(t),x()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();function ae(g,f){const k=document.body,y=k.classList,A=new Set((k.dataset.railSite||"").split(" ").filter(Boolean)),b=()=>window.matchMedia("(min-width: 993px)").matches;g.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const c=a.textContent.trim();c&&(a.setAttribute("title",c),a.querySelector("svg, [data-icon]")||(a.dataset.initial=c.charAt(0).toUpperCase()))});const o=document.createElement("button");o.type="button",o.className="dm-nav-collapse-toggle";const t=()=>{const a=y.contains("dm-vnav-collapsed");o.setAttribute("aria-pressed",String(a)),o.setAttribute("aria-label",a?"Expand menu":"Collapse menu"),o.title=o.getAttribute("aria-label");const c=f?"chevrons-left":"chevrons-right",v=f?"chevrons-right":"chevrons-left";o.innerHTML=`<span data-icon="${a?c:v}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(o)},n=a=>{y.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),t()};o.addEventListener("click",()=>n(!y.contains("dm-vnav-collapsed"))),t();const s=g.querySelector(".navbar-container")||g;s.insertBefore(o,s.firstChild?.nextSibling||null);const e=a=>{y.toggle("dm-vnav-autohide",a),y.toggle("dm-vnav-collapsible",!a&&A.has("collapsible")),a?y.remove("dm-vnav-collapsed"):A.has("collapsible")&&S.get("nav_collapsed")===!0&&y.add("dm-vnav-collapsed"),g.classList.remove("dm-nav-revealed"),t()};g.addEventListener("pointerdown",a=>{y.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!g.classList.contains("dm-nav-revealed")&&(g.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{g.contains(a.target)||g.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(g,{items:()=>{const a=y.contains("dm-vnav-autohide"),c=y.contains("dm-vnav-collapsed"),v=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:c?"Expand menu":"Collapse to icons",icon:c?"maximize":"minimize",disabled:!b()||a||!A.has("collapsible"),action:()=>n(!c)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!b(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!v,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(A.has("autohide")),y.toggle("dm-vnav-collapsed",!A.has("autohide")&&A.has("start-collapsed")),t()}}]}})}function ne(g){g.classList.add("dm-nav-scroll-hide");let f=window.scrollY,k=!1;window.addEventListener("scroll",()=>{k||(k=!0,requestAnimationFrame(()=>{k=!1;const y=window.scrollY,A=g.matches(":focus-within")||g.querySelector(".navbar-dropdown.open, .navbar-collapse.show");y>f&&y>g.offsetHeight&&!A?g.classList.add("dm-nav-scrolled-away"):y<f&&g.classList.remove("dm-nav-scrolled-away"),f=y}))},{passive:!0})}
|
|
1
|
+
import{decodeBase64Json as B}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as j,applyColour as P,applyPill as U,applyFloat as G,applyHighlight as O,applyItemHighlight as K,itemHighlightAttrs as Q,isCurrentItem as R,menuAlign as Z,applyClasses as N,cssClasses as W}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{initFloatingMenus as ee}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as V,markInvalidFields as Y,showValidationPopup as J}from"/public/js/forms.js?v=20261009-form-classes";import{buildWizardSteps as te}from"/public/js/form-steps.mjs";import{applyFormClasses as X,applySubmitButtons as ne,applyWizardButtons as oe}from"/public/js/form-submit.mjs";$(()=>{const p=window.__CMS_NAV__||{},d=window.__CMS_SITE__||{};if(d.autoTheme?.enabled){let t=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>=t(n.dayStart)&&a<t(n.nightStart)?n.dayTheme:n.nightTheme},o=function(e){const a=n.apply&&n.apply[e]||{theme:e,custom:""};Domma.theme.set(a.theme);for(const h of[...document.body.classList])h.startsWith("dm-custom-")&&document.body.classList.remove(h);a.custom&&document.body.classList.add(a.custom)};const n=d.autoTheme;o(s()),setInterval(()=>o(s()),6e4)}const L=$("#site-navbar");if(L.length&&p.brand){let s=function(c,m=0){return Array.isArray(c)?c.filter(l=>!(l&&(l.type==="spacer"||l.type==="theme-switcher"))).filter(l=>!(l&&l.type==="separator"&&m===0)).map(l=>l&&l.type==="separator"?{divider:!0}:Array.isArray(l?.items)?{...l,items:s(l.items,m+1)}:l):c},o=function(c){return Array.isArray(c)?c.filter(m=>!(m&&(m.type==="separator"||m.type==="spacer"||m.type==="theme-switcher"))):[]};const n={...p.brand},t=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let c="";n.logo?c+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(c+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(c+=`<span class="navbar-brand-text${t}">${n.text}</span>`),n.tagline&&(c+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=c}else t&&n.text&&(n.html=`<span class="navbar-brand-text${t}">${n.text}</span>`);const e=p.position==="floating",a=p.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:s(p.items||[]),variant:p.variant||"dark",position:e?"static":p.position||"sticky",collapsible:!0,appearOnHover:!!p.appearOnHover}),Domma.icons.scan("#site-navbar");const h=L.get(0);h.classList.add(`dm-nav-align-${Z(p)||"centre"}`),a&&L.get(0).classList.add("dm-nav-vertical",p.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const w=p.dock||{};a&&!e&&ae(h,p.side==="right"),!a&&w.hideOnScroll&&se(h),O(h,p),N(h,p.class),e&&(G(L.get(0),p),ee(L.get(0).parentNode||document));const i=h.querySelector(".navbar-toggle"),u=h.querySelector(".navbar-collapse");if(i&&u){let c=null;const m=()=>{u.classList.contains("show")&&i.click()},l=()=>{c&&(c.remove(),c=null),document.body.classList.remove("dm-drawer-open")},f=()=>{c||(c=document.createElement("div"),c.className="dm-navbar-backdrop",c.addEventListener("click",m),document.body.appendChild(c),document.body.classList.add("dm-drawer-open"))};i.addEventListener("click",()=>{requestAnimationFrame(()=>{u.classList.contains("show")?f():l()})}),u.addEventListener("click",r=>{r.target.closest("a")&&requestAnimationFrame(l)}),document.addEventListener("keydown",r=>{r.key==="Escape"&&u.classList.contains("show")&&m()})}const y=h.querySelector(".navbar-nav");if(y){const c=Array.from(y.children);let m=0;for(const l of p.items||[])if(!(l&&l.type==="separator")){if(l&&l.type==="spacer"){const f=document.createElement("li"),r=["xs","sm","md","lg","xl","flex"].includes(l.size)?l.size:"md";f.className=`dm-menu-spacer dm-menu-spacer--${r}`,f.setAttribute("aria-hidden","true"),y.insertBefore(f,c[m]||null);continue}if(l&&l.type==="theme-switcher"){const f=document.createElement("li");f.className="dm-menu-theme-switcher";const r=document.createElement("span");r.dataset.themeSwitcher="menu",r.dataset.position="inline",f.appendChild(r),y.insertBefore(f,c[m]||null);continue}m++}window.CMSThemeSwitcher?.scan(y)}const b=o(p.items||[]);h.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(c=>{const m=b[parseInt(c.dataset.index,10)];if(!m)return;const l=c.dataset.subindex!=null,f=l&&Array.isArray(m.items)?m.items[parseInt(c.dataset.subindex,10)]:m;if(!f)return;f.colour&&P(c,f.colour),f.pill&&!l&&U(c,f.pill),K(c,f),N(c.closest("li"),f.class);const r=j(f.badge);r&&c.appendChild(r),R(f.url,location.pathname)&&(c.classList.add("active"),c.setAttribute("aria-current","page"))})}const v=$("#site-footer");if(v.length){let e=function(r){return String(r??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},a=function(r){return e(r).replace(/'/g,"'")},h=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 x=a(r.url||"#"),g=e(r.text||""),D=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(h).join("")}</ul>`:"",q=Q(r),A=[q.hover&&` data-menu-item-hover="${a(q.hover)}"`,q.active&&` data-menu-item-active="${a(q.active)}"`].filter(Boolean).join(""),M=q.accent?` style="--dm-menu-highlight:${a(q.accent)}"`:"",I=W(r.class).join(" ");return`<li${I?` class="${a(I)}"`:""}><a href="${x}"${A}${M}>${g}</a>${D}</li>`},w=function(r,x){return(r||[]).forEach(g=>{g&&(g.type==="separator"||g.type==="spacer"||g.type==="theme-switcher")||(x.push(g),Array.isArray(g.items)&&g.items.length&&w(g.items,x))}),x};const n=d.social||{},t={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 o=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(o.copyright&&(s+=`<p>${e(o.copyright)}</p>`),o.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${o.primary.map(h).join("")}</ul></nav>`),o.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${o.legal.map(h).join("")}</ul></nav>`),d.footer){const r=Object.keys(t).filter(g=>n[g]),x=W(d.footer.socialClass).join(" ");r.length&&(s+='<div class="footer-social">',r.forEach(g=>{const{label:D,svg:q}=t[g];s+=`<a href="${n[g]}" target="_blank" rel="noopener noreferrer" aria-label="${D}" class="footer-social-link${x?` ${a(x)}`:""}">${q}</a>`}),s+="</div>")}s+="</div>",v.html(s),window.CMSThemeSwitcher?.scan(v.get(0)),N(v.get(0),d.footer?.class);const i=[...w(o.primary,[]),...w(o.legal,[])];v.get(0).querySelectorAll(".footer-nav a").forEach((r,x)=>{const g=i[x];if(!g)return;g.colour&&P(r,g.colour);const D=j(g.badge);D&&r.appendChild(D),R(g.url,location.pathname)&&(r.classList.add("is-active"),r.setAttribute("aria-current","page"))}),O(v.get(0).querySelector(".footer-nav--primary"),{highlight:o.primaryHighlight}),O(v.get(0).querySelector(".footer-nav--legal"),{highlight:o.legalHighlight}),N(v.get(0).querySelector(".footer-nav--primary"),o.primaryClass),N(v.get(0).querySelector(".footer-nav--legal"),o.legalClass);const y=d.footer?.showMotionToggle!==!1,b=S.get("reduced_motion"),c=d.footer?.motionDefault,m=b!=null?!!b:c==="on"?!0:c==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),l=d.footer?.motionControl==="checkbox",f=v.get(0).querySelector(".footer-inner");if(y&&f){const r=document.createElement("input");r.type="checkbox",r.className=l?"footer-motion-checkbox":"form-switch-input",r.id="dm-motion-switch",r.checked=m,r.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const x=document.createElement("span");x.className=l?"footer-motion-label":"form-switch-label",x.textContent="Reduce motion";const g=document.createElement("label");g.className=l?"footer-motion-check":"form-switch footer-motion-switch",g.title="Reduce motion",g.appendChild(r),g.appendChild(x),f.appendChild(g)}}const k=document.getElementById("site-sidebar");k&&ie(k),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const C=$(".page-body");if(C.length){C.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),C.find(".tabs").each(function(){Domma.elements.tabs(this)}),C.find(".carousel").each(function(){const n={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide"};if(this.dataset.animationDuration){const t=parseInt(this.dataset.animationDuration,10);Number.isFinite(t)&&(n.animationDuration=t)}this.dataset.animationEasing&&(n.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(t=>t.cloneNode(!0)),this._dmCarouselOpts=n),this._dmCarousel=Domma.elements.carousel(this,n)}),C.find(".dm-countdown").each(function(){const n={autoStart:!0},t=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),o=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=o?new Date(+o[1],+o[2]-1,+o[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(t);n.format=a?"hh:mm:ss":t||(n.duration>=36e5?"hh:mm:ss":"mm:ss");const h=this,w=u=>String(u).padStart(2,"0"),i=u=>{const y=Math.max(0,Math.floor(u/1e3)),b=h.querySelector(".dm-timer-time");b&&(b.textContent=`${w(Math.floor(y/86400))}:${w(Math.floor(y%86400/3600))}:${w(Math.floor(y%3600/60))}:${w(y%60)}`)};a&&(n.onTick=i),Domma.elements.timer(this,n),a&&i(n.duration||0)}),C.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),C.find(".dm-progression").each(function(){const n=Array.from(this.querySelectorAll(".dm-progression-item")).map(o=>{const e={title:o.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:o.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return o.dataset.date&&(e.date=o.dataset.date),o.dataset.status&&(e.status=o.dataset.status),o.dataset.entryId&&(e._entryId=o.dataset.entryId),e}),t=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:t,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...n.length?{items:n}:{}});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 t=n.dataset.revealMode||"stagger",s=n.dataset.revealAnimation||"slide-up",o=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,a=parseInt(n.dataset.revealDelay,10)||0,h=n.dataset.revealDirection==="rtl",w=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)"};w.forEach((u,y)=>{u.style.opacity="0",u.style.transform=i[s]||"",u.style.transition=`opacity ${o}ms ease, transform ${o}ms ease`;const b=h?w.length-1-y:y;u.style.transitionDelay=t==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const u=new IntersectionObserver(y=>{y.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",u.unobserve(b.target))})},{threshold:.1});w.forEach(y=>u.observe(y))}))}),C.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),C.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,t=document.getElementById(n);if(!t)return;const s=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",s.setContent(t),s.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const s=B(t.dataset.config);Object.assign(n,s)}catch{}}),Object.keys(n).length>0){const t={};for(const[s,o]of Object.entries(n)){const e=o?.events?.click,{confirm:a,toast:h,alert:w,prompt:i,...u}=e||{};a||h||w||i?($(s).on("click",async function(b){if(b.preventDefault(),a&&!await E.confirm(a))return;let c=null;if(!(i&&(c=await E.prompt(i,{inputPlaceholder:u.promptPlaceholder||"",inputValue:u.promptDefault||""}),c===null))){if(u.target){const m=$(u.target);u.toggleClass&&m.toggleClass(u.toggleClass),u.addClass&&m.addClass(u.addClass),u.removeClass&&m.removeClass(u.removeClass),c!==null&&(u.setText&&m.text(c),u.setVal&&m.val(c),u.setAttr&&m.attr(u.setAttr,c))}u.href&&(window.location.href=u.href),h&&E.toast(h,{type:u.toastType||"success"}),w&&E.alert(w)}}),Object.keys(u).length&&(t[s]={...o,events:{...o.events,click:u}})):t[s]=o}$.setup(t)}}C.length&&z(C.get(0))}),window.wireCTAButtons=z;function z(p){p.querySelectorAll(".dm-cta-trigger").forEach(d=>{d.__ctaWired||(d.__ctaWired=!0,d.addEventListener("click",async()=>{const L=d.dataset.action,v=d.dataset.entry,k=d.dataset.confirm;let C=S.get("auth_token");if(!C){E.toast("Please log in to perform this action.",{type:"warning"});return}if(k&&!await E.confirm(k))return;const n=Array.from(d.childNodes).map(s=>s.cloneNode(!0));d.disabled=!0,d.textContent="Running\u2026";const t=s=>fetch(`/api/actions/${L}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(d.dataset.scope?{entryId:v,scope:d.dataset.scope}:{entryId:v})});try{let s=await t(C);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:h}=await a.json();S.set("auth_token",h),C=h,s=await t(C)}}}const o=await s.json().catch(()=>({}));if(!s.ok)throw new Error(o.error||o.message||`Error ${s.status}`);E.toast(o.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{d.disabled=!1,d.textContent="",n.forEach(s=>d.appendChild(s)),Domma.icons.scan(d)}}))})}(function(){const d=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!d.length)return;const L=S.get("auth_token");function v(C,n){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(n){s.appendChild(document.createTextNode("Please "));const o=document.createElement("a");o.href="/login",o.textContent="sign in",s.appendChild(o),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=C;return t.appendChild(s),t}function k(C,n){const t=document.createElement("template");t.innerHTML=n||"",C.replaceWith(t.content)}d.forEach(async C=>{const n=C.dataset.collectionAttrs;if(!n)return;if(!L){C.replaceChildren(v("",!0));return}const t=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:n})});try{let s=L,o=await t(s);if(o.status===401){const a=S.get("auth_refresh_token");if(a){const h=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(h.ok){const{token:w}=await h.json();S.set("auth_token",w),s=w,o=await t(s)}}}if(!o.ok){const{error:a}=await o.json().catch(()=>({}));C.replaceChildren(v(a||"Unable to load entries.",!1));return}const{html:e}=await o.json();k(C,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{C.replaceChildren(v("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),z(document.body)},100)})(),(function(){const d=document.querySelectorAll("[data-collection-table]");!d.length||typeof T>"u"||!T.create||d.forEach(L=>{let v;try{v=B(L.dataset.payload)}catch{return}const{columns:k,rows:C,search:n,sortable:t,exportable:s,pageSize:o,empty:e,ctaConfig:a,slug:h,exportCfg:w}=v;if(!k?.length)return;if(a){const y=k.findIndex(b=>b.key==="_cta");y!==-1&&(k[y]={key:"_cta",title:"",render:(b,c)=>{const m=document.createElement("button");if(m.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,m.dataset.action=a.action||"",a.scope&&(m.dataset.scope=a.scope),m.dataset.entry=c._entryId||"",a.confirm&&(m.dataset.confirm=a.confirm),a.icon){const l=document.createElement("span");l.dataset.icon=a.icon,m.appendChild(l),m.appendChild(document.createTextNode(" "))}return m.appendChild(document.createTextNode(a.label||"Run")),m}})}const i="col-table-"+Math.random().toString(36).slice(2,7),u=document.createElement("div");u.id=i,L.replaceChildren(u),T.create("#"+i,{data:C,columns:k,search:n,sortable:t,exportable:s,pageSize:o,emptyMessage:e}),a&&z(u),L._dmTable={columns:k,rows:C,slug:h,exportCfg:w,render(y){T.create("#"+i,{data:y,columns:k,search:n,sortable:t,exportable:s,pageSize:o,emptyMessage:e}),a&&z(u)}}})})(),(function(){const d=document.querySelectorAll("[data-form-inline]");if(!d.length||typeof F>"u")return;function L(t){const s=t&&t.logic;return!!(s&&(s.visibility||s.requirement))}function v(t,s){const o={};return(t||[]).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)),h=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,w={...e.formConfig||{}};w.span==="full"&&s&&(w.span=s);const i={type:h,required:L(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},...w}};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)),o[e.name]=i}),o}function k(t){const s={};return(t||[]).forEach(o=>{if(!(!o.name||o.type==="page-break"||o.type==="spacer")&&(o.type==="select"||o.type==="multiselect")&&o.required){const e=(o.options||[])[0];e&&(s[o.name]=typeof e=="object"?e.value:e)}}),s}function C(t,s){(s||[]).forEach(o=>{if(o.type!=="date"||!o.name)return;const e=t.querySelector(`[name="${o.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(t,s,o){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=s,e.style.cssText=o?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}d.forEach(t=>{let s;try{s=B(t.dataset.formInline)}catch{return}const o=s.fields||[],e=s.settings||{},a=Number(e.columns)===3?3:2,h=e.layout||"grid",w=o.some(m=>m.type==="page-break"),i=async m=>{if(t.dataset.triggerBlocked==="true"){const l=t._formLogicRuntime,f=l&&l.submitBlock?l.submitBlock().reasons:[];n(t,f.join(" ")||"This form cannot be submitted yet.",!0);const r=new Error("Blocked by a form trigger");throw r.formSubmitFailed=!0,r}try{const l=t.querySelector(".fb-form-honeypot"),f=l?.querySelector('input[type="text"]')||null,r=l?.querySelector('[name="_t"]')||null,x=Object.assign({},m);delete x._t,l?.dataset.hpName&&delete x[l.dataset.hpName],f!==null&&(x._hp=f.value),r!==null&&(x._t=r.value);const g=await H.post(`/api/forms/submit/${s.slug}`,x);if(g&&g.error){n(t,g.error,!0);const D=new Error(g.error);throw D.formSubmitFailed=!0,D}if(g?.redirect){window.location.href=g.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);n(t,g?.message||e.successMessage||"Thank you for your submission.",!1)}catch(l){throw l.formSubmitFailed||n(t,l.message||"Submission failed. Please try again.",!0),l}},u=Date.now();function y(m){const l=m.querySelector("form");if(!l||l.querySelector(".fb-form-honeypot"))return;const f=document.createElement("div");f.className="fb-form-honeypot",f.setAttribute("aria-hidden","true");const r=document.createElement("input");r.name=l.querySelector('[name="website"]')?"_website":"website",f.dataset.hpName=r.name,r.type="text",r.tabIndex=-1,r.autocomplete="url",r.placeholder="https://",f.appendChild(r);const x=document.createElement("input");x.name="_t",x.type="hidden",x.value=u,f.appendChild(x),l.appendChild(f)}function b(m){if(!window.FormLogicEngine||!o.some(f=>f.logic||Array.isArray(f.triggers)&&f.triggers.length))return null;const l=new window.FormLogicEngine.FormLogicRuntime(s,t,m?{baseValues:m}:void 0);if(l.init(),t._formLogicRuntime=l,t._formLogicObserved)return l;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const f=new MutationObserver(function(r){for(const x of r)for(const g of x.removedNodes)if(g===t||g.nodeType===1&&g.contains&&g.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),f.disconnect();return}});f.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=f}return l}let c=!1;if(t.addEventListener("dm-form-end",m=>{const l=m.detail?.validation;if(l&&!l.valid){Y(t,l.missing),J(l);return}c||(c=!0,i(m.detail?.values||{}).catch(()=>{}).finally(()=>{c=!1}))}),w&&F.wizard){const m=te(o,s.title),l=m.map(A=>({title:A.title,description:A.description,fields:v(A.fields,a)})),f=m.map(A=>A.fields);let r=0,x=null,g=null;const D=A=>{const M=window.FormLogicEngine;if(!M||!M.validateFields)return;const I=M.validateFields(t,f[r]||[]);I.valid||(A.preventDefault(),A.stopImmediatePropagation(),Y(t,I.missing),J(I))},q=()=>{const A=f[r]||o;oe(t,e),X(t,e,A),C(t,A),V(t,A),e.honeypot!==!1&&y(t),x&&x.destroy(),x=b(g?.getStepData?g.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(M=>M.addEventListener("click",D,!0))};g=F.wizard(t,{schema:{steps:l},layout:h,columns:a,finishText:e.submitText||"Finish",onSubmit:i,onStepChange:A=>{r=A,setTimeout(q,0)}}),t.addEventListener("dm-form-trigger",A=>{if(A.detail?.type!=="goto-step"||!g?.goToStep)return;const M=Number(A.detail.action.step);!Number.isFinite(M)||M<0||M>=l.length||M!==r&&g.goToStep(M)}),Promise.resolve(g).then(q)}else if(F.render){const m=F.render(t,v(o,a),k(o),{submitText:e.submitText||"Submit",layout:h,columns:a,onSubmit:i});Promise.resolve(m).then(function(){ne(t,e,h),X(t,e,o),C(t,o),V(t,o),e.honeypot!==!1&&y(t),b()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();function ae(p,d){const L=document.body,v=L.classList,k=new Set((L.dataset.railSite||"").split(" ").filter(Boolean)),C=()=>window.matchMedia("(min-width: 993px)").matches;p.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const h=a.textContent.trim();h&&(a.setAttribute("title",h),a.querySelector("svg, [data-icon]")||(a.dataset.initial=h.charAt(0).toUpperCase()))});const n=document.createElement("button");n.type="button",n.className="dm-nav-collapse-toggle";const t=()=>{const a=v.contains("dm-vnav-collapsed");n.setAttribute("aria-pressed",String(a)),n.setAttribute("aria-label",a?"Expand menu":"Collapse menu"),n.title=n.getAttribute("aria-label");const h=d?"chevrons-left":"chevrons-right",w=d?"chevrons-right":"chevrons-left";n.innerHTML=`<span data-icon="${a?h:w}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(n)},s=a=>{v.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),t()};n.addEventListener("click",()=>s(!v.contains("dm-vnav-collapsed"))),t();const o=p.querySelector(".navbar-container")||p;o.insertBefore(n,o.firstChild?.nextSibling||null);const e=a=>{v.toggle("dm-vnav-autohide",a),v.toggle("dm-vnav-collapsible",!a&&k.has("collapsible")),a?v.remove("dm-vnav-collapsed"):k.has("collapsible")&&S.get("nav_collapsed")===!0&&v.add("dm-vnav-collapsed"),p.classList.remove("dm-nav-revealed"),t()};p.addEventListener("pointerdown",a=>{v.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!p.classList.contains("dm-nav-revealed")&&(p.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{p.contains(a.target)||p.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(p,{items:()=>{const a=v.contains("dm-vnav-autohide"),h=v.contains("dm-vnav-collapsed"),w=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:h?"Expand menu":"Collapse to icons",icon:h?"maximize":"minimize",disabled:!C()||a||!k.has("collapsible"),action:()=>s(!h)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!C(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!w,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(k.has("autohide")),v.toggle("dm-vnav-collapsed",!k.has("autohide")&&k.has("start-collapsed")),t()}}]}})}function se(p){p.classList.add("dm-nav-scroll-hide");let d=window.scrollY,L=!1;window.addEventListener("scroll",()=>{L||(L=!0,requestAnimationFrame(()=>{L=!1;const v=window.scrollY,k=p.matches(":focus-within")||p.querySelector(".navbar-dropdown.open, .navbar-collapse.show");v>d&&v>p.offsetHeight&&!k?p.classList.add("dm-nav-scrolled-away"):v<d&&p.classList.remove("dm-nav-scrolled-away"),d=v}))},{passive:!0})}function ie(p){let d={};try{d=JSON.parse(p.dataset.contents||"{}")}catch{}const L=p.closest(".site-main"),v=document.querySelector(".site-content"),k=new Set(String(d.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),C=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=i=>(v?[...v.querySelectorAll(i)]:[]).filter(C).filter(u=>!k.has(u.textContent.trim().toLowerCase())),t=d.headings==="h2"?n("h2"):d.headings==="h3"?n("h3"):d.headings==="h2h3"?n("h2, h3"):n("h2").length?n("h2"):n("h3");if(!t.length){L?.classList.add("no-sidebar-items");return}const s=new Set([...document.querySelectorAll("[id]")].map(i=>i.id)),o=document.createElement("nav");o.className="dm-contents",o.setAttribute("aria-label",d.title||"Page contents"),d.hover&&(o.dataset.menuHover=d.hover),d.selected&&(o.dataset.menuActive=d.selected),d.accent&&o.style.setProperty("--dm-menu-highlight",d.accent.startsWith("#")?d.accent:`var(--dm-${d.accent})`),d.font&&(o.style.fontFamily=d.font),d.fontSize&&(o.style.fontSize=d.fontSize),d.fontWeight&&(o.style.fontWeight=d.fontWeight);const e=document.createElement("div");e.className="dm-contents-head";const a=document.createElement("p");a.className="dm-contents-title",a.textContent=d.title||"",e.appendChild(a),o.appendChild(e);const h=document.createElement("ol");h.className="dm-contents-list"+(d.numbered?" is-numbered":"");const w=[];for(const i of t){if(!i.id){const b=(i.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let c=b;for(let m=2;s.has(c);m++)c=`${b}-${m}`;s.add(c),i.id=c}const u=document.createElement("li");u.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const y=document.createElement("a");y.href=`#${i.id}`,y.textContent=i.textContent.trim(),u.appendChild(y),h.appendChild(u),w.push([i,y])}if(o.appendChild(h),p.replaceChildren(o),d.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,u=()=>{const b=w.find(([c])=>i.get(c))||null;w.forEach(([,c])=>{const m=b&&c===b[1];c.classList.toggle("is-active",!!m),m?c.setAttribute("aria-current","location"):c.removeAttribute("aria-current")})},y=new IntersectionObserver(b=>{b.forEach(c=>i.set(c.target,c.isIntersecting)),u()},{rootMargin:"-80px 0px -60% 0px"});w.forEach(([b])=>y.observe(b))}if(L?.classList.contains("contents-collapsible")){const i=document.createElement("button");i.type="button",i.className="dm-contents-toggle";const u=()=>{const y=L.classList.contains("contents-collapsed");i.setAttribute("aria-expanded",String(!y)),i.setAttribute("aria-label",y?"Show the contents":"Hide the contents"),i.title=i.getAttribute("aria-label"),i.innerHTML=`<span data-icon="${y?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(i)};i.addEventListener("click",()=>{S.set("contents_collapsed",L.classList.toggle("contents-collapsed")),u()}),u(),e.insertBefore(i,e.firstChild)}if(L?.classList.contains("contents-floating")&&d.movable){o.classList.add("is-movable");const i=S.get("contents_position");i&&Number.isFinite(i.x)&&Object.assign(p.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",u=>{if(u.target.closest("button"))return;const y=p.getBoundingClientRect(),b=u.clientX-y.left,c=u.clientY-y.top,m=f=>{const r=Math.max(0,Math.min(window.innerWidth-y.width,f.clientX-b)),x=Math.max(0,Math.min(window.innerHeight-40,f.clientY-c));Object.assign(p.style,{left:`${r}px`,top:`${x}px`,right:"auto",bottom:"auto"})},l=()=>{document.removeEventListener("pointermove",m),S.set("contents_position",{x:parseInt(p.style.left,10),y:parseInt(p.style.top,10)})};document.addEventListener("pointermove",m),document.addEventListener("pointerup",l,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(p,{items:()=>{const i=L?.classList.contains("contents-collapsible"),u=L?.classList.contains("contents-collapsed"),y=w.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:u?"Show the contents":"Collapse to a button",icon:u?"list":"minimize",action:()=>p.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!y,action:async()=>{const b=`${location.origin}${location.pathname}#${y[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"})}]}}),L?.classList.contains("contents-autohide")&&(p.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!p.classList.contains("is-revealed")&&(p.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{p.contains(i.target)||p.classList.remove("is-revealed")}))}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* `_` is outside the key alphabet (slugify below), so no layout can shadow the last two.
|
|
14
14
|
*/
|
|
15
|
+
import {normaliseContents} from '../../services/pageContents.js';
|
|
15
16
|
import {getConfig, saveConfig} from '../../config.js';
|
|
16
17
|
import {
|
|
17
18
|
authenticate as defaultAuthenticate,
|
|
@@ -77,6 +78,8 @@ function sanitisePreset(data) {
|
|
|
77
78
|
footer: data.footer !== false,
|
|
78
79
|
sidebar: data.sidebar === true,
|
|
79
80
|
sidebarSide: data.sidebarSide === 'right' ? 'right' : 'left',
|
|
81
|
+
// The page contents list's settings (pageContents.js); empty when none are set.
|
|
82
|
+
...(Object.keys(normaliseContents(data.contents)).length && {contents: normaliseContents(data.contents)}),
|
|
80
83
|
width: VALID_WIDTHS.has(data.width) ? data.width : 'normal',
|
|
81
84
|
bgColor: data.bgColor && BG_COLOR_RE.test(data.bgColor) ? data.bgColor : '',
|
|
82
85
|
bgImage: typeof data.bgImage === 'string' ? data.bgImage.trim() : '',
|
|
@@ -13,6 +13,7 @@ import {applyTransforms, getSanitizeExtensions, getShortcodeProcessors} from './
|
|
|
13
13
|
import {getConfig} from '../config.js';
|
|
14
14
|
import {getCollection, listEntries} from './collections.js';
|
|
15
15
|
import {getMenu, navbarOverrideFromAttrs, resolveLocation, resolveMenuDecorations} from './menus.js';
|
|
16
|
+
import {contentsOverrideFromAttrs} from './pageContents.js';
|
|
16
17
|
import {buildMenuNav} from './menuRender.js';
|
|
17
18
|
import {checkVisibility} from '../middleware/auth.js';
|
|
18
19
|
import {getRoleHierarchy, getRoleLevel} from './roles.js';
|
|
@@ -1840,8 +1841,13 @@ function processEffectsBlocks(markdown) {
|
|
|
1840
1841
|
apply('scribe', (attrStr, body) => {
|
|
1841
1842
|
if (body === null) return '';
|
|
1842
1843
|
const attrs = parseShortcodeAttrs(attrStr);
|
|
1844
|
+
// Composer attributes use HTML entities for literal quotes and ampersands.
|
|
1845
|
+
const entities = {amp: '&', quot: '"', apos: "'", lt: '<', gt: '>'};
|
|
1846
|
+
for (const key of Object.keys(attrs)) {
|
|
1847
|
+
if (typeof attrs[key] === 'string') attrs[key] = attrs[key].replace(/&(amp|quot|apos|lt|gt);/g, (_, name) => entities[name]);
|
|
1848
|
+
}
|
|
1843
1849
|
const dataAttrs = Object.entries(attrs).map(([k, v]) => ` data-fx-${k}="${escapeAttr(v)}"`).join('');
|
|
1844
|
-
const hasActions = /\[\s*(render|wait|undo|redraw)\b
|
|
1850
|
+
const hasActions = /\[\s*(render|wait|undo|redraw)\b/i.test(body);
|
|
1845
1851
|
if (!hasActions) {
|
|
1846
1852
|
// Restore code placeholders before the nested parse (tabs-handler
|
|
1847
1853
|
// pattern) so code spans/fences render as <code>, not raw backticks.
|
|
@@ -1851,15 +1857,16 @@ function processEffectsBlocks(markdown) {
|
|
|
1851
1857
|
const actions = [];
|
|
1852
1858
|
const actionRe = /\[render([^\]]*)\]([\s\S]*?)\[\/render\]|\[wait\]([\s\S]*?)\[\/wait\]|\[undo([^\]]*?)\/\]|\[redraw([^\]]*)\]([\s\S]*?)\[\/redraw\]/gi;
|
|
1853
1859
|
for (const match of body.matchAll(actionRe)) {
|
|
1854
|
-
|
|
1860
|
+
const tag = match[0].toLowerCase();
|
|
1861
|
+
if (tag.startsWith('[render')) {
|
|
1855
1862
|
const rAttrs = parseShortcodeAttrs(match[1] || '');
|
|
1856
1863
|
const action = {render: match[2].trim()};
|
|
1857
1864
|
if (rAttrs.effect) action.effect = rAttrs.effect;
|
|
1858
1865
|
actions.push(action);
|
|
1859
|
-
} else if (
|
|
1866
|
+
} else if (tag.startsWith('[wait')) {
|
|
1860
1867
|
const val = match[3].trim();
|
|
1861
|
-
actions.push({wait: isNaN(val) ? val :
|
|
1862
|
-
} else if (
|
|
1868
|
+
actions.push({wait: isNaN(val) ? val : Number(val)});
|
|
1869
|
+
} else if (tag.startsWith('[undo')) {
|
|
1863
1870
|
const uAttrs = parseShortcodeAttrs(match[4] || '');
|
|
1864
1871
|
if (uAttrs.all === 'true') actions.push({undoRender: 'all'});
|
|
1865
1872
|
else if (uAttrs.count) actions.push({undoRender: parseInt(uAttrs.count, 10)});
|
|
@@ -3633,6 +3640,26 @@ function processSpacerBlocks(markdown) {
|
|
|
3633
3640
|
* [navbar autohide="true" width="200" /]
|
|
3634
3641
|
* [navbar position="floating" float-anchor="TR" float-collapsible="true" /]
|
|
3635
3642
|
*/
|
|
3643
|
+
/**
|
|
3644
|
+
* [contents ... /] - this page's own settings for its contents list (the
|
|
3645
|
+
* sidebar a layout builds from the page's headings), over the layout's. See
|
|
3646
|
+
* pageContents.js for the attributes. Leaves a hidden marker the renderer reads
|
|
3647
|
+
* (the first tag on a page counts); draws nothing itself.
|
|
3648
|
+
*
|
|
3649
|
+
* [contents side="right" numbered="true" /]
|
|
3650
|
+
* [contents headings="h2h3" title="On this page" exclude="API Reference" /]
|
|
3651
|
+
* [contents floating="true" float-anchor="BR" movable="true" /]
|
|
3652
|
+
* [contents hidden="true" /]
|
|
3653
|
+
*/
|
|
3654
|
+
function processContentsBlocks(markdown) {
|
|
3655
|
+
const {scrubbed, restore} = scrubCodeRegions(markdown);
|
|
3656
|
+
return restore(scrubbed.replace(/\[contents(\s[^\]]*?)?\/?\]/gi, (_, attrStr) => {
|
|
3657
|
+
const override = contentsOverrideFromAttrs(parseShortcodeAttrs(attrStr || ''));
|
|
3658
|
+
if (!Object.keys(override).length) return '';
|
|
3659
|
+
return `<span class="dm-contents-override" hidden data-contents-override="${escapeAttr(JSON.stringify(override))}"></span>`;
|
|
3660
|
+
}));
|
|
3661
|
+
}
|
|
3662
|
+
|
|
3636
3663
|
function processNavbarBlocks(markdown) {
|
|
3637
3664
|
const {scrubbed, restore} = scrubCodeRegions(markdown);
|
|
3638
3665
|
return restore(scrubbed.replace(/\[navbar(\s[^\]]*?)?\/?\]/gi, (_, attrStr) => {
|
|
@@ -4494,7 +4521,7 @@ export async function parseMarkdown(raw, opts = {}) {
|
|
|
4494
4521
|
const withTimeline = processTimelineBlocks(withCountdown);
|
|
4495
4522
|
const withListGroup = processListGroupBlocks(withTimeline);
|
|
4496
4523
|
const withSpacer = processSpacerBlocks(withListGroup);
|
|
4497
|
-
const withSwitcher = processNavbarBlocks(processThemeSwitcherBlocks(withSpacer));
|
|
4524
|
+
const withSwitcher = processContentsBlocks(processNavbarBlocks(processThemeSwitcherBlocks(withSpacer)));
|
|
4498
4525
|
const withCenter = processCenterBlocks(withSwitcher);
|
|
4499
4526
|
const withIcon = processIconBlocks(withCenter);
|
|
4500
4527
|
const withDemo = processDemoBlocks(withIcon);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page contents - the list a layout with a sidebar builds from the page's own
|
|
3
|
+
* headings (the "With Sidebar" layout's left or right column).
|
|
4
|
+
*
|
|
5
|
+
* Its settings live on the layout (presets.json: `contents: {...}`, edited in
|
|
6
|
+
* Layouts) and a page can change any of them for itself with `[contents ... /]`.
|
|
7
|
+
* The renderer merges layout, page front matter (`sidebarSide`, kept for older
|
|
8
|
+
* pages) and the page's tag, and hands the result to site.js as __CMS_CONTENTS__;
|
|
9
|
+
* the parts the first paint needs (side, width, hidden, floating, autohide) also
|
|
10
|
+
* become classes and a token on the page.
|
|
11
|
+
*
|
|
12
|
+
* Layout side, width, sticky, floating, floatAnchor, movable, hidden
|
|
13
|
+
* Behaviour collapsible, collapsed, autohide, scrollSpy
|
|
14
|
+
* Content headings ('auto' | 'h2' | 'h3' | 'h2h3'), numbered, title, exclude
|
|
15
|
+
* Look hover, selected, accent, font, fontSize, fontWeight
|
|
16
|
+
*/
|
|
17
|
+
import {FLOAT_ANCHORS, isValidColour} from './menus.js';
|
|
18
|
+
import {HIGHLIGHT_HOVER, HIGHLIGHT_ACTIVE} from '../../public/js/menu-decor.mjs';
|
|
19
|
+
|
|
20
|
+
export const CONTENTS_WIDTH_MIN = 160;
|
|
21
|
+
export const CONTENTS_WIDTH_MAX = 480;
|
|
22
|
+
const HEADINGS = new Set(['auto', 'h2', 'h3', 'h2h3']);
|
|
23
|
+
const BOOL_KEYS = ['sticky', 'floating', 'movable', 'hidden', 'collapsible', 'collapsed', 'autohide', 'scrollSpy', 'numbered'];
|
|
24
|
+
const CSS_SIZE = /^\d+(\.\d+)?(px|rem|em|%)$/;
|
|
25
|
+
const FONT = /^[A-Za-z0-9 ,'"-]{1,80}$/;
|
|
26
|
+
|
|
27
|
+
/** The defaults: what the With Sidebar layout has always done. */
|
|
28
|
+
export const CONTENTS_DEFAULTS = Object.freeze({side: 'left', sticky: true, scrollSpy: true, headings: 'auto'});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A contents block with only known keys and sane values. Booleans keep `false`
|
|
32
|
+
* (a page turning off what its layout turns on); unknown or bad values are dropped.
|
|
33
|
+
*/
|
|
34
|
+
export function normaliseContents(input) {
|
|
35
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) return {};
|
|
36
|
+
const out = {};
|
|
37
|
+
if (input.side === 'left' || input.side === 'right') out.side = input.side;
|
|
38
|
+
const width = parseInt(input.width, 10);
|
|
39
|
+
if (Number.isFinite(width)) out.width = Math.min(CONTENTS_WIDTH_MAX, Math.max(CONTENTS_WIDTH_MIN, width));
|
|
40
|
+
for (const key of BOOL_KEYS) if (typeof input[key] === 'boolean') out[key] = input[key];
|
|
41
|
+
if (FLOAT_ANCHORS.has(input.floatAnchor)) out.floatAnchor = input.floatAnchor;
|
|
42
|
+
if (HEADINGS.has(input.headings)) out.headings = input.headings;
|
|
43
|
+
if (typeof input.title === 'string' && input.title.trim()) out.title = input.title.trim().slice(0, 80);
|
|
44
|
+
if (typeof input.exclude === 'string' && input.exclude.trim()) out.exclude = input.exclude.trim().slice(0, 500);
|
|
45
|
+
if (HIGHLIGHT_HOVER.includes(input.hover)) out.hover = input.hover;
|
|
46
|
+
if (HIGHLIGHT_ACTIVE.includes(input.selected)) out.selected = input.selected;
|
|
47
|
+
if (input.accent && isValidColour(input.accent)) out.accent = input.accent;
|
|
48
|
+
if (typeof input.font === 'string' && FONT.test(input.font)) out.font = input.font;
|
|
49
|
+
if (typeof input.fontSize === 'string' && CSS_SIZE.test(input.fontSize)) out.fontSize = input.fontSize;
|
|
50
|
+
if (/^[1-9]00$|^(normal|bold)$/.test(String(input.fontWeight || ''))) out.fontWeight = String(input.fontWeight);
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The tag's attributes (kebab-case) and the keys they set. */
|
|
55
|
+
export const CONTENTS_ATTRS = {
|
|
56
|
+
'side': 'side', 'width': 'width', 'sticky': 'sticky', 'floating': 'floating', 'float-anchor': 'floatAnchor',
|
|
57
|
+
'movable': 'movable', 'hidden': 'hidden', 'collapsible': 'collapsible', 'collapsed': 'collapsed',
|
|
58
|
+
'autohide': 'autohide', 'scroll-spy': 'scrollSpy', 'headings': 'headings', 'numbered': 'numbered',
|
|
59
|
+
'title': 'title', 'exclude': 'exclude', 'hover': 'hover', 'selected': 'selected', 'accent': 'accent',
|
|
60
|
+
'font': 'font', 'font-size': 'fontSize', 'font-weight': 'fontWeight'
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** The override a `[contents]` tag's attributes describe (normalised). */
|
|
64
|
+
export function contentsOverrideFromAttrs(attrs = {}) {
|
|
65
|
+
const raw = {};
|
|
66
|
+
for (const [name, value] of Object.entries(attrs)) {
|
|
67
|
+
const key = CONTENTS_ATTRS[name];
|
|
68
|
+
if (!key) continue;
|
|
69
|
+
let v = value === true ? 'true' : String(value).trim();
|
|
70
|
+
if (BOOL_KEYS.includes(key)) v = !(v === 'false' || v === '0' || v === 'no');
|
|
71
|
+
raw[key] = v;
|
|
72
|
+
}
|
|
73
|
+
return normaliseContents(raw);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The settings a page gets: defaults, then its layout's, then the older
|
|
78
|
+
* `sidebarSide` (layout, then page), then the page's `[contents]` tag.
|
|
79
|
+
*/
|
|
80
|
+
export function resolveContents(preset = {}, page = {}, override = null) {
|
|
81
|
+
const layout = normaliseContents(preset.contents);
|
|
82
|
+
const merged = {
|
|
83
|
+
...CONTENTS_DEFAULTS,
|
|
84
|
+
...(preset.sidebarSide === 'right' && {side: 'right'}),
|
|
85
|
+
...layout,
|
|
86
|
+
...(page.sidebarSide === 'right' || page.sidebarSide === 'left' ? {side: page.sidebarSide} : {}),
|
|
87
|
+
...normaliseContents(override || {})
|
|
88
|
+
};
|
|
89
|
+
// Autohide and floating take the list out of the column; collapse then means nothing.
|
|
90
|
+
if (merged.autohide || merged.floating) { delete merged.collapsible; delete merged.collapsed; }
|
|
91
|
+
if (merged.collapsed) merged.collapsible = true;
|
|
92
|
+
return merged;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The classes the page's <main> needs from the first paint, and the width token.
|
|
97
|
+
* @returns {{mainClass: string, style: string}}
|
|
98
|
+
*/
|
|
99
|
+
export function contentsPaint(c) {
|
|
100
|
+
const cls = [
|
|
101
|
+
c.side === 'right' ? 'sidebar-right' : '',
|
|
102
|
+
c.hidden ? 'contents-hidden' : '',
|
|
103
|
+
c.floating ? `contents-floating contents-float-${(c.floatAnchor || 'TR').toLowerCase()}` : '',
|
|
104
|
+
c.autohide ? 'contents-autohide' : '',
|
|
105
|
+
c.sticky === false ? 'contents-static' : '',
|
|
106
|
+
c.collapsible ? 'contents-collapsible' : '',
|
|
107
|
+
c.collapsed ? 'contents-start-collapsed' : ''
|
|
108
|
+
].filter(Boolean).join(' ');
|
|
109
|
+
const style = Number.isFinite(c.width) ? `--dm-contents-width:${c.width}px` : '';
|
|
110
|
+
return {mainClass: cls, style};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The override a page's [contents] tag left in its html (the first), or null. */
|
|
114
|
+
export function readContentsOverride(html) {
|
|
115
|
+
const m = /data-contents-override="([^"]*)"/.exec(html || '');
|
|
116
|
+
if (!m) return null;
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(m[1].replace(/"/g, '"').replace(/'/g, "'").replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'));
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -8,6 +8,7 @@ import {fileURLToPath} from 'url';
|
|
|
8
8
|
import {getConfig} from '../config.js';
|
|
9
9
|
import {getInjectionSnippets} from './plugins.js';
|
|
10
10
|
import {applyTransforms} from './hooks.js';
|
|
11
|
+
import {contentsPaint, readContentsOverride, resolveContents} from './pageContents.js';
|
|
11
12
|
import {applyMenuOverride, hasThemeSwitcherItem, resolveLocation, resolveMenuDecorations, resolveOverlaysForPage} from './menus.js';
|
|
12
13
|
import {buildMenuNav} from './menuRender.js';
|
|
13
14
|
import {getProjectForPage} from './projects.js';
|
|
@@ -122,6 +123,11 @@ function buildAnalyticsTag() {
|
|
|
122
123
|
* html (body and overlay menus) and the item lists of its navbar and footers
|
|
123
124
|
* @returns {{headTag: string, bodyTag: string}} Empty strings when nothing needs it
|
|
124
125
|
*/
|
|
126
|
+
/** A value for a double-quoted HTML attribute. */
|
|
127
|
+
function escapeAttrValue(v) {
|
|
128
|
+
return String(v).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
129
|
+
}
|
|
130
|
+
|
|
125
131
|
/** The site's reduced-motion default for visitors who have not chosen: 'system', 'off' or 'on'. */
|
|
126
132
|
function motionDefaultOf(site) {
|
|
127
133
|
const d = site?.footer?.motionDefault;
|
|
@@ -309,6 +315,7 @@ export async function renderPage(page, opts = {}) {
|
|
|
309
315
|
: (await readLegacyNavBackup()) || {brand: site.brand || {}, items: []};
|
|
310
316
|
|
|
311
317
|
const preset = presets[page.layout] || presets['default'] || {};
|
|
318
|
+
const pageContents = resolveContents(preset, page, readContentsOverride(page.html));
|
|
312
319
|
|
|
313
320
|
// Per-page `width:` frontmatter overrides the preset's width when valid.
|
|
314
321
|
const pageWidth = VALID_LAYOUT_WIDTHS.has(page.width) ? page.width : null;
|
|
@@ -428,8 +435,13 @@ export async function renderPage(page, opts = {}) {
|
|
|
428
435
|
railDocked: /\bdm-vnav\b/.test(verticalNavClass),
|
|
429
436
|
showNavbar: preset.navbar !== false,
|
|
430
437
|
showFooter: preset.footer !== false,
|
|
431
|
-
|
|
432
|
-
|
|
438
|
+
// The page contents list: the layout's settings, the page's sidebarSide and its
|
|
439
|
+
// [contents] tag merged (pageContents.js); `hidden` drops the column altogether.
|
|
440
|
+
showSidebar: (preset.sidebar === true || page.sidebar === true) && !pageContents.hidden,
|
|
441
|
+
contentsClass: contentsPaint(pageContents).mainClass,
|
|
442
|
+
contentsStyle: contentsPaint(pageContents).style,
|
|
443
|
+
contentsJson: escapeAttrValue(JSON.stringify(pageContents)),
|
|
444
|
+
contentsCollapsible: pageContents.collapsible === true && (preset.sidebar === true || page.sidebar === true) && !pageContents.hidden,
|
|
433
445
|
navJson: JSON.stringify(filterHiddenNavItems(navigation)).replace(/<\/script>/gi, '<\\/script>'),
|
|
434
446
|
footerScript: footerJson ? `window.__CMS_FOOTER__ = ${footerJson};` : '',
|
|
435
447
|
ctxMenusScript: ctxMenusScript,
|
|
@@ -37,7 +37,7 @@ const SEED_ITEMS = [
|
|
|
37
37
|
items: [
|
|
38
38
|
{text: 'Pages', url: '#/pages', icon: 'file-text', permission: 'pages'},
|
|
39
39
|
{text: 'Media', url: '#/media', icon: 'image', permission: 'media'},
|
|
40
|
-
{text: '
|
|
40
|
+
{text: 'Site menus', url: '#/menus', icon: 'menu', permission: 'menus'}
|
|
41
41
|
]
|
|
42
42
|
},
|
|
43
43
|
{
|
|
@@ -372,6 +372,19 @@ export async function ensureFolderKeys(opts = {}) {
|
|
|
372
372
|
changed = true;
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
// The Menus screen became "Site menus" in 1.6.16 (beside "Page contents", the list a
|
|
376
|
+
// page builds from its headings). Renamed once (`meta.siteMenusName`) and only where
|
|
377
|
+
// the item still says Menus, so a name an admin gave it stays.
|
|
378
|
+
if (!menu.meta?.siteMenusName && items.length) {
|
|
379
|
+
const rename = (list) => (list || []).forEach(n => {
|
|
380
|
+
if (n && n.url === '#/menus' && String(n.text || '').trim() === 'Menus') n.text = 'Site menus';
|
|
381
|
+
if (Array.isArray(n?.items)) rename(n.items);
|
|
382
|
+
});
|
|
383
|
+
rename(items);
|
|
384
|
+
menu.meta = {...(menu.meta || {}), siteMenusName: true};
|
|
385
|
+
changed = true;
|
|
386
|
+
}
|
|
387
|
+
|
|
375
388
|
// An empty menu is left empty: the renderer's minimal fallback (Dashboard,
|
|
376
389
|
// Menus) is what an admin needs to repair it, and a lone Tools folder would
|
|
377
390
|
// stop that fallback from applying.
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
<link rel="stylesheet" href="/dist/domma/themes/domma-themes.css">
|
|
26
26
|
|
|
27
27
|
<!-- Site CSS -->
|
|
28
|
-
<link rel="stylesheet" href="/public/css/site.css?v=20261011-
|
|
28
|
+
<link rel="stylesheet" href="/public/css/site.css?v=20261011-page-contents">
|
|
29
29
|
<link rel="stylesheet" href="/public/css/forms.css?v=20260927-submit-btn-css">
|
|
30
30
|
<!-- Menu item hover / current-page effects. Loaded after site.css so a
|
|
31
31
|
menu's own highlight wins over the blanket link styling there. -->
|
|
@@ -65,9 +65,19 @@
|
|
|
65
65
|
<nav id="site-navbar" class="{{navbarClass}}"></nav>
|
|
66
66
|
{{/if}}
|
|
67
67
|
|
|
68
|
-
<main class="site-main {{#if showSidebar}}with-sidebar{{/if}}
|
|
68
|
+
<main class="site-main {{#if showSidebar}}with-sidebar {{contentsClass}}{{/if}}"{{#if contentsStyle}} style="{{contentsStyle}}"{{/if}}>
|
|
69
69
|
{{#if showSidebar}}
|
|
70
|
-
<aside id="site-sidebar" class="site-sidebar"></aside>
|
|
70
|
+
<aside id="site-sidebar" class="site-sidebar" data-contents="{{contentsJson}}"></aside>
|
|
71
|
+
{{/if}}
|
|
72
|
+
{{#if contentsCollapsible}}
|
|
73
|
+
<script>
|
|
74
|
+
// The visitor's own choice (or the layout's starting state) before the first paint.
|
|
75
|
+
(function () {
|
|
76
|
+
var m = document.currentScript.parentNode, c = null;
|
|
77
|
+
try { c = JSON.parse(localStorage.getItem('domma:contents_collapsed')); } catch (e) {}
|
|
78
|
+
if (c === true || (c === null && m.classList.contains('contents-start-collapsed'))) m.classList.add('contents-collapsed');
|
|
79
|
+
})();
|
|
80
|
+
</script>
|
|
71
81
|
{{/if}}
|
|
72
82
|
|
|
73
83
|
<article class="site-content">
|
|
@@ -163,7 +173,7 @@
|
|
|
163
173
|
</script>
|
|
164
174
|
|
|
165
175
|
<!-- Site initialisation -->
|
|
166
|
-
<script src="/public/js/site.js?v=20261011-
|
|
176
|
+
<script src="/public/js/site.js?v=20261011-page-contents" type="module"></script>
|
|
167
177
|
<script src="/public/js/collection-context.js?v=20261010-utf8-payloads" type="module"></script>
|
|
168
178
|
{{ctxMenusModule}}
|
|
169
179
|
|