domma-cms 0.54.2 → 0.55.1
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 +23 -77
- package/admin/js/lib/sidebar-renderer.js +4 -4
- package/admin/js/templates/menu-editor.html +19 -25
- package/admin/js/templates/settings.html +11 -0
- package/admin/js/views/menu-editor.js +13 -13
- package/admin/js/views/settings.js +3 -3
- package/bin/cli.js +9 -2
- package/bin/lib/smtp-defaults.js +53 -0
- package/package.json +1 -1
- package/public/css/menu-highlight.css +1 -1
- package/public/css/site.css +1 -1
- package/public/js/menu-decor.mjs +1 -1
- package/public/js/site.js +1 -1
- package/scripts/setup.js +8 -0
- package/server/services/email.js +29 -3
- package/server/services/health.js +23 -2
- package/server/services/menuRender.js +28 -4
- package/server/services/menus.js +25 -1
- package/server/services/renderer.js +4 -0
package/public/js/site.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{makeBadgeEl as M,applyColour as N,applyPill as B,applyFloat as P,applyHighlight as q,isCurrentItem as O}from"/public/js/menu-decor.mjs?v=20260905-menu-highlight";import{initFloatingMenus as R}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as j,markInvalidFields as V,showValidationPopup as J}from"/public/js/forms.js?v=20260821-wizard-grid";import{buildWizardSteps as W}from"/public/js/form-steps.mjs";$(()=>{const w=window.__CMS_NAV__||{},g=window.__CMS_SITE__||{};if(g.autoTheme?.enabled){let t=function(a){const e=(a||"07:00").split(":");return+e[0]*60+(+e[1]||0)},o=function(){const a=new Date,e=a.getHours()*60+a.getMinutes();return e>=t(n.dayStart)&&e<t(n.nightStart)?n.dayTheme:n.nightTheme};const n=g.autoTheme;Domma.theme.set(o()),setInterval(()=>Domma.theme.set(o()),6e4)}const x=$("#site-navbar");if(x.length&&w.brand){let o=function(i,c=0){return Array.isArray(i)?i.filter(r=>!(r&&r.type==="spacer")).filter(r=>!(r&&r.type==="separator"&&c===0)).map(r=>r&&r.type==="separator"?{divider:!0}:Array.isArray(r?.items)?{...r,items:o(r.items,c+1)}:r):i},a=function(i){return Array.isArray(i)?i.filter(c=>!(c&&(c.type==="separator"||c.type==="spacer"))):[]};const n={...w.brand},t=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let i="";n.logo?i+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(i+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(i+=`<span class="navbar-brand-text${t}">${n.text}</span>`),n.tagline&&(i+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=i}else t&&n.text&&(n.html=`<span class="navbar-brand-text${t}">${n.text}</span>`);const e=w.position==="floating",u=w.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:o(w.items||[]),variant:w.variant||"dark",position:e?"static":w.position||"sticky",collapsible:!0,appearOnHover:!!w.appearOnHover}),Domma.icons.scan("#site-navbar");const h=x.get(0);u&&x.get(0).classList.add("dm-nav-vertical",w.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),q(h,w),e&&(P(x.get(0),w),R(x.get(0).parentNode||document));const y=h.querySelector(".navbar-toggle"),f=h.querySelector(".navbar-collapse");if(y&&f){let i=null;const c=()=>{f.classList.contains("show")&&y.click()},r=()=>{i&&(i.remove(),i=null),document.body.classList.remove("dm-drawer-open")},s=()=>{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")?s():r()})}),f.addEventListener("click",l=>{l.target.closest("a")&&requestAnimationFrame(r)}),document.addEventListener("keydown",l=>{l.key==="Escape"&&f.classList.contains("show")&&c()})}const m=h.querySelector(".navbar-nav");if(m){const i=Array.from(m.children);let c=0;for(const r of w.items||[])if(!(r&&r.type==="separator")){if(r&&r.type==="spacer"){const s=document.createElement("li"),l=["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md";s.className=`dm-menu-spacer dm-menu-spacer--${l}`,s.setAttribute("aria-hidden","true"),m.insertBefore(s,i[c]||null);continue}c++}}const b=a(w.items||[]);h.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(i=>{const c=b[parseInt(i.dataset.index,10)];if(!c)return;const r=i.dataset.subindex!=null,s=r&&Array.isArray(c.items)?c.items[parseInt(i.dataset.subindex,10)]:c;if(!s)return;s.colour&&N(i,s.colour),s.pill&&!r&&B(i,s.pill);const l=M(s.badge);l&&i.appendChild(l),O(s.url,location.pathname)&&(i.classList.add("active"),i.setAttribute("aria-current","page"))})}const C=$("#site-footer");if(C.length){let e=function(s){return String(s??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},u=function(s){return e(s).replace(/'/g,"'")},h=function(s){if(s&&s.type==="separator")return'<li class="footer-sep"><hr></li>';if(s&&s.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(s.size)?s.size:"md"}" aria-hidden="true"></li>`;const l=u(s.url||"#"),d=e(s.text||""),v=Array.isArray(s.items)&&s.items.length?`<ul>${s.items.map(h).join("")}</ul>`:"";return`<li><a href="${l}">${d}</a>${v}</li>`},y=function(s,l){return(s||[]).forEach(d=>{d&&(d.type==="separator"||d.type==="spacer")||(l.push(d),Array.isArray(d.items)&&d.items.length&&y(d.items,l))}),l};const n=g.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 o='<div class="footer-inner container">';const a=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(a.copyright&&(o+=`<p>${e(a.copyright)}</p>`),a.primary.length&&(o+=`<nav class="footer-nav footer-nav--primary"><ul>${a.primary.map(h).join("")}</ul></nav>`),a.legal.length&&(o+=`<nav class="footer-nav footer-nav--legal"><ul>${a.legal.map(h).join("")}</ul></nav>`),g.footer){const s=Object.keys(t).filter(l=>n[l]);s.length&&(o+='<div class="footer-social">',s.forEach(l=>{const{label:d,svg:v}=t[l];o+=`<a href="${n[l]}" target="_blank" rel="noopener noreferrer" aria-label="${d}" class="footer-social-link">${v}</a>`}),o+="</div>")}o+="</div>",C.html(o);const f=[...y(a.primary,[]),...y(a.legal,[])];C.get(0).querySelectorAll(".footer-nav a").forEach((s,l)=>{const d=f[l];if(!d)return;d.colour&&N(s,d.colour);const v=M(d.badge);v&&s.appendChild(v),O(d.url,location.pathname)&&(s.classList.add("is-active"),s.setAttribute("aria-current","page"))}),q(C.get(0).querySelector(".footer-nav--primary"),{highlight:a.primaryHighlight}),q(C.get(0).querySelector(".footer-nav--legal"),{highlight:a.legalHighlight});const b=g.footer?.showMotionToggle!==!1,i=S.get("reduced_motion"),c=i!==null?!!i:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),r=C.get(0).querySelector(".footer-inner");if(b&&r){const s=document.createElement("input");s.type="checkbox",s.className="form-switch-input",s.id="dm-motion-switch",s.checked=c,s.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 d=document.createElement("label");d.className="form-switch footer-motion-switch",d.title="Reduce motion",d.appendChild(s),d.appendChild(l),r.appendChild(d)}}$("#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 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)}),p.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)}),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 n=Array.from(this.querySelectorAll(".dm-progression-item")).map(a=>{const e={title:a.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:a.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return a.dataset.date&&(e.date=a.dataset.date),a.dataset.status&&(e.status=a.dataset.status),a.dataset.entryId&&(e._entryId=a.dataset.entryId),e}),t=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",o=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:o,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",o=n.dataset.revealAnimation||"slide-up",a=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,u=parseInt(n.dataset.revealDelay,10)||0,h=n.dataset.revealDirection==="rtl",y=Array.from(n.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((m,b)=>{m.style.opacity="0",m.style.transform=f[o]||"",m.style.transition=`opacity ${a}ms ease, transform ${a}ms ease`;const i=h?y.length-1-b:b;m.style.transitionDelay=t==="stagger"?`${u+i*e}ms`:`${u}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const m=new IntersectionObserver(b=>{b.forEach(i=>{i.isIntersecting&&(i.target.offsetWidth,i.target.style.opacity="1",i.target.style.transform="none",m.unobserve(i.target))})},{threshold:.1});y.forEach(b=>m.observe(b))}))}),p.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),p.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,t=document.getElementById(n);if(!t)return;const o=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",o.setContent(t),o.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const o=atob(t.dataset.config),a=JSON.parse(o);Object.assign(n,a)}catch{}}),Object.keys(n).length>0){const t={};for(const[o,a]of Object.entries(n)){const e=a?.events?.click,{confirm:u,toast:h,alert:y,prompt:f,...m}=e||{};u||h||y||f?($(o).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:m.promptPlaceholder||"",inputValue:m.promptDefault||""}),c===null))){if(m.target){const r=$(m.target);m.toggleClass&&r.toggleClass(m.toggleClass),m.addClass&&r.addClass(m.addClass),m.removeClass&&r.removeClass(m.removeClass),c!==null&&(m.setText&&r.text(c),m.setVal&&r.val(c),m.setAttr&&r.attr(m.setAttr,c))}m.href&&(window.location.href=m.href),h&&E.toast(h,{type:m.toastType||"success"}),y&&E.alert(y)}}),Object.keys(m).length&&(t[o]={...a,events:{...a.events,click:m}})):t[o]=a}$.setup(t)}}p.length&&D(p.get(0))});function D(w){w.querySelectorAll(".dm-cta-trigger").forEach(g=>{g.__ctaWired||(g.__ctaWired=!0,g.addEventListener("click",async()=>{const x=g.dataset.action,C=g.dataset.entry,k=g.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 n=Array.from(g.childNodes).map(o=>o.cloneNode(!0));g.disabled=!0,g.textContent="Running\u2026";const t=o=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({entryId:C})});try{let o=await t(p);if(o.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:h}=await u.json();S.set("auth_token",h),p=h,o=await t(p)}}}const a=await o.json().catch(()=>({}));if(!o.ok)throw new Error(a.error||a.message||`Error ${o.status}`);E.toast(a.message||"Action completed.",{type:"success"})}catch(o){E.toast(o.message||"Action failed.",{type:"error"})}finally{g.disabled=!1,g.textContent="",n.forEach(o=>g.appendChild(o)),Domma.icons.scan(g)}}))})}(function(){const g=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!g.length)return;const x=S.get("auth_token");function C(p,n){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const o=document.createElement("p");if(n){o.appendChild(document.createTextNode("Please "));const a=document.createElement("a");a.href="/login",a.textContent="sign in",o.appendChild(a),o.appendChild(document.createTextNode(" to view your entries."))}else o.textContent=p;return t.appendChild(o),t}function k(p,n){const t=document.createElement("template");t.innerHTML=n||"",p.replaceWith(t.content)}g.forEach(async p=>{const n=p.dataset.collectionAttrs;if(!n)return;if(!x){p.replaceChildren(C("",!0));return}const t=o=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({attrs:n})});try{let o=x,a=await t(o);if(a.status===401){const u=S.get("auth_refresh_token");if(u){const h=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:u})});if(h.ok){const{token:y}=await h.json();S.set("auth_token",y),o=y,a=await t(o)}}}if(!a.ok){const{error:u}=await a.json().catch(()=>({}));p.replaceChildren(C(u||"Unable to load entries.",!1));return}const{html:e}=await a.json();k(p,e)}catch{p.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 g=document.querySelectorAll("[data-collection-table]");!g.length||typeof T>"u"||!T.create||g.forEach(x=>{let C;try{C=JSON.parse(atob(x.dataset.payload))}catch{return}const{columns:k,rows:p,search:n,sortable:t,exportable:o,pageSize:a,empty:e,ctaConfig:u,slug:h,exportCfg:y}=C;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 r=document.createElement("button");if(r.className=`btn btn-${u.style||"primary"} dm-cta-trigger`,r.dataset.action=u.action||"",r.dataset.entry=c._entryId||"",u.confirm&&(r.dataset.confirm=u.confirm),u.icon){const s=document.createElement("span");s.dataset.icon=u.icon,r.appendChild(s),r.appendChild(document.createTextNode(" "))}return r.appendChild(document.createTextNode(u.label||"Run")),r}})}const f="col-table-"+Math.random().toString(36).slice(2,7),m=document.createElement("div");m.id=f,x.replaceChildren(m),T.create("#"+f,{data:p,columns:k,search:n,sortable:t,exportable:o,pageSize:a,emptyMessage:e}),u&&D(m),x._dmTable={columns:k,rows:p,slug:h,exportCfg:y,render(b){T.create("#"+f,{data:b,columns:k,search:n,sortable:t,exportable:o,pageSize:a,emptyMessage:e}),u&&D(m)}}})})(),(function(){const g=document.querySelectorAll("[data-form-inline]");if(!g.length||typeof F>"u")return;function x(t){const o=t&&t.logic;return!!(o&&(o.visibility||o.requirement))}function C(t,o){const a={};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)),h=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":u?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&o&&(y.span=o);const f={type:h,required:x(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)),a[e.name]=f}),a}function k(t){const o={};return(t||[]).forEach(a=>{if(!(!a.name||a.type==="page-break"||a.type==="spacer")&&(a.type==="select"||a.type==="multiselect")&&a.required){const e=(a.options||[])[0];e&&(o[a.name]=typeof e=="object"?e.value:e)}}),o}function p(t,o){(o||[]).forEach(a=>{if(a.type!=="date"||!a.name)return;const e=t.querySelector(`[name="${a.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(t,o,a){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=o,e.style.cssText=a?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}g.forEach(t=>{let o;try{o=JSON.parse(atob(t.dataset.formInline))}catch{return}const a=o.fields||[],e=o.settings||{},u=Number(e.columns)===3?3:2,h=e.layout||"grid",y=a.some(c=>c.type==="page-break"),f=async c=>{try{const r=t.querySelector(".fb-form-honeypot"),s=r?.querySelector('input[type="text"]')||null,l=r?.querySelector('[name="_t"]')||null,d=Object.assign({},c);delete d._t,r?.dataset.hpName&&delete d[r.dataset.hpName],s!==null&&(d._hp=s.value),l!==null&&(d._t=l.value);const v=await H.post(`/api/forms/submit/${o.slug}`,d);if(v&&v.error){n(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);n(t,v?.message||e.successMessage||"Thank you for your submission.",!1)}catch(r){throw r.formSubmitFailed||n(t,r.message||"Submission failed. Please try again.",!0),r}},m=Date.now();function b(c){const r=c.querySelector("form");if(!r||r.querySelector(".fb-form-honeypot"))return;const s=document.createElement("div");s.className="fb-form-honeypot",s.setAttribute("aria-hidden","true");const l=document.createElement("input");l.name=r.querySelector('[name="website"]')?"_website":"website",s.dataset.hpName=l.name,l.type="text",l.tabIndex=-1,l.autocomplete="url",l.placeholder="https://",s.appendChild(l);const d=document.createElement("input");d.name="_t",d.type="hidden",d.value=m,s.appendChild(d),r.appendChild(s)}function i(c){if(!window.FormLogicEngine||!a.some(s=>s.logic))return null;const r=new window.FormLogicEngine.FormLogicRuntime(o,t,c?{baseValues:c}:void 0);if(r.init(),t._formLogicRuntime=r,t._formLogicObserved)return r;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const s=new MutationObserver(function(l){for(const d of l)for(const v of d.removedNodes)if(v===t||v.nodeType===1&&v.contains&&v.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),s.disconnect();return}});s.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=s}return r}if(y&&F.wizard){const c=W(a,o.title),r=c.map(_=>({title:_.title,description:_.description,fields:C(_.fields,u)})),s=c.map(_=>_.fields);let l=0,d=null,v=null;const A=_=>{const I=window.FormLogicEngine;if(!I||!I.validateFields)return;const L=I.validateFields(t,s[l]||[]);L.valid||(_.preventDefault(),_.stopImmediatePropagation(),V(t,L.missing),J(L))},z=()=>{const _=s[l]||a;p(t,_),j(t,_),e.honeypot!==!1&&b(t),d&&d.destroy(),d=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:r},layout:h,columns:u,finishText:e.submitText||"Finish",onSubmit:f,onStepChange:_=>{l=_,setTimeout(z,0)}}),Promise.resolve(v).then(z)}else if(F.render){const c=F.render(t,C(a,u),k(a),{submitText:e.submitText||"Submit",layout:h,columns:u,onSubmit:f});Promise.resolve(c).then(function(){if(h==="grid"&&e.submitSpan==="full"){const r=t.querySelector(".form-buttons");r&&r.classList.add("col-span-full")}p(t,a),j(t,a),e.honeypot!==!1&&b(t),i()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
|
1
|
+
import{makeBadgeEl as M,applyColour as N,applyPill as B,applyFloat as P,applyHighlight as z,applyItemHighlight as R,itemHighlightAttrs as V,isCurrentItem as O,menuAlign as J}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{initFloatingMenus as W}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as j,markInvalidFields as G,showValidationPopup as X}from"/public/js/forms.js?v=20260821-wizard-grid";import{buildWizardSteps as Y}from"/public/js/form-steps.mjs";$(()=>{const b=window.__CMS_NAV__||{},g=window.__CMS_SITE__||{};if(g.autoTheme?.enabled){let t=function(a){const e=(a||"07:00").split(":");return+e[0]*60+(+e[1]||0)},n=function(){const a=new Date,e=a.getHours()*60+a.getMinutes();return e>=t(o.dayStart)&&e<t(o.nightStart)?o.dayTheme:o.nightTheme};const o=g.autoTheme;Domma.theme.set(n()),setInterval(()=>Domma.theme.set(n()),6e4)}const x=$("#site-navbar");if(x.length&&b.brand){let n=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:n(s.items,c+1)}:s):i},a=function(i){return Array.isArray(i)?i.filter(c=>!(c&&(c.type==="separator"||c.type==="spacer"))):[]};const o={...b.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=b.position==="floating",d=b.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:o,items:n(b.items||[]),variant:b.variant||"dark",position:e?"static":b.position||"sticky",collapsible:!0,appearOnHover:!!b.appearOnHover}),Domma.icons.scan("#site-navbar");const h=x.get(0);h.classList.add(`dm-nav-align-${J(b)||"centre"}`),d&&x.get(0).classList.add("dm-nav-vertical",b.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),z(h,b),e&&(P(x.get(0),b),W(x.get(0).parentNode||document));const y=h.querySelector(".navbar-toggle"),f=h.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 u=h.querySelector(".navbar-nav");if(u){const i=Array.from(u.children);let c=0;for(const s of b.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"),u.insertBefore(r,i[c]||null);continue}c++}}const w=a(b.items||[]);h.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(i=>{const c=w[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&&N(i,r.colour),r.pill&&!s&&B(i,r.pill),R(i,r);const l=M(r.badge);l&&i.appendChild(l),O(r.url,location.pathname)&&(i.classList.add("active"),i.setAttribute("aria-current","page"))})}const C=$("#site-footer");if(C.length){let e=function(r){return String(r??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},d=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>`;const l=d(r.url||"#"),m=e(r.text||""),v=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(h).join("")}</ul>`:"",A=V(r),D=[A.hover&&` data-menu-item-hover="${d(A.hover)}"`,A.active&&` data-menu-item-active="${d(A.active)}"`].filter(Boolean).join(""),k=A.accent?` style="--dm-menu-highlight:${d(A.accent)}"`:"";return`<li><a href="${l}"${D}${k}>${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=g.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 a=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(a.copyright&&(n+=`<p>${e(a.copyright)}</p>`),a.primary.length&&(n+=`<nav class="footer-nav footer-nav--primary"><ul>${a.primary.map(h).join("")}</ul></nav>`),a.legal.length&&(n+=`<nav class="footer-nav footer-nav--legal"><ul>${a.legal.map(h).join("")}</ul></nav>`),g.footer){const r=Object.keys(t).filter(l=>o[l]);r.length&&(n+='<div class="footer-social">',r.forEach(l=>{const{label:m,svg:v}=t[l];n+=`<a href="${o[l]}" target="_blank" rel="noopener noreferrer" aria-label="${m}" class="footer-social-link">${v}</a>`}),n+="</div>")}n+="</div>",C.html(n);const f=[...y(a.primary,[]),...y(a.legal,[])];C.get(0).querySelectorAll(".footer-nav a").forEach((r,l)=>{const m=f[l];if(!m)return;m.colour&&N(r,m.colour);const v=M(m.badge);v&&r.appendChild(v),O(m.url,location.pathname)&&(r.classList.add("is-active"),r.setAttribute("aria-current","page"))}),z(C.get(0).querySelector(".footer-nav--primary"),{highlight:a.primaryHighlight}),z(C.get(0).querySelector(".footer-nav--legal"),{highlight:a.legalHighlight});const w=g.footer?.showMotionToggle!==!1,i=S.get("reduced_motion"),c=i!==null?!!i:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),s=C.get(0).querySelector(".footer-inner");if(w&&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(a=>{const e={title:a.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:a.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return a.dataset.date&&(e.date=a.dataset.date),a.dataset.status&&(e.status=a.dataset.status),a.dataset.entryId&&(e._entryId=a.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",a=parseInt(o.dataset.revealDuration,10)||400,e=parseInt(o.dataset.revealStagger,10)||60,d=parseInt(o.dataset.revealDelay,10)||0,h=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((u,w)=>{u.style.opacity="0",u.style.transform=f[n]||"",u.style.transition=`opacity ${a}ms ease, transform ${a}ms ease`;const i=h?y.length-1-w:w;u.style.transitionDelay=t==="stagger"?`${d+i*e}ms`:`${d}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const u=new IntersectionObserver(w=>{w.forEach(i=>{i.isIntersecting&&(i.target.offsetWidth,i.target.style.opacity="1",i.target.style.transform="none",u.unobserve(i.target))})},{threshold:.1});y.forEach(w=>u.observe(w))}))}),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 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=atob(t.dataset.config),a=JSON.parse(n);Object.assign(o,a)}catch{}}),Object.keys(o).length>0){const t={};for(const[n,a]of Object.entries(o)){const e=a?.events?.click,{confirm:d,toast:h,alert:y,prompt:f,...u}=e||{};d||h||y||f?($(n).on("click",async function(i){if(i.preventDefault(),d&&!await E.confirm(d))return;let c=null;if(!(f&&(c=await E.prompt(f,{inputPlaceholder:u.promptPlaceholder||"",inputValue:u.promptDefault||""}),c===null))){if(u.target){const s=$(u.target);u.toggleClass&&s.toggleClass(u.toggleClass),u.addClass&&s.addClass(u.addClass),u.removeClass&&s.removeClass(u.removeClass),c!==null&&(u.setText&&s.text(c),u.setVal&&s.val(c),u.setAttr&&s.attr(u.setAttr,c))}u.href&&(window.location.href=u.href),h&&E.toast(h,{type:u.toastType||"success"}),y&&E.alert(y)}}),Object.keys(u).length&&(t[n]={...a,events:{...a.events,click:u}})):t[n]=a}$.setup(t)}}p.length&&L(p.get(0))});function L(b){b.querySelectorAll(".dm-cta-trigger").forEach(g=>{g.__ctaWired||(g.__ctaWired=!0,g.addEventListener("click",async()=>{const x=g.dataset.action,C=g.dataset.entry,_=g.dataset.confirm;let p=S.get("auth_token");if(!p){E.toast("Please log in to perform this action.",{type:"warning"});return}if(_&&!await E.confirm(_))return;const o=Array.from(g.childNodes).map(n=>n.cloneNode(!0));g.disabled=!0,g.textContent="Running\u2026";const t=n=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify({entryId:C})});try{let n=await t(p);if(n.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:h}=await d.json();S.set("auth_token",h),p=h,n=await t(p)}}}const a=await n.json().catch(()=>({}));if(!n.ok)throw new Error(a.error||a.message||`Error ${n.status}`);E.toast(a.message||"Action completed.",{type:"success"})}catch(n){E.toast(n.message||"Action failed.",{type:"error"})}finally{g.disabled=!1,g.textContent="",o.forEach(n=>g.appendChild(n)),Domma.icons.scan(g)}}))})}(function(){const g=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!g.length)return;const x=S.get("auth_token");function C(p,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 a=document.createElement("a");a.href="/login",a.textContent="sign in",n.appendChild(a),n.appendChild(document.createTextNode(" to view your entries."))}else n.textContent=p;return t.appendChild(n),t}function _(p,o){const t=document.createElement("template");t.innerHTML=o||"",p.replaceWith(t.content)}g.forEach(async p=>{const o=p.dataset.collectionAttrs;if(!o)return;if(!x){p.replaceChildren(C("",!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=x,a=await t(n);if(a.status===401){const d=S.get("auth_refresh_token");if(d){const h=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:d})});if(h.ok){const{token:y}=await h.json();S.set("auth_token",y),n=y,a=await t(n)}}}if(!a.ok){const{error:d}=await a.json().catch(()=>({}));p.replaceChildren(C(d||"Unable to load entries.",!1));return}const{html:e}=await a.json();_(p,e)}catch{p.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(),L(document.body)},100)})(),(function(){const g=document.querySelectorAll("[data-collection-table]");!g.length||typeof T>"u"||!T.create||g.forEach(x=>{let C;try{C=JSON.parse(atob(x.dataset.payload))}catch{return}const{columns:_,rows:p,search:o,sortable:t,exportable:n,pageSize:a,empty:e,ctaConfig:d,slug:h,exportCfg:y}=C;if(!_?.length)return;if(d){const w=_.findIndex(i=>i.key==="_cta");w!==-1&&(_[w]={key:"_cta",title:"",render:(i,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 r=document.createElement("span");r.dataset.icon=d.icon,s.appendChild(r),s.appendChild(document.createTextNode(" "))}return s.appendChild(document.createTextNode(d.label||"Run")),s}})}const f="col-table-"+Math.random().toString(36).slice(2,7),u=document.createElement("div");u.id=f,x.replaceChildren(u),T.create("#"+f,{data:p,columns:_,search:o,sortable:t,exportable:n,pageSize:a,emptyMessage:e}),d&&L(u),x._dmTable={columns:_,rows:p,slug:h,exportCfg:y,render(w){T.create("#"+f,{data:w,columns:_,search:o,sortable:t,exportable:n,pageSize:a,emptyMessage:e}),d&&L(u)}}})})(),(function(){const g=document.querySelectorAll("[data-form-inline]");if(!g.length||typeof F>"u")return;function x(t){const n=t&&t.logic;return!!(n&&(n.visibility||n.requirement))}function C(t,n){const a={};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)),h=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":d?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&n&&(y.span=n);const f={type:h,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&&(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)),a[e.name]=f}),a}function _(t){const n={};return(t||[]).forEach(a=>{if(!(!a.name||a.type==="page-break"||a.type==="spacer")&&(a.type==="select"||a.type==="multiselect")&&a.required){const e=(a.options||[])[0];e&&(n[a.name]=typeof e=="object"?e.value:e)}}),n}function p(t,n){(n||[]).forEach(a=>{if(a.type!=="date"||!a.name)return;const e=t.querySelector(`[name="${a.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function o(t,n,a){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=a?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}g.forEach(t=>{let n;try{n=JSON.parse(atob(t.dataset.formInline))}catch{return}const a=n.fields||[],e=n.settings||{},d=Number(e.columns)===3?3:2,h=e.layout||"grid",y=a.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/${n.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}},u=Date.now();function w(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=u,r.appendChild(m),s.appendChild(r)}function i(c){if(!window.FormLogicEngine||!a.some(r=>r.logic))return null;const s=new window.FormLogicEngine.FormLogicRuntime(n,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=Y(a,n.title),s=c.map(k=>({title:k.title,description:k.description,fields:C(k.fields,d)})),r=c.map(k=>k.fields);let l=0,m=null,v=null;const A=k=>{const I=window.FormLogicEngine;if(!I||!I.validateFields)return;const q=I.validateFields(t,r[l]||[]);q.valid||(k.preventDefault(),k.stopImmediatePropagation(),G(t,q.missing),X(q))},D=()=>{const k=r[l]||a;p(t,k),j(t,k),e.honeypot!==!1&&w(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:h,columns:d,finishText:e.submitText||"Finish",onSubmit:f,onStepChange:k=>{l=k,setTimeout(D,0)}}),Promise.resolve(v).then(D)}else if(F.render){const c=F.render(t,C(a,d),_(a),{submitText:e.submitText||"Submit",layout:h,columns:d,onSubmit:f});Promise.resolve(c).then(function(){if(h==="grid"&&e.submitSpan==="full"){const s=t.querySelector(".form-buttons");s&&s.classList.add("col-span-full")}p(t,a),j(t,a),e.honeypot!==!1&&w(t),i()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
package/scripts/setup.js
CHANGED
|
@@ -241,10 +241,18 @@ section('4. Site Identity');
|
|
|
241
241
|
rl.close();
|
|
242
242
|
|
|
243
243
|
const site = JSON.parse(await readFile(SITE_CONFIG, 'utf8'));
|
|
244
|
+
const previousTitle = site.title;
|
|
244
245
|
if (siteTitle) site.title = siteTitle;
|
|
245
246
|
if (tagline) site.tagline = tagline;
|
|
246
247
|
site.seo = site.seo ?? {};
|
|
247
248
|
if (siteTitle) site.seo.defaultTitle = siteTitle;
|
|
249
|
+
// The scaffolder seeds smtp.fromName from the placeholder site name, so
|
|
250
|
+
// carry a renamed site through to it - otherwise every email a fresh
|
|
251
|
+
// install sends is signed "My Site". Only when it still holds the name the
|
|
252
|
+
// site had a moment ago, so a From name someone chose deliberately stands.
|
|
253
|
+
if (siteTitle && site.smtp && (!site.smtp.fromName || site.smtp.fromName === previousTitle)) {
|
|
254
|
+
site.smtp.fromName = siteTitle;
|
|
255
|
+
}
|
|
248
256
|
await writeFile(SITE_CONFIG, JSON.stringify(site, null, 4) + '\n', 'utf8');
|
|
249
257
|
|
|
250
258
|
const nav = JSON.parse(await readFile(NAV_CONFIG, 'utf8'));
|
package/server/services/email.js
CHANGED
|
@@ -45,6 +45,21 @@ export function escapeHtml(str) {
|
|
|
45
45
|
.replace(/'/g, ''');
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Does this site have somewhere real to send mail?
|
|
50
|
+
*
|
|
51
|
+
* The host is the only field that decides it: everything else has a usable
|
|
52
|
+
* default, and an empty host is what sends the whole site to Ethereal. Shared
|
|
53
|
+
* with the health check and the admin so all three agree on what "configured"
|
|
54
|
+
* means.
|
|
55
|
+
*
|
|
56
|
+
* @param {{host?: string}} smtp
|
|
57
|
+
* @returns {boolean}
|
|
58
|
+
*/
|
|
59
|
+
export function isSmtpConfigured(smtp) {
|
|
60
|
+
return typeof smtp?.host === 'string' && smtp.host.trim() !== '';
|
|
61
|
+
}
|
|
62
|
+
|
|
48
63
|
/**
|
|
49
64
|
* Create a nodemailer transport.
|
|
50
65
|
* Falls back to an Ethereal test account when no SMTP host is configured.
|
|
@@ -54,7 +69,7 @@ export function escapeHtml(str) {
|
|
|
54
69
|
* @throws {Error} If Ethereal test account creation fails.
|
|
55
70
|
*/
|
|
56
71
|
export async function createTransport(smtp) {
|
|
57
|
-
if (smtp
|
|
72
|
+
if (isSmtpConfigured(smtp)) {
|
|
58
73
|
return nodemailer.createTransport({
|
|
59
74
|
host: smtp.host,
|
|
60
75
|
port: smtp.port || 587,
|
|
@@ -64,9 +79,20 @@ export async function createTransport(smtp) {
|
|
|
64
79
|
});
|
|
65
80
|
}
|
|
66
81
|
|
|
67
|
-
// No SMTP configured - use Ethereal for dev/demo
|
|
82
|
+
// No SMTP configured - use Ethereal for dev/demo.
|
|
83
|
+
//
|
|
84
|
+
// This is the quiet failure mode of the whole email feature: Ethereal
|
|
85
|
+
// accepts the message, nodemailer reports success, the health check records
|
|
86
|
+
// a good send, and nothing is delivered to anyone. Password resets and form
|
|
87
|
+
// notifications all "work". Say so at warn level rather than as a friendly
|
|
88
|
+
// info line, and say it in terms of the consequence - the admin carries the
|
|
89
|
+
// same warning on the Email tab, and checkSmtpConfig() surfaces it in
|
|
90
|
+
// Health. A site scaffolded by the CLI or the fleet manager ships with a
|
|
91
|
+
// host precisely so it never lands here.
|
|
68
92
|
const testAccount = await nodemailer.createTestAccount();
|
|
69
|
-
console.
|
|
93
|
+
console.warn('[email] NO SMTP HOST CONFIGURED - mail is going to an Ethereal test inbox '
|
|
94
|
+
+ 'and will NOT reach its recipients. Set one in Settings > Email. '
|
|
95
|
+
+ 'Ethereal account: ' + testAccount.user);
|
|
70
96
|
return nodemailer.createTransport({
|
|
71
97
|
host: 'smtp.ethereal.email',
|
|
72
98
|
port: 587,
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import fs from 'fs/promises';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import {fileURLToPath} from 'url';
|
|
9
|
-
import {config} from '../config.js';
|
|
10
|
-
import {getLastSendResult} from './email.js';
|
|
9
|
+
import {config, getConfig} from '../config.js';
|
|
10
|
+
import {getLastSendResult, isSmtpConfigured} from './email.js';
|
|
11
11
|
import {listPages} from './content.js';
|
|
12
12
|
import {getPluginLoadFailures} from './plugins.js';
|
|
13
13
|
import {FORMS_DIR} from './forms.js';
|
|
@@ -203,6 +203,26 @@ async function checkSmtp() {
|
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
// Distinct from checkSmtp above, which reports the LAST SEND. That check goes
|
|
207
|
+
// green on an Ethereal send, because as far as nodemailer is concerned the
|
|
208
|
+
// message was accepted - so on a site with no relay the two checks disagree on
|
|
209
|
+
// purpose: "last email send: ok" next to "SMTP host: not configured" is the
|
|
210
|
+
// whole story, and neither half tells it alone.
|
|
211
|
+
async function checkSmtpConfig() {
|
|
212
|
+
const smtp = getConfig('site')?.smtp;
|
|
213
|
+
if (isSmtpConfigured(smtp)) {
|
|
214
|
+
return { id: 'smtp-config', level: 'ok', label: 'SMTP host', value: smtp.host };
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
id: 'smtp-config',
|
|
218
|
+
level: 'warn',
|
|
219
|
+
label: 'SMTP host',
|
|
220
|
+
value: 'not configured',
|
|
221
|
+
detail: ['Mail is going to an Ethereal test inbox and will not reach its recipients.',
|
|
222
|
+
'Set a host in Settings > Email.']
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
206
226
|
async function checkPluginLoad() {
|
|
207
227
|
const failures = getPluginLoadFailures();
|
|
208
228
|
return {
|
|
@@ -265,6 +285,7 @@ export async function getHealth() {
|
|
|
265
285
|
safeCheck('missing-meta', checkMissingMeta),
|
|
266
286
|
safeCheck('last-form', checkLastForm),
|
|
267
287
|
safeCheck('smtp', checkSmtp),
|
|
288
|
+
safeCheck('smtp-config', checkSmtpConfig),
|
|
268
289
|
safeCheck('plugin-load', checkPluginLoad)
|
|
269
290
|
]);
|
|
270
291
|
const value = { status: worstLevel(checks), checks };
|
|
@@ -14,7 +14,7 @@ import {colourToCss, floatToCss, floatBehaviour, spacerSize} from './menus.js';
|
|
|
14
14
|
// Highlight helpers live once, in the browser module, and are imported here -
|
|
15
15
|
// the same shared-module arrangement collection-export.mjs has with
|
|
16
16
|
// collections.js. Nothing in them touches the DOM.
|
|
17
|
-
import {highlightAttrs, isCurrentItem} from '../../public/js/menu-decor.mjs';
|
|
17
|
+
import {highlightAttrs, itemHighlightAttrs, isCurrentItem, menuAlign} from '../../public/js/menu-decor.mjs';
|
|
18
18
|
|
|
19
19
|
/** Attribute-safe escaping - mirrors escapeAttr in markdown.js. */
|
|
20
20
|
function esc(str) {
|
|
@@ -57,9 +57,26 @@ export function renderMenuItemsAsUl(items, currentPath = '') {
|
|
|
57
57
|
const current = isCurrentItem(it.url, currentPath);
|
|
58
58
|
const classAttr = current ? ' class="is-active" aria-current="page"' : '';
|
|
59
59
|
|
|
60
|
+
// Per-item highlight override. `data-menu-item-*` rather than the
|
|
61
|
+
// menu's `data-menu-*` so the CSS can let the item win without the two
|
|
62
|
+
// rules colliding on specificity - see the note in menu-highlight.css.
|
|
63
|
+
// The sanitiser allows `data-*` and `style` on every tag, so both
|
|
64
|
+
// survive the `[menu]` shortcode's trip through sanitize-html.
|
|
65
|
+
const ihl = itemHighlightAttrs(it);
|
|
66
|
+
const ihlAttrs = [
|
|
67
|
+
ihl.hover && ` data-menu-item-hover="${esc(ihl.hover)}"`,
|
|
68
|
+
ihl.active && ` data-menu-item-active="${esc(ihl.active)}"`
|
|
69
|
+
].filter(Boolean).join('');
|
|
70
|
+
|
|
60
71
|
// Colour override (the server sanitiser allows inline style on <a>).
|
|
72
|
+
// The item's accent rides in the same style attribute: it inherits, so
|
|
73
|
+
// it shadows the container's `--dm-menu-highlight` for this item alone.
|
|
61
74
|
const colourCss = it.colour ? colourToCss(it.colour) : '';
|
|
62
|
-
const
|
|
75
|
+
const styleCss = [
|
|
76
|
+
colourCss && `color:${colourCss}`,
|
|
77
|
+
ihl.accent && `--dm-menu-highlight:${ihl.accent}`
|
|
78
|
+
].filter(Boolean).join(';');
|
|
79
|
+
const styleAttr = styleCss ? ` style="${esc(styleCss)}"` : '';
|
|
63
80
|
|
|
64
81
|
// Badge - static text, or a count stamped on by resolveMenuDecorations.
|
|
65
82
|
let badge = '';
|
|
@@ -69,7 +86,7 @@ export function renderMenuItemsAsUl(items, currentPath = '') {
|
|
|
69
86
|
badge = `<span class="dm-menu-badge"${badgeStyle}>${esc(String(it.badge.text))}</span>`;
|
|
70
87
|
}
|
|
71
88
|
|
|
72
|
-
return `<li><a href="${href}"${classAttr}${styleAttr}>${text}${badge}</a>${child}</li>`;
|
|
89
|
+
return `<li><a href="${href}"${classAttr}${styleAttr}${ihlAttrs}>${text}${badge}</a>${child}</li>`;
|
|
73
90
|
}).join('');
|
|
74
91
|
return `<ul>${lis}</ul>`;
|
|
75
92
|
}
|
|
@@ -99,6 +116,13 @@ export function buildMenuNav(menu, items, opts = {}) {
|
|
|
99
116
|
: (['horizontal', 'vertical'].includes(menu.orientation) ? menu.orientation : null);
|
|
100
117
|
const orientClass = orientation ? ` dm-menu--${orientation}` : '';
|
|
101
118
|
|
|
119
|
+
// Alignment only has anywhere to go in a horizontal layout - a vertical
|
|
120
|
+
// list is as wide as its box. Unlike the navbar, an unset value emits no
|
|
121
|
+
// class: a `[menu]` in the page body has always packed left and a silent
|
|
122
|
+
// change of that on upgrade is not wanted.
|
|
123
|
+
const align = orientation === 'horizontal' ? menuAlign(menu) : '';
|
|
124
|
+
const alignClass = align ? ` dm-menu--align-${align}` : '';
|
|
125
|
+
|
|
102
126
|
const floatCss = opts.noFloat ? '' : floatToCss(menu);
|
|
103
127
|
const floatClass = floatCss ? ' dm-menu-floating' : '';
|
|
104
128
|
const floatAnchorAttr = floatCss
|
|
@@ -134,7 +158,7 @@ export function buildMenuNav(menu, items, opts = {}) {
|
|
|
134
158
|
? ` style="${esc([floatCss, hlStyle].filter(Boolean).join(';'))}"`
|
|
135
159
|
: '';
|
|
136
160
|
|
|
137
|
-
return `<nav class="dm-menu dm-menu--${esc(menu.slug)}${orientClass}${variantClass}${floatClass}${collapsedClass}${flexClass}${klass}"`
|
|
161
|
+
return `<nav class="dm-menu dm-menu--${esc(menu.slug)}${orientClass}${alignClass}${variantClass}${floatClass}${collapsedClass}${flexClass}${klass}"`
|
|
138
162
|
+ ` data-menu="${esc(menu.slug)}"${styleAttr}${floatAnchorAttr}${behAttrs}${hlAttrs}>`
|
|
139
163
|
+ buildFloatChrome(menu, beh)
|
|
140
164
|
+ renderMenuItemsAsUl(items, opts.currentPath || '')
|
package/server/services/menus.js
CHANGED
|
@@ -17,7 +17,7 @@ import * as cache from './cache/index.js';
|
|
|
17
17
|
// The highlight vocabularies are defined once, in the browser module, and
|
|
18
18
|
// imported here so validation and the renderer can never drift apart.
|
|
19
19
|
import {
|
|
20
|
-
HIGHLIGHT_HOVER, HIGHLIGHT_ACTIVE,
|
|
20
|
+
HIGHLIGHT_HOVER, HIGHLIGHT_ACTIVE, MENU_ALIGN,
|
|
21
21
|
LEGACY_STYLE_HOVER, LEGACY_STYLE_ACTIVE, legacyStyleHighlight,
|
|
22
22
|
PRESET_COLOUR_VARS, PRESET_COLOUR_FALLBACKS, colourToCss
|
|
23
23
|
} from '../../public/js/menu-decor.mjs';
|
|
@@ -437,6 +437,7 @@ export async function createMenu(input) {
|
|
|
437
437
|
...(input.variant != null && {variant: input.variant}),
|
|
438
438
|
...(input.position != null && {position: input.position}),
|
|
439
439
|
...(input.orientation != null && {orientation: input.orientation}),
|
|
440
|
+
...(input.align != null && {align: input.align}),
|
|
440
441
|
...(input.side != null && {side: input.side}),
|
|
441
442
|
...(input.appearOnHover != null && {appearOnHover: !!input.appearOnHover}),
|
|
442
443
|
...(input.float != null && {float: input.float}),
|
|
@@ -481,6 +482,7 @@ export async function updateMenu(slug, input) {
|
|
|
481
482
|
...(input.variant != null && {variant: input.variant}),
|
|
482
483
|
...(input.position != null && {position: input.position}),
|
|
483
484
|
...(input.orientation != null && {orientation: input.orientation}),
|
|
485
|
+
...(input.align != null && {align: input.align}),
|
|
484
486
|
...(input.side != null && {side: input.side}),
|
|
485
487
|
...(input.appearOnHover != null && {appearOnHover: !!input.appearOnHover}),
|
|
486
488
|
...(input.float != null && {float: input.float}),
|
|
@@ -677,6 +679,9 @@ export function validateMenu(menu) {
|
|
|
677
679
|
}
|
|
678
680
|
}
|
|
679
681
|
}
|
|
682
|
+
if (menu.align != null && menu.align !== '' && !MENU_ALIGN.includes(menu.align)) {
|
|
683
|
+
errors.push(`align must be one of: ${MENU_ALIGN.join(', ')}`);
|
|
684
|
+
}
|
|
680
685
|
if (menu.highlight != null) {
|
|
681
686
|
if (typeof menu.highlight !== 'object' || Array.isArray(menu.highlight)) {
|
|
682
687
|
errors.push('highlight must be an object');
|
|
@@ -760,6 +765,25 @@ export function validateMenu(menu) {
|
|
|
760
765
|
if (item.colour != null && !isValidColour(item.colour)) {
|
|
761
766
|
errors.push(`Item ${path}: colour must be a preset key or #rrggbb`);
|
|
762
767
|
}
|
|
768
|
+
// Per-item highlight override. Same shape and same value sets as
|
|
769
|
+
// the menu-level `highlight` block validated above - an item that
|
|
770
|
+
// omits a half falls through to the menu's choice for that half.
|
|
771
|
+
if (item.highlight != null) {
|
|
772
|
+
if (typeof item.highlight !== 'object' || Array.isArray(item.highlight)) {
|
|
773
|
+
errors.push(`Item ${path}: highlight must be an object`);
|
|
774
|
+
} else {
|
|
775
|
+
const {hover, active, accent} = item.highlight;
|
|
776
|
+
if (hover != null && hover !== '' && !HIGHLIGHT_HOVER.includes(hover)) {
|
|
777
|
+
errors.push(`Item ${path}: highlight.hover must be one of: ${HIGHLIGHT_HOVER.join(', ')}`);
|
|
778
|
+
}
|
|
779
|
+
if (active != null && active !== '' && !HIGHLIGHT_ACTIVE.includes(active)) {
|
|
780
|
+
errors.push(`Item ${path}: highlight.active must be one of: ${HIGHLIGHT_ACTIVE.join(', ')}`);
|
|
781
|
+
}
|
|
782
|
+
if (accent != null && accent !== '' && !isValidColour(accent)) {
|
|
783
|
+
errors.push(`Item ${path}: highlight.accent must be a preset key or #rrggbb`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
763
787
|
});
|
|
764
788
|
}
|
|
765
789
|
return errors;
|
|
@@ -140,6 +140,9 @@ export async function renderPage(page, opts = {}) {
|
|
|
140
140
|
// the horizontal default, so __CMS_NAV__ stays unchanged for
|
|
141
141
|
// existing sites.
|
|
142
142
|
...(navMenu.orientation === 'vertical' && {orientation: 'vertical', side: navMenu.side === 'right' ? 'right' : 'left'}),
|
|
143
|
+
// Where the items sit along the bar. Like `highlight`, the navbar
|
|
144
|
+
// is drawn in the browser, so the value has to travel to site.js.
|
|
145
|
+
...(navMenu.align && {align: navMenu.align}),
|
|
143
146
|
...(navMenu.float && {float: navMenu.float}),
|
|
144
147
|
...(navMenu.appearOnHover && {appearOnHover: true}),
|
|
145
148
|
// Item highlighting: the navbar is built in the browser, so the
|
|
@@ -717,6 +720,7 @@ export async function renderBlogPage(templatePath, data = {}, seoMeta = {}) {
|
|
|
717
720
|
// the horizontal default, so __CMS_NAV__ stays unchanged for
|
|
718
721
|
// existing sites.
|
|
719
722
|
...(navMenu.orientation === 'vertical' && {orientation: 'vertical', side: navMenu.side === 'right' ? 'right' : 'left'}),
|
|
723
|
+
...(navMenu.align && {align: navMenu.align}),
|
|
720
724
|
...(navMenu.appearOnHover && {appearOnHover: true}),
|
|
721
725
|
// Item highlighting: the navbar is built in the browser, so the
|
|
722
726
|
// config has to travel to site.js rather than being rendered here.
|