domma-cms 0.47.2 → 0.49.0
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/CLAUDE.md +13 -10
- package/admin/css/admin.css +1 -1
- package/admin/js/lib/menu-tree.js +1 -1
- package/admin/js/lib/sidebar-renderer.js +3 -3
- package/admin/js/templates/menu-editor.html +26 -0
- package/admin/js/templates/settings.html +10 -0
- package/admin/js/views/menu-editor.js +27 -12
- package/admin/js/views/settings.js +2 -2
- package/package.json +1 -1
- package/public/css/site.css +1 -1
- package/public/js/menu-decor.mjs +1 -1
- package/public/js/menu-float.js +1 -0
- package/public/js/site.js +1 -1
- package/server/services/markdown.js +4 -1
- package/server/services/menuRender.js +63 -3
- package/server/services/menus.js +77 -1
- package/server/templates/page.html +6 -1
package/public/js/site.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{makeBadgeEl as M,applyColour as N,applyPill as z,applyFloat as j}from"/public/js/menu-decor.mjs?v=20260817-menu-bindings";import{enhancePickerFields as O,markInvalidFields as P,showValidationPopup as R}from"/public/js/forms.js?v=20260821-wizard-grid";import{buildWizardSteps as B}from"/public/js/form-steps.mjs";$(()=>{const w=window.__CMS_NAV__||{},h=window.__CMS_SITE__||{};if(h.autoTheme?.enabled){let t=function(o){const e=(o||"07:00").split(":");return+e[0]*60+(+e[1]||0)},a=function(){const o=new Date,e=o.getHours()*60+o.getMinutes();return e>=t(n.dayStart)&&e<t(n.nightStart)?n.dayTheme:n.nightTheme};const n=h.autoTheme;Domma.theme.set(a()),setInterval(()=>Domma.theme.set(a()),6e4)}const x=$("#site-navbar");if(x.length&&w.brand){let a=function(r,f=0){return Array.isArray(r)?r.filter(c=>!(c&&c.type==="separator"&&f===0)).map(c=>c&&c.type==="separator"?{divider:!0}:Array.isArray(c?.items)?{...c,items:a(c.items,f+1)}:c):r},o=function(r){return Array.isArray(r)?r.filter(f=>!(f&&f.type==="separator")):[]};const n={...w.brand},t=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let r="";n.logo?r+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(r+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(r+=`<span class="navbar-brand-text${t}">${n.text}</span>`),n.tagline&&(r+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=r}else t&&n.text&&(n.html=`<span class="navbar-brand-text${t}">${n.text}</span>`);const e=w.position==="floating",d=w.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:a(w.items||[]),variant:w.variant||"dark",position:e?"static":w.position||"sticky",collapsible:!0,appearOnHover:!!w.appearOnHover}),Domma.icons.scan("#site-navbar"),d&&x.get(0).classList.add("dm-nav-vertical",w.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),e&&j(x.get(0),w);const g=x.get(0),y=g.querySelector(".navbar-toggle"),p=g.querySelector(".navbar-collapse");if(y&&p){let r=null;const f=()=>{p.classList.contains("show")&&y.click()},c=()=>{r&&(r.remove(),r=null),document.body.classList.remove("dm-drawer-open")},s=()=>{r||(r=document.createElement("div"),r.className="dm-navbar-backdrop",r.addEventListener("click",f),document.body.appendChild(r),document.body.classList.add("dm-drawer-open"))};y.addEventListener("click",()=>{requestAnimationFrame(()=>{p.classList.contains("show")?s():c()})}),p.addEventListener("click",i=>{i.target.closest("a")&&requestAnimationFrame(c)}),document.addEventListener("keydown",i=>{i.key==="Escape"&&p.classList.contains("show")&&f()})}const m=o(w.items||[]);g.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(r=>{const f=m[parseInt(r.dataset.index,10)];if(!f)return;const c=r.dataset.subindex!=null,s=c&&Array.isArray(f.items)?f.items[parseInt(r.dataset.subindex,10)]:f;if(!s)return;s.colour&&N(r,s.colour),s.pill&&!c&&z(r,s.pill);const i=M(s.badge);i&&r.appendChild(i)})}const C=$("#site-footer");if(C.length){let e=function(s){return String(s??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},d=function(s){return e(s).replace(/'/g,"'")},g=function(s){if(s&&s.type==="separator")return'<li class="footer-sep"><hr></li>';const i=d(s.url||"#"),l=e(s.text||""),v=Array.isArray(s.items)&&s.items.length?`<ul>${s.items.map(g).join("")}</ul>`:"";return`<li><a href="${i}">${l}</a>${v}</li>`},y=function(s,i){return(s||[]).forEach(l=>{l&&l.type==="separator"||(i.push(l),Array.isArray(l.items)&&l.items.length&&y(l.items,i))}),i};const n=h.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 a='<div class="footer-inner container">';const o=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(o.copyright&&(a+=`<p>${e(o.copyright)}</p>`),o.primary.length&&(a+=`<nav class="footer-nav footer-nav--primary"><ul>${o.primary.map(g).join("")}</ul></nav>`),o.legal.length&&(a+=`<nav class="footer-nav footer-nav--legal"><ul>${o.legal.map(g).join("")}</ul></nav>`),h.footer){const s=Object.keys(t).filter(i=>n[i]);s.length&&(a+='<div class="footer-social">',s.forEach(i=>{const{label:l,svg:v}=t[i];a+=`<a href="${n[i]}" target="_blank" rel="noopener noreferrer" aria-label="${l}" class="footer-social-link">${v}</a>`}),a+="</div>")}a+="</div>",C.html(a);const p=[...y(o.primary,[]),...y(o.legal,[])];C.get(0).querySelectorAll(".footer-nav a").forEach((s,i)=>{const l=p[i];if(!l)return;l.colour&&N(s,l.colour);const v=M(l.badge);v&&s.appendChild(v)});const r=S.get("reduced_motion"),f=r!==null?!!r:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),c=C.get(0).querySelector(".footer-inner");if(c){const s=document.createElement("input");s.type="checkbox",s.className="form-switch-input",s.id="dm-motion-switch",s.checked=f,s.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const i=document.createElement("span");i.className="form-switch-label",i.textContent="Reduce motion";const l=document.createElement("label");l.className="form-switch footer-motion-switch",l.title="Reduce motion",l.appendChild(s),l.appendChild(i),c.appendChild(l)}}$("#site-sidebar").length&&Domma.elements.sidebar("#site-sidebar",{autoGenerate:!0,selector:"h2, h3",collapsible:!1,push:!0,contentSelector:".site-content"}),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const u=$(".page-body");if(u.length){u.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),u.find(".tabs").each(function(){Domma.elements.tabs(this)}),u.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)}),u.find(".dm-countdown").each(function(){const n={autoStart:!0};this.dataset.to&&(n.targetDate=new Date(this.dataset.to)),this.dataset.duration&&(n.duration=parseInt(this.dataset.duration,10)),this.dataset.format&&(n.format=this.dataset.format),Domma.elements.timer(this,n)}),u.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),u.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",a=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:a,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",a=n.dataset.revealAnimation||"slide-up",o=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,d=parseInt(n.dataset.revealDelay,10)||0,g=n.dataset.revealDirection==="rtl",y=Array.from(n.children),p={"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)"};y.forEach((m,r)=>{m.style.opacity="0",m.style.transform=p[a]||"",m.style.transition=`opacity ${o}ms ease, transform ${o}ms ease`;const f=g?y.length-1-r:r;m.style.transitionDelay=t==="stagger"?`${d+f*e}ms`:`${d}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const m=new IntersectionObserver(r=>{r.forEach(f=>{f.isIntersecting&&(f.target.offsetWidth,f.target.style.opacity="1",f.target.style.transform="none",m.unobserve(f.target))})},{threshold:.1});y.forEach(r=>m.observe(r))}))}),u.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),u.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,t=document.getElementById(n);if(!t)return;const a=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",a.setContent(t),a.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const a=atob(t.dataset.config),o=JSON.parse(a);Object.assign(n,o)}catch{}}),Object.keys(n).length>0){const t={};for(const[a,o]of Object.entries(n)){const e=o?.events?.click,{confirm:d,toast:g,alert:y,prompt:p,...m}=e||{};d||g||y||p?($(a).on("click",async function(f){if(f.preventDefault(),d&&!await E.confirm(d))return;let c=null;if(!(p&&(c=await E.prompt(p,{inputPlaceholder:m.promptPlaceholder||"",inputValue:m.promptDefault||""}),c===null))){if(m.target){const s=$(m.target);m.toggleClass&&s.toggleClass(m.toggleClass),m.addClass&&s.addClass(m.addClass),m.removeClass&&s.removeClass(m.removeClass),c!==null&&(m.setText&&s.text(c),m.setVal&&s.val(c),m.setAttr&&s.attr(m.setAttr,c))}m.href&&(window.location.href=m.href),g&&E.toast(g,{type:m.toastType||"success"}),y&&E.alert(y)}}),Object.keys(m).length&&(t[a]={...o,events:{...o.events,click:m}})):t[a]=o}$.setup(t)}}u.length&&D(u.get(0))});function D(w){w.querySelectorAll(".dm-cta-trigger").forEach(h=>{h.__ctaWired||(h.__ctaWired=!0,h.addEventListener("click",async()=>{const x=h.dataset.action,C=h.dataset.entry,k=h.dataset.confirm;let u=S.get("auth_token");if(!u){E.toast("Please log in to perform this action.",{type:"warning"});return}if(k&&!await E.confirm(k))return;const n=Array.from(h.childNodes).map(a=>a.cloneNode(!0));h.disabled=!0,h.textContent="Running\u2026";const t=a=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({entryId:C})});try{let a=await t(u);if(a.status===401){const e=S.get("auth_refresh_token");if(e){const d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(d.ok){const{token:g}=await d.json();S.set("auth_token",g),u=g,a=await t(u)}}}const o=await a.json().catch(()=>({}));if(!a.ok)throw new Error(o.error||o.message||`Error ${a.status}`);E.toast(o.message||"Action completed.",{type:"success"})}catch(a){E.toast(a.message||"Action failed.",{type:"error"})}finally{h.disabled=!1,h.textContent="",n.forEach(a=>h.appendChild(a)),Domma.icons.scan(h)}}))})}(function(){const h=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!h.length)return;const x=S.get("auth_token");function C(u,n){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const a=document.createElement("p");if(n){a.appendChild(document.createTextNode("Please "));const o=document.createElement("a");o.href="/login",o.textContent="sign in",a.appendChild(o),a.appendChild(document.createTextNode(" to view your entries."))}else a.textContent=u;return t.appendChild(a),t}function k(u,n){const t=document.createElement("template");t.innerHTML=n||"",u.replaceWith(t.content)}h.forEach(async u=>{const n=u.dataset.collectionAttrs;if(!n)return;if(!x){u.replaceChildren(C("",!0));return}const t=a=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({attrs:n})});try{let a=x,o=await t(a);if(o.status===401){const d=S.get("auth_refresh_token");if(d){const g=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:d})});if(g.ok){const{token:y}=await g.json();S.set("auth_token",y),a=y,o=await t(a)}}}if(!o.ok){const{error:d}=await o.json().catch(()=>({}));u.replaceChildren(C(d||"Unable to load entries.",!1));return}const{html:e}=await o.json();k(u,e)}catch{u.replaceChildren(C("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),D(document.body)},100)})(),(function(){const h=document.querySelectorAll("[data-collection-table]");!h.length||typeof T>"u"||!T.create||h.forEach(x=>{let C;try{C=JSON.parse(atob(x.dataset.payload))}catch{return}const{columns:k,rows:u,search:n,sortable:t,exportable:a,pageSize:o,empty:e,ctaConfig:d,slug:g,exportCfg:y}=C;if(!k?.length)return;if(d){const r=k.findIndex(f=>f.key==="_cta");r!==-1&&(k[r]={key:"_cta",title:"",render:(f,c)=>{const s=document.createElement("button");if(s.className=`btn btn-${d.style||"primary"} dm-cta-trigger`,s.dataset.action=d.action||"",s.dataset.entry=c._entryId||"",d.confirm&&(s.dataset.confirm=d.confirm),d.icon){const i=document.createElement("span");i.dataset.icon=d.icon,s.appendChild(i),s.appendChild(document.createTextNode(" "))}return s.appendChild(document.createTextNode(d.label||"Run")),s}})}const p="col-table-"+Math.random().toString(36).slice(2,7),m=document.createElement("div");m.id=p,x.replaceChildren(m),T.create("#"+p,{data:u,columns:k,search:n,sortable:t,exportable:a,pageSize:o,emptyMessage:e}),d&&D(m),x._dmTable={columns:k,rows:u,slug:g,exportCfg:y,render(r){T.create("#"+p,{data:r,columns:k,search:n,sortable:t,exportable:a,pageSize:o,emptyMessage:e}),d&&D(m)}}})})(),(function(){const h=document.querySelectorAll("[data-form-inline]");if(!h.length||typeof F>"u")return;function x(t){const a=t&&t.logic;return!!(a&&(a.visibility||a.requirement))}function C(t,a){const o={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const d=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),g=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":d?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&a&&(y.span=a);const p={type:g,required:x(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...y}};d&&(e.variant&&(p.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(p.multiple=!0),e.density&&(p.density=e.density),e.columns&&(p.columns=e.columns),e.accent&&(p.accent=e.accent),e.accentStyle&&(p.accentStyle=e.accentStyle),e.glow&&(p.glow=!0),e.glowColour&&(p.glowColour=e.glowColour),e.shadow&&(p.shadow=e.shadow),e.shadowColour&&(p.shadowColour=e.shadowColour)),o[e.name]=p}),o}function k(t){const a={};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&&(a[o.name]=typeof e=="object"?e.value:e)}}),a}function u(t,a){(a||[]).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,a,o){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=a,e.style.cssText=o?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}h.forEach(t=>{let a;try{a=JSON.parse(atob(t.dataset.formInline))}catch{return}const o=a.fields||[],e=a.settings||{},d=Number(e.columns)===3?3:2,g=e.layout||"grid",y=o.some(c=>c.type==="page-break"),p=async c=>{try{const s=t.querySelector(".fb-form-honeypot"),i=s?.querySelector('input[type="text"]')||null,l=s?.querySelector('[name="_t"]')||null,v=Object.assign({},c);delete v._t,s?.dataset.hpName&&delete v[s.dataset.hpName],i!==null&&(v._hp=i.value),l!==null&&(v._t=l.value);const b=await H.post(`/api/forms/submit/${a.slug}`,v);if(b&&b.error){n(t,b.error,!0);const I=new Error(b.error);throw I.formSubmitFailed=!0,I}if(b?.redirect){window.location.href=b.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);n(t,b?.message||e.successMessage||"Thank you for your submission.",!1)}catch(s){throw s.formSubmitFailed||n(t,s.message||"Submission failed. Please try again.",!0),s}},m=Date.now();function r(c){const s=c.querySelector("form");if(!s||s.querySelector(".fb-form-honeypot"))return;const i=document.createElement("div");i.className="fb-form-honeypot",i.setAttribute("aria-hidden","true");const l=document.createElement("input");l.name=s.querySelector('[name="website"]')?"_website":"website",i.dataset.hpName=l.name,l.type="text",l.tabIndex=-1,l.autocomplete="url",l.placeholder="https://",i.appendChild(l);const v=document.createElement("input");v.name="_t",v.type="hidden",v.value=m,i.appendChild(v),s.appendChild(i)}function f(c){if(!window.FormLogicEngine||!o.some(i=>i.logic))return null;const s=new window.FormLogicEngine.FormLogicRuntime(a,t,c?{baseValues:c}:void 0);if(s.init(),t._formLogicRuntime=s,t._formLogicObserved)return s;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const i=new MutationObserver(function(l){for(const v of l)for(const b of v.removedNodes)if(b===t||b.nodeType===1&&b.contains&&b.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),i.disconnect();return}});i.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=i}return s}if(y&&F.wizard){const c=B(o,a.title),s=c.map(_=>({title:_.title,description:_.description,fields:C(_.fields,d)})),i=c.map(_=>_.fields);let l=0,v=null,b=null;const I=_=>{const A=window.FormLogicEngine;if(!A||!A.validateFields)return;const L=A.validateFields(t,i[l]||[]);L.valid||(_.preventDefault(),_.stopImmediatePropagation(),P(t,L.missing),R(L))},q=()=>{const _=i[l]||o;u(t,_),O(t,_),e.honeypot!==!1&&r(t),v&&v.destroy(),v=f(b?.getStepData?b.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(A=>A.addEventListener("click",I,!0))};b=F.wizard(t,{schema:{steps:s},layout:g,columns:d,finishText:e.submitText||"Finish",onSubmit:p,onStepChange:_=>{l=_,setTimeout(q,0)}}),Promise.resolve(b).then(q)}else if(F.render){const c=F.render(t,C(o,d),k(o),{submitText:e.submitText||"Submit",layout:g,columns:d,onSubmit:p});Promise.resolve(c).then(function(){if(g==="grid"&&e.submitSpan==="full"){const s=t.querySelector(".form-buttons");s&&s.classList.add("col-span-full")}u(t,o),O(t,o),e.honeypot!==!1&&r(t),f()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
|
1
|
+
import{makeBadgeEl as z,applyColour as M,applyPill as O,applyFloat as j}from"/public/js/menu-decor.mjs?v=20260829-float-panel";import{initFloatingMenus as B}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as N,markInvalidFields as P,showValidationPopup as R}from"/public/js/forms.js?v=20260821-wizard-grid";import{buildWizardSteps as V}from"/public/js/form-steps.mjs";$(()=>{const w=window.__CMS_NAV__||{},h=window.__CMS_SITE__||{};if(h.autoTheme?.enabled){let t=function(n){const e=(n||"07:00").split(":");return+e[0]*60+(+e[1]||0)},a=function(){const n=new Date,e=n.getHours()*60+n.getMinutes();return e>=t(o.dayStart)&&e<t(o.nightStart)?o.dayTheme:o.nightTheme};const o=h.autoTheme;Domma.theme.set(a()),setInterval(()=>Domma.theme.set(a()),6e4)}const C=$("#site-navbar");if(C.length&&w.brand){let a=function(i,c=0){return Array.isArray(i)?i.filter(s=>!(s&&s.type==="spacer")).filter(s=>!(s&&s.type==="separator"&&c===0)).map(s=>s&&s.type==="separator"?{divider:!0}:Array.isArray(s?.items)?{...s,items:a(s.items,c+1)}:s):i},n=function(i){return Array.isArray(i)?i.filter(c=>!(c&&(c.type==="separator"||c.type==="spacer"))):[]};const o={...w.brand},t=o.size&&o.size!=="md"?` navbar-brand-${o.size}`:"";if(o.logo||o.icon||o.tagline){let i="";o.logo?i+=`<img src="${o.logo}" class="navbar-brand-logo" alt="${o.text||""}">`:o.icon&&(i+=`<span data-icon="${o.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),o.text&&(i+=`<span class="navbar-brand-text${t}">${o.text}</span>`),o.tagline&&(i+=`<small class="navbar-brand-tagline">${o.tagline}</small>`),o.html=i}else t&&o.text&&(o.html=`<span class="navbar-brand-text${t}">${o.text}</span>`);const e=w.position==="floating",u=w.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:o,items:a(w.items||[]),variant:w.variant||"dark",position:e?"static":w.position||"sticky",collapsible:!0,appearOnHover:!!w.appearOnHover}),Domma.icons.scan("#site-navbar"),u&&C.get(0).classList.add("dm-nav-vertical",w.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),e&&(j(C.get(0),w),B(C.get(0).parentNode||document));const g=C.get(0),y=g.querySelector(".navbar-toggle"),f=g.querySelector(".navbar-collapse");if(y&&f){let i=null;const c=()=>{f.classList.contains("show")&&y.click()},s=()=>{i&&(i.remove(),i=null),document.body.classList.remove("dm-drawer-open")},r=()=>{i||(i=document.createElement("div"),i.className="dm-navbar-backdrop",i.addEventListener("click",c),document.body.appendChild(i),document.body.classList.add("dm-drawer-open"))};y.addEventListener("click",()=>{requestAnimationFrame(()=>{f.classList.contains("show")?r():s()})}),f.addEventListener("click",l=>{l.target.closest("a")&&requestAnimationFrame(s)}),document.addEventListener("keydown",l=>{l.key==="Escape"&&f.classList.contains("show")&&c()})}const d=g.querySelector(".navbar-nav");if(d){const i=Array.from(d.children);let c=0;for(const s of w.items||[])if(!(s&&s.type==="separator")){if(s&&s.type==="spacer"){const r=document.createElement("li"),l=["xs","sm","md","lg","xl","flex"].includes(s.size)?s.size:"md";r.className=`dm-menu-spacer dm-menu-spacer--${l}`,r.setAttribute("aria-hidden","true"),d.insertBefore(r,i[c]||null);continue}c++}}const b=n(w.items||[]);g.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(i=>{const c=b[parseInt(i.dataset.index,10)];if(!c)return;const s=i.dataset.subindex!=null,r=s&&Array.isArray(c.items)?c.items[parseInt(i.dataset.subindex,10)]:c;if(!r)return;r.colour&&M(i,r.colour),r.pill&&!s&&O(i,r.pill);const l=z(r.badge);l&&i.appendChild(l)})}const x=$("#site-footer");if(x.length){let e=function(r){return String(r??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},u=function(r){return e(r).replace(/'/g,"'")},g=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>`;const l=u(r.url||"#"),m=e(r.text||""),v=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(g).join("")}</ul>`:"";return`<li><a href="${l}">${m}</a>${v}</li>`},y=function(r,l){return(r||[]).forEach(m=>{m&&(m.type==="separator"||m.type==="spacer")||(l.push(m),Array.isArray(m.items)&&m.items.length&&y(m.items,l))}),l};const o=h.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 a='<div class="footer-inner container">';const n=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(n.copyright&&(a+=`<p>${e(n.copyright)}</p>`),n.primary.length&&(a+=`<nav class="footer-nav footer-nav--primary"><ul>${n.primary.map(g).join("")}</ul></nav>`),n.legal.length&&(a+=`<nav class="footer-nav footer-nav--legal"><ul>${n.legal.map(g).join("")}</ul></nav>`),h.footer){const r=Object.keys(t).filter(l=>o[l]);r.length&&(a+='<div class="footer-social">',r.forEach(l=>{const{label:m,svg:v}=t[l];a+=`<a href="${o[l]}" target="_blank" rel="noopener noreferrer" aria-label="${m}" class="footer-social-link">${v}</a>`}),a+="</div>")}a+="</div>",x.html(a);const f=[...y(n.primary,[]),...y(n.legal,[])];x.get(0).querySelectorAll(".footer-nav a").forEach((r,l)=>{const m=f[l];if(!m)return;m.colour&&M(r,m.colour);const v=z(m.badge);v&&r.appendChild(v)});const b=h.footer?.showMotionToggle!==!1,i=S.get("reduced_motion"),c=i!==null?!!i:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),s=x.get(0).querySelector(".footer-inner");if(b&&s){const r=document.createElement("input");r.type="checkbox",r.className="form-switch-input",r.id="dm-motion-switch",r.checked=c,r.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const l=document.createElement("span");l.className="form-switch-label",l.textContent="Reduce motion";const m=document.createElement("label");m.className="form-switch footer-motion-switch",m.title="Reduce motion",m.appendChild(r),m.appendChild(l),s.appendChild(m)}}$("#site-sidebar").length&&Domma.elements.sidebar("#site-sidebar",{autoGenerate:!0,selector:"h2, h3",collapsible:!1,push:!0,contentSelector:".site-content"}),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const p=$(".page-body");if(p.length){p.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),p.find(".tabs").each(function(){Domma.elements.tabs(this)}),p.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)}),p.find(".dm-countdown").each(function(){const o={autoStart:!0};this.dataset.to&&(o.targetDate=new Date(this.dataset.to)),this.dataset.duration&&(o.duration=parseInt(this.dataset.duration,10)),this.dataset.format&&(o.format=this.dataset.format),Domma.elements.timer(this,o)}),p.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),p.find(".dm-progression").each(function(){const o=Array.from(this.querySelectorAll(".dm-progression-item")).map(n=>{const e={title:n.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:n.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return n.dataset.date&&(e.date=n.dataset.date),n.dataset.status&&(e.status=n.dataset.status),n.dataset.entryId&&(e._entryId=n.dataset.entryId),e}),t=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",a=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:a,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",a=o.dataset.revealAnimation||"slide-up",n=parseInt(o.dataset.revealDuration,10)||400,e=parseInt(o.dataset.revealStagger,10)||60,u=parseInt(o.dataset.revealDelay,10)||0,g=o.dataset.revealDirection==="rtl",y=Array.from(o.children),f={"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)"};y.forEach((d,b)=>{d.style.opacity="0",d.style.transform=f[a]||"",d.style.transition=`opacity ${n}ms ease, transform ${n}ms ease`;const i=g?y.length-1-b:b;d.style.transitionDelay=t==="stagger"?`${u+i*e}ms`:`${u}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const d=new IntersectionObserver(b=>{b.forEach(i=>{i.isIntersecting&&(i.target.offsetWidth,i.target.style.opacity="1",i.target.style.transform="none",d.unobserve(i.target))})},{threshold:.1});y.forEach(b=>d.observe(b))}))}),p.find(".card[data-collapsible]").each(function(){const o=this.querySelector(".card-header");o&&o.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),p.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const o=this.dataset.soTarget,t=document.getElementById(o);if(!t)return;const a=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",a.setContent(t),a.open()})})}if(typeof $.setup=="function"){const o=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const a=atob(t.dataset.config),n=JSON.parse(a);Object.assign(o,n)}catch{}}),Object.keys(o).length>0){const t={};for(const[a,n]of Object.entries(o)){const e=n?.events?.click,{confirm:u,toast:g,alert:y,prompt:f,...d}=e||{};u||g||y||f?($(a).on("click",async function(i){if(i.preventDefault(),u&&!await E.confirm(u))return;let c=null;if(!(f&&(c=await E.prompt(f,{inputPlaceholder:d.promptPlaceholder||"",inputValue:d.promptDefault||""}),c===null))){if(d.target){const s=$(d.target);d.toggleClass&&s.toggleClass(d.toggleClass),d.addClass&&s.addClass(d.addClass),d.removeClass&&s.removeClass(d.removeClass),c!==null&&(d.setText&&s.text(c),d.setVal&&s.val(c),d.setAttr&&s.attr(d.setAttr,c))}d.href&&(window.location.href=d.href),g&&E.toast(g,{type:d.toastType||"success"}),y&&E.alert(y)}}),Object.keys(d).length&&(t[a]={...n,events:{...n.events,click:d}})):t[a]=n}$.setup(t)}}p.length&&D(p.get(0))});function D(w){w.querySelectorAll(".dm-cta-trigger").forEach(h=>{h.__ctaWired||(h.__ctaWired=!0,h.addEventListener("click",async()=>{const C=h.dataset.action,x=h.dataset.entry,k=h.dataset.confirm;let p=S.get("auth_token");if(!p){E.toast("Please log in to perform this action.",{type:"warning"});return}if(k&&!await E.confirm(k))return;const o=Array.from(h.childNodes).map(a=>a.cloneNode(!0));h.disabled=!0,h.textContent="Running\u2026";const t=a=>fetch(`/api/actions/${C}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({entryId:x})});try{let a=await t(p);if(a.status===401){const e=S.get("auth_refresh_token");if(e){const u=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(u.ok){const{token:g}=await u.json();S.set("auth_token",g),p=g,a=await t(p)}}}const n=await a.json().catch(()=>({}));if(!a.ok)throw new Error(n.error||n.message||`Error ${a.status}`);E.toast(n.message||"Action completed.",{type:"success"})}catch(a){E.toast(a.message||"Action failed.",{type:"error"})}finally{h.disabled=!1,h.textContent="",o.forEach(a=>h.appendChild(a)),Domma.icons.scan(h)}}))})}(function(){const h=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!h.length)return;const C=S.get("auth_token");function x(p,o){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const a=document.createElement("p");if(o){a.appendChild(document.createTextNode("Please "));const n=document.createElement("a");n.href="/login",n.textContent="sign in",a.appendChild(n),a.appendChild(document.createTextNode(" to view your entries."))}else a.textContent=p;return t.appendChild(a),t}function k(p,o){const t=document.createElement("template");t.innerHTML=o||"",p.replaceWith(t.content)}h.forEach(async p=>{const o=p.dataset.collectionAttrs;if(!o)return;if(!C){p.replaceChildren(x("",!0));return}const t=a=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({attrs:o})});try{let a=C,n=await t(a);if(n.status===401){const u=S.get("auth_refresh_token");if(u){const g=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:u})});if(g.ok){const{token:y}=await g.json();S.set("auth_token",y),a=y,n=await t(a)}}}if(!n.ok){const{error:u}=await n.json().catch(()=>({}));p.replaceChildren(x(u||"Unable to load entries.",!1));return}const{html:e}=await n.json();k(p,e)}catch{p.replaceChildren(x("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),D(document.body)},100)})(),(function(){const h=document.querySelectorAll("[data-collection-table]");!h.length||typeof T>"u"||!T.create||h.forEach(C=>{let x;try{x=JSON.parse(atob(C.dataset.payload))}catch{return}const{columns:k,rows:p,search:o,sortable:t,exportable:a,pageSize:n,empty:e,ctaConfig:u,slug:g,exportCfg:y}=x;if(!k?.length)return;if(u){const b=k.findIndex(i=>i.key==="_cta");b!==-1&&(k[b]={key:"_cta",title:"",render:(i,c)=>{const s=document.createElement("button");if(s.className=`btn btn-${u.style||"primary"} dm-cta-trigger`,s.dataset.action=u.action||"",s.dataset.entry=c._entryId||"",u.confirm&&(s.dataset.confirm=u.confirm),u.icon){const r=document.createElement("span");r.dataset.icon=u.icon,s.appendChild(r),s.appendChild(document.createTextNode(" "))}return s.appendChild(document.createTextNode(u.label||"Run")),s}})}const f="col-table-"+Math.random().toString(36).slice(2,7),d=document.createElement("div");d.id=f,C.replaceChildren(d),T.create("#"+f,{data:p,columns:k,search:o,sortable:t,exportable:a,pageSize:n,emptyMessage:e}),u&&D(d),C._dmTable={columns:k,rows:p,slug:g,exportCfg:y,render(b){T.create("#"+f,{data:b,columns:k,search:o,sortable:t,exportable:a,pageSize:n,emptyMessage:e}),u&&D(d)}}})})(),(function(){const h=document.querySelectorAll("[data-form-inline]");if(!h.length||typeof F>"u")return;function C(t){const a=t&&t.logic;return!!(a&&(a.visibility||a.requirement))}function x(t,a){const n={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const u=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),g=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":u?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&a&&(y.span=a);const f={type:g,required:C(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...y}};u&&(e.variant&&(f.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(f.multiple=!0),e.density&&(f.density=e.density),e.columns&&(f.columns=e.columns),e.accent&&(f.accent=e.accent),e.accentStyle&&(f.accentStyle=e.accentStyle),e.glow&&(f.glow=!0),e.glowColour&&(f.glowColour=e.glowColour),e.shadow&&(f.shadow=e.shadow),e.shadowColour&&(f.shadowColour=e.shadowColour)),n[e.name]=f}),n}function k(t){const a={};return(t||[]).forEach(n=>{if(!(!n.name||n.type==="page-break"||n.type==="spacer")&&(n.type==="select"||n.type==="multiselect")&&n.required){const e=(n.options||[])[0];e&&(a[n.name]=typeof e=="object"?e.value:e)}}),a}function p(t,a){(a||[]).forEach(n=>{if(n.type!=="date"||!n.name)return;const e=t.querySelector(`[name="${n.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function o(t,a,n){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=a,e.style.cssText=n?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}h.forEach(t=>{let a;try{a=JSON.parse(atob(t.dataset.formInline))}catch{return}const n=a.fields||[],e=a.settings||{},u=Number(e.columns)===3?3:2,g=e.layout||"grid",y=n.some(c=>c.type==="page-break"),f=async c=>{try{const s=t.querySelector(".fb-form-honeypot"),r=s?.querySelector('input[type="text"]')||null,l=s?.querySelector('[name="_t"]')||null,m=Object.assign({},c);delete m._t,s?.dataset.hpName&&delete m[s.dataset.hpName],r!==null&&(m._hp=r.value),l!==null&&(m._t=l.value);const v=await H.post(`/api/forms/submit/${a.slug}`,m);if(v&&v.error){o(t,v.error,!0);const A=new Error(v.error);throw A.formSubmitFailed=!0,A}if(v?.redirect){window.location.href=v.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);o(t,v?.message||e.successMessage||"Thank you for your submission.",!1)}catch(s){throw s.formSubmitFailed||o(t,s.message||"Submission failed. Please try again.",!0),s}},d=Date.now();function b(c){const s=c.querySelector("form");if(!s||s.querySelector(".fb-form-honeypot"))return;const r=document.createElement("div");r.className="fb-form-honeypot",r.setAttribute("aria-hidden","true");const l=document.createElement("input");l.name=s.querySelector('[name="website"]')?"_website":"website",r.dataset.hpName=l.name,l.type="text",l.tabIndex=-1,l.autocomplete="url",l.placeholder="https://",r.appendChild(l);const m=document.createElement("input");m.name="_t",m.type="hidden",m.value=d,r.appendChild(m),s.appendChild(r)}function i(c){if(!window.FormLogicEngine||!n.some(r=>r.logic))return null;const s=new window.FormLogicEngine.FormLogicRuntime(a,t,c?{baseValues:c}:void 0);if(s.init(),t._formLogicRuntime=s,t._formLogicObserved)return s;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const r=new MutationObserver(function(l){for(const m of l)for(const v of m.removedNodes)if(v===t||v.nodeType===1&&v.contains&&v.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),r.disconnect();return}});r.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=r}return s}if(y&&F.wizard){const c=V(n,a.title),s=c.map(_=>({title:_.title,description:_.description,fields:x(_.fields,u)})),r=c.map(_=>_.fields);let l=0,m=null,v=null;const A=_=>{const I=window.FormLogicEngine;if(!I||!I.validateFields)return;const L=I.validateFields(t,r[l]||[]);L.valid||(_.preventDefault(),_.stopImmediatePropagation(),P(t,L.missing),R(L))},q=()=>{const _=r[l]||n;p(t,_),N(t,_),e.honeypot!==!1&&b(t),m&&m.destroy(),m=i(v?.getStepData?v.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(I=>I.addEventListener("click",A,!0))};v=F.wizard(t,{schema:{steps:s},layout:g,columns:u,finishText:e.submitText||"Finish",onSubmit:f,onStepChange:_=>{l=_,setTimeout(q,0)}}),Promise.resolve(v).then(q)}else if(F.render){const c=F.render(t,x(n,u),k(n),{submitText:e.submitText||"Submit",layout:g,columns:u,onSubmit:f});Promise.resolve(c).then(function(){if(g==="grid"&&e.submitSpan==="full"){const s=t.querySelector(".form-buttons");s&&s.classList.add("col-span-full")}p(t,n),N(t,n),e.honeypot!==!1&&b(t),i()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
|
@@ -4117,7 +4117,10 @@ export async function parseMarkdown(raw, opts = {}) {
|
|
|
4117
4117
|
]),
|
|
4118
4118
|
allowedAttributes: {
|
|
4119
4119
|
...sanitizeHtml.defaults.allowedAttributes,
|
|
4120
|
-
|
|
4120
|
+
// aria-hidden marks decorative markup (menu spacers, the floating panel's
|
|
4121
|
+
// drag grip) so it isn't announced as an empty item. Presentational only,
|
|
4122
|
+
// no script surface - unlike the on* handlers stripped globally.
|
|
4123
|
+
'*': ['class', 'id', 'style', 'data-*', 'aria-hidden'],
|
|
4121
4124
|
img: ['src', 'alt', 'title', 'width', 'height', 'loading'],
|
|
4122
4125
|
form: ['action', 'method'],
|
|
4123
4126
|
input: ['type', 'name', 'placeholder', 'value', 'required', 'disabled',
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Item filtering (visibility, `hidden`), depth capping and badge-count
|
|
11
11
|
* resolution happen before this - callers hand in the items they want drawn.
|
|
12
12
|
*/
|
|
13
|
-
import {colourToCss, floatToCss} from './menus.js';
|
|
13
|
+
import {colourToCss, floatToCss, floatBehaviour, spacerSize} from './menus.js';
|
|
14
14
|
|
|
15
15
|
/** Attribute-safe escaping - mirrors escapeAttr in markdown.js. */
|
|
16
16
|
function esc(str) {
|
|
@@ -35,6 +35,11 @@ export function renderMenuItemsAsUl(items) {
|
|
|
35
35
|
if (!items || !items.length) return '';
|
|
36
36
|
const lis = items.map(it => {
|
|
37
37
|
if (it && it.type === 'separator') return '<li class="dm-menu-sep" role="separator"><hr></li>';
|
|
38
|
+
// Spacers are blank room, not content - hidden from assistive tech so
|
|
39
|
+
// they don't read as an empty list item.
|
|
40
|
+
if (it && it.type === 'spacer') {
|
|
41
|
+
return `<li class="dm-menu-spacer dm-menu-spacer--${esc(spacerSize(it))}" aria-hidden="true"></li>`;
|
|
42
|
+
}
|
|
38
43
|
const href = esc(it.url || '#');
|
|
39
44
|
const text = esc(it.text || '');
|
|
40
45
|
const child = Array.isArray(it.items) && it.items.length ? renderMenuItemsAsUl(it.items) : '';
|
|
@@ -85,6 +90,61 @@ export function buildMenuNav(menu, items, opts = {}) {
|
|
|
85
90
|
? ` style="${esc(floatCss)}" data-float-anchor="${esc(menu.float?.anchor || 'TL')}"`
|
|
86
91
|
: '';
|
|
87
92
|
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
// Panel behaviour - only ever applies where the menu actually floats, so a
|
|
94
|
+
// shortcode rendered inline (noFloat) never grows a drag handle.
|
|
95
|
+
const beh = floatCss ? floatBehaviour(menu) : floatBehaviour(null);
|
|
96
|
+
const behAttrs = [
|
|
97
|
+
beh.collapsible && ' data-float-collapsible="1"',
|
|
98
|
+
beh.movable && ' data-float-movable="1"',
|
|
99
|
+
beh.resizable && ' data-float-resizable="1"'
|
|
100
|
+
].filter(Boolean).join('');
|
|
101
|
+
// The author's default state is stamped as a class, not left to JS, so a
|
|
102
|
+
// panel that starts collapsed never flashes open on first paint.
|
|
103
|
+
const collapsedClass = beh.collapsed ? ' is-collapsed' : '';
|
|
104
|
+
|
|
105
|
+
const flexClass = hasFlexSpacer(items) ? ' dm-menu--has-flex-spacer' : '';
|
|
106
|
+
|
|
107
|
+
return `<nav class="dm-menu dm-menu--${esc(menu.slug)}${orientClass}${variantClass}${floatClass}${collapsedClass}${flexClass}${klass}"`
|
|
108
|
+
+ ` data-menu="${esc(menu.slug)}"${floatAttrs}${behAttrs}>`
|
|
109
|
+
+ buildFloatChrome(menu, beh)
|
|
110
|
+
+ renderMenuItemsAsUl(items)
|
|
111
|
+
+ `</nav>`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Does this level contain a spacer that soaks up the leftover room? */
|
|
115
|
+
function hasFlexSpacer(items) {
|
|
116
|
+
return (items || []).some(it => it && it.type === 'spacer' && spacerSize(it) === 'flex');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The bar across the top of a floating panel: drag grip, title, and the
|
|
121
|
+
* hamburger that collapses the panel down to just that button.
|
|
122
|
+
*
|
|
123
|
+
* Returns '' when the menu is neither collapsible nor movable - a plain
|
|
124
|
+
* floating panel keeps the bare `<ul>` markup it has always had.
|
|
125
|
+
*
|
|
126
|
+
* @param {object} menu
|
|
127
|
+
* @param {object} beh Result of floatBehaviour(menu)
|
|
128
|
+
* @returns {string}
|
|
129
|
+
*/
|
|
130
|
+
function buildFloatChrome(menu, beh) {
|
|
131
|
+
if (!beh.chrome) return '';
|
|
132
|
+
|
|
133
|
+
const grip = beh.movable
|
|
134
|
+
? '<span class="dm-menu-float-grip" aria-hidden="true"></span>'
|
|
135
|
+
: '';
|
|
136
|
+
const title = beh.title
|
|
137
|
+
? `<span class="dm-menu-float-title">${esc(beh.title)}</span>`
|
|
138
|
+
: '';
|
|
139
|
+
// No aria-expanded here: sanitize-html strips aria attributes from the
|
|
140
|
+
// `[menu]` shortcode path, so menu-float.js stamps the ARIA state on init
|
|
141
|
+
// and keeps it in step from then on.
|
|
142
|
+
const toggle = beh.collapsible
|
|
143
|
+
? `<button type="button" class="dm-menu-float-toggle" data-float-toggle`
|
|
144
|
+
+ ` aria-label="${esc(beh.title ? `Toggle ${beh.title}` : 'Toggle menu')}">`
|
|
145
|
+
+ `<span class="dm-menu-float-icon" data-icon="bars"></span></button>`
|
|
146
|
+
: '';
|
|
147
|
+
|
|
148
|
+
return `<div class="dm-menu-float-bar"${beh.movable ? ' data-float-handle' : ''}>`
|
|
149
|
+
+ `${grip}${title}${toggle}</div>`;
|
|
90
150
|
}
|
package/server/services/menus.js
CHANGED
|
@@ -184,6 +184,48 @@ export function floatToCss(menu) {
|
|
|
184
184
|
return decls.join(';');
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// Floating panel behaviour - collapse to a hamburger, drag to move, resize.
|
|
189
|
+
//
|
|
190
|
+
// These are behaviour flags rather than CSS: `floatToCss` still owns where the
|
|
191
|
+
// panel starts, and `menu-float.js` owns what the visitor can then do to it.
|
|
192
|
+
// The server needs them at render time to emit the panel chrome (title bar and
|
|
193
|
+
// toggle button), so they resolve here and are mirrored in menu-decor.mjs.
|
|
194
|
+
//
|
|
195
|
+
// A visitor's own drag/size/collapse state lives in their browser, keyed by
|
|
196
|
+
// menu slug - the config below is only ever the starting point.
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
export const FLOAT_FLAGS = ['collapsible', 'collapsed', 'movable', 'resizable'];
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Resolve a floating menu's behaviour flags, plus whether it needs the chrome
|
|
202
|
+
* bar drawn at all. Returns all-false for a menu that is not floating, so a
|
|
203
|
+
* `[menu]` shortcode rendered inline never grows a drag handle.
|
|
204
|
+
*
|
|
205
|
+
* `title` is what the bar shows: the author's override, else the menu name.
|
|
206
|
+
*
|
|
207
|
+
* @param {object} menu A menu record (uses .position, .float, .name)
|
|
208
|
+
* @returns {{collapsible: boolean, collapsed: boolean, movable: boolean,
|
|
209
|
+
* resizable: boolean, chrome: boolean, title: string}}
|
|
210
|
+
*/
|
|
211
|
+
export function floatBehaviour(menu) {
|
|
212
|
+
const off = {collapsible: false, collapsed: false, movable: false, resizable: false, chrome: false, title: ''};
|
|
213
|
+
if (!menu || menu.position !== 'floating') return off;
|
|
214
|
+
|
|
215
|
+
const f = (menu.float && typeof menu.float === 'object' && !Array.isArray(menu.float)) ? menu.float : {};
|
|
216
|
+
const flags = {
|
|
217
|
+
collapsible: !!f.collapsible,
|
|
218
|
+
collapsed: !!f.collapsible && !!f.collapsed,
|
|
219
|
+
movable: !!f.movable,
|
|
220
|
+
resizable: !!f.resizable
|
|
221
|
+
};
|
|
222
|
+
// The bar carries the toggle and doubles as the drag handle; a menu that
|
|
223
|
+
// can only be resized needs no bar - the UA resize grip is enough.
|
|
224
|
+
const chrome = flags.collapsible || flags.movable;
|
|
225
|
+
const title = typeof f.title === 'string' && f.title !== '' ? f.title : (menu.name || '');
|
|
226
|
+
return {...flags, chrome, title: chrome ? title : ''};
|
|
227
|
+
}
|
|
228
|
+
|
|
187
229
|
// ---------------------------------------------------------------------------
|
|
188
230
|
// Orientation - how the menu flows where it renders. `horizontal` is the
|
|
189
231
|
// historic behaviour and stays the default for every menu without the field.
|
|
@@ -193,6 +235,23 @@ export function floatToCss(menu) {
|
|
|
193
235
|
// `side` only means anything for a vertical menu in the navbar slot - it picks
|
|
194
236
|
// the viewport edge the rail docks to.
|
|
195
237
|
// ---------------------------------------------------------------------------
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// Spacer items - `{type: 'spacer'}`, the sibling of `{type: 'separator'}`.
|
|
240
|
+
//
|
|
241
|
+
// A separator draws a line; a spacer draws nothing and just takes up room. The
|
|
242
|
+
// fixed sizes map to the theme's spacing tokens so a spacer scales with the
|
|
243
|
+
// rest of the UI. `flex` is the interesting one: it soaks up all the leftover
|
|
244
|
+
// room, which is how you push the items after it to the far end of a navbar or
|
|
245
|
+
// down to the foot of a vertical rail.
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
export const SPACER_SIZES = new Set(['xs', 'sm', 'md', 'lg', 'xl', 'flex']);
|
|
248
|
+
export const SPACER_DEFAULT_SIZE = 'md';
|
|
249
|
+
|
|
250
|
+
/** A spacer's size, defaulted and validated. */
|
|
251
|
+
export function spacerSize(item) {
|
|
252
|
+
return SPACER_SIZES.has(item?.size) ? item.size : SPACER_DEFAULT_SIZE;
|
|
253
|
+
}
|
|
254
|
+
|
|
196
255
|
export const ORIENTATIONS = new Set(['horizontal', 'vertical']);
|
|
197
256
|
export const SIDES = new Set(['left', 'right']);
|
|
198
257
|
|
|
@@ -260,7 +319,7 @@ export async function resolveMenuDecorations(items, opts = {}) {
|
|
|
260
319
|
|
|
261
320
|
async function walk(list) {
|
|
262
321
|
return Promise.all((list || []).map(async (it) => {
|
|
263
|
-
if (!it || it.type === 'separator') return it;
|
|
322
|
+
if (!it || it.type === 'separator' || it.type === 'spacer') return it;
|
|
264
323
|
const next = {...it};
|
|
265
324
|
if (it.badge && it.badge.countFrom) {
|
|
266
325
|
try {
|
|
@@ -591,6 +650,15 @@ export function validateMenu(menu) {
|
|
|
591
650
|
if (menu.float.accent != null && menu.float.accent !== '' && !isValidColour(menu.float.accent)) {
|
|
592
651
|
errors.push('float.accent must be a preset key or #rrggbb');
|
|
593
652
|
}
|
|
653
|
+
for (const key of FLOAT_FLAGS) {
|
|
654
|
+
const v = menu.float[key];
|
|
655
|
+
if (v != null && typeof v !== 'boolean') {
|
|
656
|
+
errors.push(`float.${key} must be true or false`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (menu.float.title != null && typeof menu.float.title !== 'string') {
|
|
660
|
+
errors.push('float.title must be a string');
|
|
661
|
+
}
|
|
594
662
|
}
|
|
595
663
|
}
|
|
596
664
|
if (!Array.isArray(menu.items)) {
|
|
@@ -601,6 +669,14 @@ export function validateMenu(menu) {
|
|
|
601
669
|
// Renderers translate them into <hr> / structural breaks.
|
|
602
670
|
if (item && item.type === 'separator') return;
|
|
603
671
|
|
|
672
|
+
// Spacers are blank room, likewise with nothing to point at.
|
|
673
|
+
if (item && item.type === 'spacer') {
|
|
674
|
+
if (item.size != null && !SPACER_SIZES.has(item.size)) {
|
|
675
|
+
errors.push(`Item ${path}: spacer size must be one of: ${[...SPACER_SIZES].join(', ')}`);
|
|
676
|
+
}
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
604
680
|
if (!item.text || typeof item.text !== 'string') {
|
|
605
681
|
errors.push(`Item ${path}: text is required`);
|
|
606
682
|
}
|
|
@@ -116,9 +116,14 @@
|
|
|
116
116
|
</script>
|
|
117
117
|
|
|
118
118
|
<!-- Site initialisation -->
|
|
119
|
-
<script src="/public/js/site.js?v=
|
|
119
|
+
<script src="/public/js/site.js?v=20260829-motion-toggle" type="module"></script>
|
|
120
120
|
<script src="/public/js/collection-context.js?v=20260819-export" type="module"></script>
|
|
121
121
|
|
|
122
|
+
<!-- Floating menu panels: collapse to a hamburger, drag to move, resize.
|
|
123
|
+
Loaded here rather than from site.js so server-rendered overlays and
|
|
124
|
+
[menu] shortcodes are wired even on pages with no floating navbar. -->
|
|
125
|
+
<script src="/public/js/menu-float.js?v=20260829-float-panel" type="module"></script>
|
|
126
|
+
|
|
122
127
|
<!-- Interactive Collection Browser (used by [collection] shortcodes with
|
|
123
128
|
searchable / filterable / sortable / paginate attributes) -->
|
|
124
129
|
<script src="/public/js/collection-browser.js?v=20260523-browser"></script>
|