domma-cms 0.25.13 → 0.25.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/admin/css/dashboard.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.dashboard-grid{display:flex;flex-direction:column;gap:var(--dm-spacing-4, 16px)}.dash-row{display:grid;gap:var(--dm-spacing-4, 16px)}.dash-row-kpi{grid-template-columns:repeat(4,1fr)}.dash-row-traffic{grid-template-columns:2fr 1fr}.dash-row-content,.dash-row-health{grid-template-columns:1fr 1fr}@media(max-width:960px){.dash-row-kpi{grid-template-columns:repeat(2,1fr)}.dash-row-traffic,.dash-row-content,.dash-row-health{grid-template-columns:1fr}}.dash-card{background:var(--dm-surface, #fff);border:1px solid var(--dm-border, #e5e7eb);border-radius:var(--dm-radius, 8px);padding:var(--dm-spacing-4, 16px)}.dash-kpi{display:flex;align-items:center;gap:var(--dm-spacing-3, 12px)}.dash-kpi-icon{font-size:24px;opacity:.8}.dash-kpi-value{font-size:28px;font-weight:600;line-height:1}.dash-kpi-label{font-size:12px;color:var(--dm-muted, #6b7280);text-transform:uppercase;letter-spacing:.05em}.dash-kpi-delta{font-size:12px;margin-top:4px}.dash-kpi-delta.up{color:var(--dm-success, #16a34a)}.dash-kpi-delta.down{color:var(--dm-danger, #dc2626)}.dash-health-pill{display:inline-flex;align-items:center;gap:6px}.dash-health-pill[data-level=ok]{color:var(--dm-success, #16a34a)}.dash-health-pill[data-level=warn]{color:var(--dm-warning, #d97706)}.dash-health-pill[data-level=fail]{color:var(--dm-danger, #dc2626)}.dash-spike-row{display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px dashed var(--dm-border, #e5e7eb)}.dash-spike-row:last-child{border-bottom:0}.dash-spark{display:inline-block;width:80px;height:24px;vertical-align:middle;margin-left:8px}.dash-warnings{background:var(--dm-warning-bg, #fef3c7);border:1px solid var(--dm-warning, #d97706);color:var(--dm-warning-fg, #78350f);padding:8px 12px;border-radius:var(--dm-radius, 8px);margin-bottom:12px}.dash-updated{font-size:12px;color:var(--dm-muted, #6b7280);margin-right:8px}.dash-journey-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}@media(max-width:720px){.dash-journey-grid{grid-template-columns:1fr}}.dash-journey-grid h4{font-size:12px;text-transform:uppercase;color:var(--dm-muted, #6b7280);margin:0 0 6px}.dash-bounce{margin-top:12px;font-size:14px;color:var(--dm-muted, #6b7280)}.dash-empty{font-size:13px;color:var(--dm-muted, #6b7280);margin:8px 0 0}.dash-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.dash-card-header h3{margin:0}.dash-cache-toggle{display:inline-flex;align-items:center;gap:8px;font-size:13px;color:var(--dm-muted, #6b7280);cursor:pointer}.dash-cache-toggle input[type=checkbox]{transform:scale(1.2);cursor:pointer}.dash-cache-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:12px}@media(max-width:720px){.dash-cache-grid{grid-template-columns:repeat(2,1fr)}}.dash-cache-stat-label{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--dm-muted, #6b7280);margin-bottom:4px}.dash-cache-stat-value{font-size:22px;font-weight:600;line-height:1.1}.dash-cache-stat-sub{font-size:12px;color:var(--dm-muted, #6b7280);margin-top:4px;min-height:14px}[data-field=size-bar]{height:4px;background:var(--dm-border, #e5e7eb);border-radius:2px;overflow:hidden;margin-top:6px}[data-field=size-fill]{display:block;height:100%;background:var(--dm-primary, #2563eb);width:0;transition:width .3s ease}.dash-cache-actions{display:flex;gap:8px;justify-content:flex-end}
|
|
1
|
+
.dashboard-grid{display:flex;flex-direction:column;gap:var(--dm-spacing-4, 16px)}.dash-row{display:grid;gap:var(--dm-spacing-4, 16px)}.dash-row-kpi{grid-template-columns:repeat(4,1fr)}.dash-row-traffic{grid-template-columns:2fr 1fr}.dash-row-content,.dash-row-health{grid-template-columns:1fr 1fr}@media(max-width:960px){.dash-row-kpi{grid-template-columns:repeat(2,1fr)}.dash-row-traffic,.dash-row-content,.dash-row-health{grid-template-columns:1fr}}.dash-card{background:var(--dm-surface, #fff);border:1px solid var(--dm-border, #e5e7eb);border-radius:var(--dm-radius, 8px);padding:var(--dm-spacing-4, 16px)}.dash-kpi{display:flex;align-items:center;gap:var(--dm-spacing-3, 12px)}.dash-kpi-icon{font-size:24px;opacity:.8}.dash-kpi-value{font-size:28px;font-weight:600;line-height:1}.dash-kpi-label{font-size:12px;color:var(--dm-muted, #6b7280);text-transform:uppercase;letter-spacing:.05em}.dash-kpi-delta{font-size:12px;margin-top:4px}.dash-kpi-delta.up{color:var(--dm-success, #16a34a)}.dash-kpi-delta.down{color:var(--dm-danger, #dc2626)}.dash-health-pill{display:inline-flex;align-items:center;gap:6px}.dash-health-pill[data-level=ok]{color:var(--dm-success, #16a34a)}.dash-health-pill[data-level=warn]{color:var(--dm-warning, #d97706)}.dash-health-pill[data-level=fail]{color:var(--dm-danger, #dc2626)}.dash-spike-row{display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px dashed var(--dm-border, #e5e7eb)}.dash-spike-row:last-child{border-bottom:0}.dash-spark{display:inline-block;width:80px;height:24px;vertical-align:middle;margin-left:8px}.dash-warnings{background:var(--dm-warning-bg, #fef3c7);border:1px solid var(--dm-warning, #d97706);color:var(--dm-warning-fg, #78350f);padding:8px 12px;border-radius:var(--dm-radius, 8px);margin-bottom:12px}.dash-updated{font-size:12px;color:var(--dm-muted, #6b7280);margin-right:8px}.dash-journey-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}@media(max-width:720px){.dash-journey-grid{grid-template-columns:1fr}}.dash-journey-grid h4{font-size:12px;text-transform:uppercase;color:var(--dm-muted, #6b7280);margin:0 0 6px}.dash-bounce{margin-top:12px;font-size:14px;color:var(--dm-muted, #6b7280)}.dash-empty{font-size:13px;color:var(--dm-muted, #6b7280);margin:8px 0 0}.dash-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.dash-card-header h3{margin:0}.dash-cache-toggle{display:inline-flex;align-items:center;gap:8px;font-size:13px;color:var(--dm-muted, #6b7280);cursor:pointer}.dash-cache-toggle input[type=checkbox]{transform:scale(1.2);cursor:pointer}.dash-cache-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:12px}@media(max-width:720px){.dash-cache-grid{grid-template-columns:repeat(2,1fr)}}.dash-cache-stat-label{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--dm-muted, #6b7280);margin-bottom:4px}.dash-cache-stat-value{font-size:22px;font-weight:600;line-height:1.1}.dash-cache-stat-sub{font-size:12px;color:var(--dm-muted, #6b7280);margin-top:4px;min-height:14px}[data-field=size-bar]{height:4px;background:var(--dm-border, #e5e7eb);border-radius:2px;overflow:hidden;margin-top:6px}[data-field=size-fill]{display:block;height:100%;background:var(--dm-primary, #2563eb);width:0;transition:width .3s ease}.dash-cache-actions{display:flex;gap:8px;justify-content:flex-end}.dash-chart-wrap{position:relative;height:240px}.dash-chart-wrap>canvas{position:absolute;inset:0}
|
package/package.json
CHANGED
package/public/js/site.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{makeBadgeEl as _,applyColour as A,applyPill as I}from"/public/js/menu-decor.mjs";$(()=>{const C=window.__CMS_NAV__||{},g=window.__CMS_SITE__||{};if(g.autoTheme?.enabled){let o=function(e){const f=(e||"07:00").split(":");return+f[0]*60+(+f[1]||0)},a=function(){const e=new Date,f=e.getHours()*60+e.getMinutes();return f>=o(t.dayStart)&&f<o(t.nightStart)?t.dayTheme:t.nightTheme};const t=g.autoTheme;Domma.theme.set(a()),setInterval(()=>Domma.theme.set(a()),6e4)}const b=$("#site-navbar");if(b.length&&C.brand){let a=function(c,r=0){return Array.isArray(c)?c.filter(i=>!(i&&i.type==="separator"&&r===0)).map(i=>i&&i.type==="separator"?{divider:!0}:Array.isArray(i?.items)?{...i,items:a(i.items,r+1)}:i):c},e=function(c){return Array.isArray(c)?c.filter(r=>!(r&&r.type==="separator")):[]};const t={...C.brand},o=t.size&&t.size!=="md"?` navbar-brand-${t.size}`:"";if(t.logo||t.icon||t.tagline){let c="";t.logo?c+=`<img src="${t.logo}" class="navbar-brand-logo" alt="${t.text||""}">`:t.icon&&(c+=`<span data-icon="${t.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),t.text&&(c+=`<span class="navbar-brand-text${o}">${t.text}</span>`),t.tagline&&(c+=`<small class="navbar-brand-tagline">${t.tagline}</small>`),t.html=c}else o&&t.text&&(t.html=`<span class="navbar-brand-text${o}">${t.text}</span>`);Domma.elements.navbar("#site-navbar",{brand:t,items:a(C.items||[]),variant:C.variant||"dark",position:C.position||"sticky",collapsible:!0}),Domma.icons.scan("#site-navbar");const f=b.get(0),d=f.querySelector(".navbar-toggle"),l=f.querySelector(".navbar-collapse");if(d&&l){let c=null;const r=()=>{l.classList.contains("show")&&d.click()},i=()=>{c&&(c.remove(),c=null),document.body.classList.remove("dm-drawer-open")},n=()=>{c||(c=document.createElement("div"),c.className="dm-navbar-backdrop",c.addEventListener("click",r),document.body.appendChild(c),document.body.classList.add("dm-drawer-open"))};d.addEventListener("click",()=>{requestAnimationFrame(()=>{l.classList.contains("show")?n():i()})}),l.addEventListener("click",u=>{u.target.closest("a")&&requestAnimationFrame(i)}),document.addEventListener("keydown",u=>{u.key==="Escape"&&l.classList.contains("show")&&r()})}const y=e(C.items||[]);f.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(c=>{const r=y[parseInt(c.dataset.index,10)];if(!r)return;const i=c.dataset.subindex!=null,n=i&&Array.isArray(r.items)?r.items[parseInt(c.dataset.subindex,10)]:r;if(!n)return;n.colour&&A(c,n.colour),n.pill&&!i&&I(c,n.pill);const u=_(n.badge);u&&c.appendChild(u)})}const v=$("#site-footer");if(v.length){let f=function(s){return String(s??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},d=function(s){return f(s).replace(/'/g,"'")},l=function(s){if(s&&s.type==="separator")return'<li class="footer-sep"><hr></li>';const m=d(s.url||"#"),h=f(s.text||""),k=Array.isArray(s.items)&&s.items.length?`<ul>${s.items.map(l).join("")}</ul>`:"";return`<li><a href="${m}">${h}</a>${k}</li>`},y=function(s,m){return(s||[]).forEach(h=>{h&&h.type==="separator"||(m.push(h),Array.isArray(h.items)&&h.items.length&&y(h.items,m))}),m};const t=g.social||{},o={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let a='<div class="footer-inner container">';const e=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(e.copyright&&(a+=`<p>${f(e.copyright)}</p>`),e.primary.length&&(a+=`<nav class="footer-nav footer-nav--primary"><ul>${e.primary.map(l).join("")}</ul></nav>`),e.legal.length&&(a+=`<nav class="footer-nav footer-nav--legal"><ul>${e.legal.map(l).join("")}</ul></nav>`),g.footer){const s=Object.keys(o).filter(m=>t[m]);s.length&&(a+='<div class="footer-social">',s.forEach(m=>{const{label:h,svg:k}=o[m];a+=`<a href="${t[m]}" target="_blank" rel="noopener noreferrer" aria-label="${h}" class="footer-social-link">${k}</a>`}),a+="</div>")}a+="</div>",v.html(a);const c=[...y(e.primary,[]),...y(e.legal,[])];v.get(0).querySelectorAll(".footer-nav a").forEach((s,m)=>{const h=c[m];if(!h)return;h.colour&&A(s,h.colour);const k=_(h.badge);k&&s.appendChild(k)});const i=S.get("reduced_motion"),n=i!==null?!!i:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),u=v.get(0).querySelector(".footer-inner");if(u){const s=document.createElement("input");s.type="checkbox",s.className="form-switch-input",s.id="dm-motion-switch",s.checked=n,s.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const m=document.createElement("span");m.className="form-switch-label",m.textContent="Reduce motion";const h=document.createElement("label");h.className="form-switch footer-motion-switch",h.title="Reduce motion",h.appendChild(s),h.appendChild(m),u.appendChild(h)}}$("#site-sidebar").length&&Domma.elements.sidebar("#site-sidebar",{autoGenerate:!0,selector:"h2, h3",collapsible:!1,push:!0,contentSelector:".site-content"}),Domma.icons.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 t={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 o=parseInt(this.dataset.animationDuration,10);Number.isFinite(o)&&(t.animationDuration=o)}this.dataset.animationEasing&&(t.animationEasing=this.dataset.animationEasing),Domma.elements.carousel(this,t)}),p.find(".dm-countdown").each(function(){const t={autoStart:!0};this.dataset.to&&(t.targetDate=new Date(this.dataset.to)),this.dataset.duration&&(t.duration=parseInt(this.dataset.duration,10)),this.dataset.format&&(t.format=this.dataset.format),Domma.elements.timer(this,t)}),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(){Domma.elements.progression(this,{layout:this.dataset.layout||"vertical",theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0})});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(t=>{const o=t.dataset.revealMode||"stagger",a=t.dataset.revealAnimation||"slide-up",e=parseInt(t.dataset.revealDuration,10)||400,f=parseInt(t.dataset.revealStagger,10)||60,d=parseInt(t.dataset.revealDelay,10)||0,l=t.dataset.revealDirection==="rtl",y=Array.from(t.children),c={"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((r,i)=>{r.style.opacity="0",r.style.transform=c[a]||"",r.style.transition=`opacity ${e}ms ease, transform ${e}ms ease`;const n=l?y.length-1-i:i;r.style.transitionDelay=o==="stagger"?`${d+n*f}ms`:`${d}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const r=new IntersectionObserver(i=>{i.forEach(n=>{n.isIntersecting&&(n.target.offsetWidth,n.target.style.opacity="1",n.target.style.transform="none",r.unobserve(n.target))})},{threshold:.1});y.forEach(i=>r.observe(i))}))}),p.find(".card[data-collapsible]").each(function(){const t=this.querySelector(".card-header");t&&t.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),p.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const t=this.dataset.soTarget,o=document.getElementById(t);if(!o)return;const a=E.slideover({title:o.dataset.soTitle||"",size:o.dataset.soSize||"md",position:o.dataset.soPosition||"right"});o.style.display="",a.setContent(o),a.open()})})}if(typeof $.setup=="function"){const t=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(o=>{try{const a=atob(o.dataset.config),e=JSON.parse(a);Object.assign(t,e)}catch{}}),Object.keys(t).length>0){const o={};for(const[a,e]of Object.entries(t)){const f=e?.events?.click,{confirm:d,toast:l,alert:y,prompt:c,...r}=f||{};d||l||y||c?($(a).on("click",async function(n){if(n.preventDefault(),d&&!await E.confirm(d))return;let u=null;if(!(c&&(u=await E.prompt(c,{inputPlaceholder:r.promptPlaceholder||"",inputValue:r.promptDefault||""}),u===null))){if(r.target){const s=$(r.target);r.toggleClass&&s.toggleClass(r.toggleClass),r.addClass&&s.addClass(r.addClass),r.removeClass&&s.removeClass(r.removeClass),u!==null&&(r.setText&&s.text(u),r.setVal&&s.val(u),r.setAttr&&s.attr(r.setAttr,u))}r.href&&(window.location.href=r.href),l&&E.toast(l,{type:r.toastType||"success"}),y&&E.alert(y)}}),Object.keys(r).length&&(o[a]={...e,events:{...e.events,click:r}})):o[a]=e}$.setup(o)}}p.length&&x(p.get(0))});function x(C){C.querySelectorAll(".dm-cta-trigger").forEach(g=>{g.__ctaWired||(g.__ctaWired=!0,g.addEventListener("click",async()=>{const b=g.dataset.action,v=g.dataset.entry,w=g.dataset.confirm;let p=S.get("auth_token");if(!p){E.toast("Please log in to perform this action.",{type:"warning"});return}if(w&&!await E.confirm(w))return;const t=Array.from(g.childNodes).map(a=>a.cloneNode(!0));g.disabled=!0,g.textContent="Running\u2026";const o=a=>fetch(`/api/actions/${b}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({entryId:v})});try{let a=await o(p);if(a.status===401){const f=S.get("auth_refresh_token");if(f){const d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:f})});if(d.ok){const{token:l}=await d.json();S.set("auth_token",l),p=l,a=await o(p)}}}const e=await a.json().catch(()=>({}));if(!a.ok)throw new Error(e.error||e.message||`Error ${a.status}`);E.toast(e.message||"Action completed.",{type:"success"})}catch(a){E.toast(a.message||"Action failed.",{type:"error"})}finally{g.disabled=!1,g.textContent="",t.forEach(a=>g.appendChild(a)),Domma.icons.scan(g)}}))})}(function(){const g=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!g.length)return;const b=S.get("auth_token");function v(p,t){const o=document.createElement("div");o.className="dm-collection-display dm-collection-empty";const a=document.createElement("p");if(t){a.appendChild(document.createTextNode("Please "));const e=document.createElement("a");e.href="/login",e.textContent="sign in",a.appendChild(e),a.appendChild(document.createTextNode(" to view your entries."))}else a.textContent=p;return o.appendChild(a),o}function w(p,t){const o=document.createElement("template");o.innerHTML=t||"",p.replaceWith(o.content)}g.forEach(async p=>{const t=p.dataset.collectionAttrs;if(!t)return;if(!b){p.replaceChildren(v("",!0));return}const o=a=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({attrs:t})});try{let a=b,e=await o(a);if(e.status===401){const d=S.get("auth_refresh_token");if(d){const l=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:d})});if(l.ok){const{token:y}=await l.json();S.set("auth_token",y),a=y,e=await o(a)}}}if(!e.ok){const{error:d}=await e.json().catch(()=>({}));p.replaceChildren(v(d||"Unable to load entries.",!1));return}const{html:f}=await e.json();w(p,f)}catch{p.replaceChildren(v("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),x(document.body)},100)})(),(function(){const g=document.querySelectorAll("[data-collection-table]");!g.length||typeof T>"u"||!T.create||g.forEach(b=>{let v;try{v=JSON.parse(atob(b.dataset.payload))}catch{return}const{columns:w,rows:p,search:t,sortable:o,exportable:a,pageSize:e,empty:f,ctaConfig:d}=v;if(!w?.length)return;if(d){const c=w.findIndex(r=>r.key==="_cta");c!==-1&&(w[c]={key:"_cta",title:"",render:(r,i)=>{const n=document.createElement("button");if(n.className=`btn btn-${d.style||"primary"} dm-cta-trigger`,n.dataset.action=d.action||"",n.dataset.entry=i._entryId||"",d.confirm&&(n.dataset.confirm=d.confirm),d.icon){const u=document.createElement("span");u.dataset.icon=d.icon,n.appendChild(u),n.appendChild(document.createTextNode(" "))}return n.appendChild(document.createTextNode(d.label||"Run")),n}})}const l="col-table-"+Math.random().toString(36).slice(2,7),y=document.createElement("div");y.id=l,b.replaceChildren(y),T.create("#"+l,{data:p,columns:w,search:t,sortable:o,exportable:a,pageSize:e,emptyMessage:f}),d&&x(y)})})(),(function(){const g=document.querySelectorAll("[data-form-inline]");if(!g.length||typeof F>"u")return;function b(t,o){const a={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const f=e.type==="checkbox"?"boolean":e.type==="date"?"string":e.type,d={...e.formConfig||{}};d.span==="full"&&o&&(d.span=o);const l={type:f,label:e.label,required:e.required,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...d}};e.type==="chooser"&&(e.variant&&(l.variant=e.variant),e.multiple&&(l.multiple=!0),e.density&&(l.density=e.density),e.columns&&(l.columns=e.columns),e.accent&&(l.accent=e.accent),e.accentStyle&&(l.accentStyle=e.accentStyle),e.glow&&(l.glow=!0),e.glowColour&&(l.glowColour=e.glowColour),e.shadow&&(l.shadow=e.shadow),e.shadowColour&&(l.shadowColour=e.shadowColour)),a[e.name]=l}),a}function v(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 w(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 p(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(--danger,#f87171);margin-top:.75rem;":"color:var(--success,#4ade80);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||{},f=e.columns||1,d=e.layout||"stacked",l=a.some(i=>i.type==="page-break"),y=async i=>{try{const n=t.querySelector('[name="website"]'),u=t.querySelector('[name="_t"]'),s=Object.assign({},i);n!==null&&(s._hp=n.value),u!==null&&(s._t=u.value);const m=await H.post(`/api/forms/submit/${o.slug}`,s);if(m&&m.error){p(t,m.error,!0);const h=new Error(m.error);throw h.formSubmitFailed=!0,h}if(m?.redirect){window.location.href=m.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);p(t,m?.message||e.successMessage||"Thank you for your submission.",!1)}catch(n){throw n.formSubmitFailed||p(t,n.message||"Submission failed. Please try again.",!0),n}};function c(i){const n=i.querySelector("form");if(!n)return;const u=document.createElement("div");u.className="fb-form-honeypot",u.setAttribute("aria-hidden","true");const s=document.createElement("input");s.name="website",s.type="text",s.tabIndex=-1,s.autocomplete="url",s.placeholder="https://",u.appendChild(s);const m=document.createElement("input");m.name="_t",m.type="hidden",m.value=Date.now(),u.appendChild(m),n.appendChild(u)}function r(){if(!window.FormLogicEngine||!a.some(n=>n.logic))return;const i=new window.FormLogicEngine.FormLogicRuntime(o,t);if(i.init(),t._formLogicRuntime=i,t.parentNode&&typeof MutationObserver<"u"){const n=new MutationObserver(function(u){for(const s of u)for(const m of s.removedNodes)if(m===t||m.nodeType===1&&m.contains&&m.contains(t)){i.destroy(),n.disconnect();return}});n.observe(t.parentNode,{childList:!0,subtree:!1})}}if(l&&F.wizard){const i=[];let n=[],u=o.title||"Step 1",s="";a.forEach(h=>{h.type==="page-break"?(i.push({title:u,description:s,fields:b(n,f)}),n=[],u=h.label||`Step ${i.length+1}`,s=h.description||""):h.type!=="spacer"&&n.push(h)}),i.push({title:u,description:s,fields:b(n,f)});const m=F.wizard(t,{schema:{steps:i},onSubmit:y});Promise.resolve(m).then(function(){w(t,a),e.honeypot!==!1&&c(t),r()})}else if(F.render){const i=F.render(t,b(a,f),v(a),{submitText:e.submitText||"Submit",layout:d,columns:f,onSubmit:y});Promise.resolve(i).then(function(){if(d==="grid"&&e.submitSpan==="full"){const n=t.querySelector(".form-buttons");n&&n.classList.add("col-span-full")}w(t,a),e.honeypot!==!1&&c(t),r()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
|
1
|
+
import{makeBadgeEl as _,applyColour as A,applyPill as I}from"/public/js/menu-decor.mjs";$(()=>{const C=window.__CMS_NAV__||{},g=window.__CMS_SITE__||{};if(g.autoTheme?.enabled){let o=function(e){const f=(e||"07:00").split(":");return+f[0]*60+(+f[1]||0)},a=function(){const e=new Date,f=e.getHours()*60+e.getMinutes();return f>=o(t.dayStart)&&f<o(t.nightStart)?t.dayTheme:t.nightTheme};const t=g.autoTheme;Domma.theme.set(a()),setInterval(()=>Domma.theme.set(a()),6e4)}const b=$("#site-navbar");if(b.length&&C.brand){let a=function(i,n=0){return Array.isArray(i)?i.filter(c=>!(c&&c.type==="separator"&&n===0)).map(c=>c&&c.type==="separator"?{divider:!0}:Array.isArray(c?.items)?{...c,items:a(c.items,n+1)}:c):i},e=function(i){return Array.isArray(i)?i.filter(n=>!(n&&n.type==="separator")):[]};const t={...C.brand},o=t.size&&t.size!=="md"?` navbar-brand-${t.size}`:"";if(t.logo||t.icon||t.tagline){let i="";t.logo?i+=`<img src="${t.logo}" class="navbar-brand-logo" alt="${t.text||""}">`:t.icon&&(i+=`<span data-icon="${t.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),t.text&&(i+=`<span class="navbar-brand-text${o}">${t.text}</span>`),t.tagline&&(i+=`<small class="navbar-brand-tagline">${t.tagline}</small>`),t.html=i}else o&&t.text&&(t.html=`<span class="navbar-brand-text${o}">${t.text}</span>`);Domma.elements.navbar("#site-navbar",{brand:t,items:a(C.items||[]),variant:C.variant||"dark",position:C.position||"sticky",collapsible:!0}),Domma.icons.scan("#site-navbar");const f=b.get(0);if(C.appearOnHover){const i=window.matchMedia("(min-width: 993px)");f.querySelectorAll(".navbar-dropdown").forEach(n=>{n.addEventListener("mouseenter",()=>{i.matches&&n.classList.add("open")}),n.addEventListener("mouseleave",()=>{i.matches&&n.classList.remove("open")})})}const d=f.querySelector(".navbar-toggle"),l=f.querySelector(".navbar-collapse");if(d&&l){let i=null;const n=()=>{l.classList.contains("show")&&d.click()},c=()=>{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",n),document.body.appendChild(i),document.body.classList.add("dm-drawer-open"))};d.addEventListener("click",()=>{requestAnimationFrame(()=>{l.classList.contains("show")?s():c()})}),l.addEventListener("click",u=>{u.target.closest("a")&&requestAnimationFrame(c)}),document.addEventListener("keydown",u=>{u.key==="Escape"&&l.classList.contains("show")&&n()})}const y=e(C.items||[]);f.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(i=>{const n=y[parseInt(i.dataset.index,10)];if(!n)return;const c=i.dataset.subindex!=null,s=c&&Array.isArray(n.items)?n.items[parseInt(i.dataset.subindex,10)]:n;if(!s)return;s.colour&&A(i,s.colour),s.pill&&!c&&I(i,s.pill);const u=_(s.badge);u&&i.appendChild(u)})}const v=$("#site-footer");if(v.length){let f=function(r){return String(r??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},d=function(r){return f(r).replace(/'/g,"'")},l=function(r){if(r&&r.type==="separator")return'<li class="footer-sep"><hr></li>';const m=d(r.url||"#"),h=f(r.text||""),k=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(l).join("")}</ul>`:"";return`<li><a href="${m}">${h}</a>${k}</li>`},y=function(r,m){return(r||[]).forEach(h=>{h&&h.type==="separator"||(m.push(h),Array.isArray(h.items)&&h.items.length&&y(h.items,m))}),m};const t=g.social||{},o={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let a='<div class="footer-inner container">';const e=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(e.copyright&&(a+=`<p>${f(e.copyright)}</p>`),e.primary.length&&(a+=`<nav class="footer-nav footer-nav--primary"><ul>${e.primary.map(l).join("")}</ul></nav>`),e.legal.length&&(a+=`<nav class="footer-nav footer-nav--legal"><ul>${e.legal.map(l).join("")}</ul></nav>`),g.footer){const r=Object.keys(o).filter(m=>t[m]);r.length&&(a+='<div class="footer-social">',r.forEach(m=>{const{label:h,svg:k}=o[m];a+=`<a href="${t[m]}" target="_blank" rel="noopener noreferrer" aria-label="${h}" class="footer-social-link">${k}</a>`}),a+="</div>")}a+="</div>",v.html(a);const i=[...y(e.primary,[]),...y(e.legal,[])];v.get(0).querySelectorAll(".footer-nav a").forEach((r,m)=>{const h=i[m];if(!h)return;h.colour&&A(r,h.colour);const k=_(h.badge);k&&r.appendChild(k)});const c=S.get("reduced_motion"),s=c!==null?!!c:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),u=v.get(0).querySelector(".footer-inner");if(u){const r=document.createElement("input");r.type="checkbox",r.className="form-switch-input",r.id="dm-motion-switch",r.checked=s,r.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const m=document.createElement("span");m.className="form-switch-label",m.textContent="Reduce motion";const h=document.createElement("label");h.className="form-switch footer-motion-switch",h.title="Reduce motion",h.appendChild(r),h.appendChild(m),u.appendChild(h)}}$("#site-sidebar").length&&Domma.elements.sidebar("#site-sidebar",{autoGenerate:!0,selector:"h2, h3",collapsible:!1,push:!0,contentSelector:".site-content"}),Domma.icons.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 t={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 o=parseInt(this.dataset.animationDuration,10);Number.isFinite(o)&&(t.animationDuration=o)}this.dataset.animationEasing&&(t.animationEasing=this.dataset.animationEasing),Domma.elements.carousel(this,t)}),p.find(".dm-countdown").each(function(){const t={autoStart:!0};this.dataset.to&&(t.targetDate=new Date(this.dataset.to)),this.dataset.duration&&(t.duration=parseInt(this.dataset.duration,10)),this.dataset.format&&(t.format=this.dataset.format),Domma.elements.timer(this,t)}),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(){Domma.elements.progression(this,{layout:this.dataset.layout||"vertical",theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0})});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(t=>{const o=t.dataset.revealMode||"stagger",a=t.dataset.revealAnimation||"slide-up",e=parseInt(t.dataset.revealDuration,10)||400,f=parseInt(t.dataset.revealStagger,10)||60,d=parseInt(t.dataset.revealDelay,10)||0,l=t.dataset.revealDirection==="rtl",y=Array.from(t.children),i={"slide-up":"translateY(30px)","slide-down":"translateY(-30px)","slide-left":"translateX(30px)","slide-right":"translateX(-30px)",zoom:"scale(0.85)",flip:"perspective(600px) rotateX(15deg)"};y.forEach((n,c)=>{n.style.opacity="0",n.style.transform=i[a]||"",n.style.transition=`opacity ${e}ms ease, transform ${e}ms ease`;const s=l?y.length-1-c:c;n.style.transitionDelay=o==="stagger"?`${d+s*f}ms`:`${d}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const n=new IntersectionObserver(c=>{c.forEach(s=>{s.isIntersecting&&(s.target.offsetWidth,s.target.style.opacity="1",s.target.style.transform="none",n.unobserve(s.target))})},{threshold:.1});y.forEach(c=>n.observe(c))}))}),p.find(".card[data-collapsible]").each(function(){const t=this.querySelector(".card-header");t&&t.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),p.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const t=this.dataset.soTarget,o=document.getElementById(t);if(!o)return;const a=E.slideover({title:o.dataset.soTitle||"",size:o.dataset.soSize||"md",position:o.dataset.soPosition||"right"});o.style.display="",a.setContent(o),a.open()})})}if(typeof $.setup=="function"){const t=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(o=>{try{const a=atob(o.dataset.config),e=JSON.parse(a);Object.assign(t,e)}catch{}}),Object.keys(t).length>0){const o={};for(const[a,e]of Object.entries(t)){const f=e?.events?.click,{confirm:d,toast:l,alert:y,prompt:i,...n}=f||{};d||l||y||i?($(a).on("click",async function(s){if(s.preventDefault(),d&&!await E.confirm(d))return;let u=null;if(!(i&&(u=await E.prompt(i,{inputPlaceholder:n.promptPlaceholder||"",inputValue:n.promptDefault||""}),u===null))){if(n.target){const r=$(n.target);n.toggleClass&&r.toggleClass(n.toggleClass),n.addClass&&r.addClass(n.addClass),n.removeClass&&r.removeClass(n.removeClass),u!==null&&(n.setText&&r.text(u),n.setVal&&r.val(u),n.setAttr&&r.attr(n.setAttr,u))}n.href&&(window.location.href=n.href),l&&E.toast(l,{type:n.toastType||"success"}),y&&E.alert(y)}}),Object.keys(n).length&&(o[a]={...e,events:{...e.events,click:n}})):o[a]=e}$.setup(o)}}p.length&&x(p.get(0))});function x(C){C.querySelectorAll(".dm-cta-trigger").forEach(g=>{g.__ctaWired||(g.__ctaWired=!0,g.addEventListener("click",async()=>{const b=g.dataset.action,v=g.dataset.entry,w=g.dataset.confirm;let p=S.get("auth_token");if(!p){E.toast("Please log in to perform this action.",{type:"warning"});return}if(w&&!await E.confirm(w))return;const t=Array.from(g.childNodes).map(a=>a.cloneNode(!0));g.disabled=!0,g.textContent="Running\u2026";const o=a=>fetch(`/api/actions/${b}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({entryId:v})});try{let a=await o(p);if(a.status===401){const f=S.get("auth_refresh_token");if(f){const d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:f})});if(d.ok){const{token:l}=await d.json();S.set("auth_token",l),p=l,a=await o(p)}}}const e=await a.json().catch(()=>({}));if(!a.ok)throw new Error(e.error||e.message||`Error ${a.status}`);E.toast(e.message||"Action completed.",{type:"success"})}catch(a){E.toast(a.message||"Action failed.",{type:"error"})}finally{g.disabled=!1,g.textContent="",t.forEach(a=>g.appendChild(a)),Domma.icons.scan(g)}}))})}(function(){const g=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!g.length)return;const b=S.get("auth_token");function v(p,t){const o=document.createElement("div");o.className="dm-collection-display dm-collection-empty";const a=document.createElement("p");if(t){a.appendChild(document.createTextNode("Please "));const e=document.createElement("a");e.href="/login",e.textContent="sign in",a.appendChild(e),a.appendChild(document.createTextNode(" to view your entries."))}else a.textContent=p;return o.appendChild(a),o}function w(p,t){const o=document.createElement("template");o.innerHTML=t||"",p.replaceWith(o.content)}g.forEach(async p=>{const t=p.dataset.collectionAttrs;if(!t)return;if(!b){p.replaceChildren(v("",!0));return}const o=a=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({attrs:t})});try{let a=b,e=await o(a);if(e.status===401){const d=S.get("auth_refresh_token");if(d){const l=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:d})});if(l.ok){const{token:y}=await l.json();S.set("auth_token",y),a=y,e=await o(a)}}}if(!e.ok){const{error:d}=await e.json().catch(()=>({}));p.replaceChildren(v(d||"Unable to load entries.",!1));return}const{html:f}=await e.json();w(p,f)}catch{p.replaceChildren(v("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),x(document.body)},100)})(),(function(){const g=document.querySelectorAll("[data-collection-table]");!g.length||typeof T>"u"||!T.create||g.forEach(b=>{let v;try{v=JSON.parse(atob(b.dataset.payload))}catch{return}const{columns:w,rows:p,search:t,sortable:o,exportable:a,pageSize:e,empty:f,ctaConfig:d}=v;if(!w?.length)return;if(d){const i=w.findIndex(n=>n.key==="_cta");i!==-1&&(w[i]={key:"_cta",title:"",render:(n,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 u=document.createElement("span");u.dataset.icon=d.icon,s.appendChild(u),s.appendChild(document.createTextNode(" "))}return s.appendChild(document.createTextNode(d.label||"Run")),s}})}const l="col-table-"+Math.random().toString(36).slice(2,7),y=document.createElement("div");y.id=l,b.replaceChildren(y),T.create("#"+l,{data:p,columns:w,search:t,sortable:o,exportable:a,pageSize:e,emptyMessage:f}),d&&x(y)})})(),(function(){const g=document.querySelectorAll("[data-form-inline]");if(!g.length||typeof F>"u")return;function b(t,o){const a={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const f=e.type==="checkbox"?"boolean":e.type==="date"?"string":e.type,d={...e.formConfig||{}};d.span==="full"&&o&&(d.span=o);const l={type:f,label:e.label,required:e.required,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...d}};e.type==="chooser"&&(e.variant&&(l.variant=e.variant),e.multiple&&(l.multiple=!0),e.density&&(l.density=e.density),e.columns&&(l.columns=e.columns),e.accent&&(l.accent=e.accent),e.accentStyle&&(l.accentStyle=e.accentStyle),e.glow&&(l.glow=!0),e.glowColour&&(l.glowColour=e.glowColour),e.shadow&&(l.shadow=e.shadow),e.shadowColour&&(l.shadowColour=e.shadowColour)),a[e.name]=l}),a}function v(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 w(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 p(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(--danger,#f87171);margin-top:.75rem;":"color:var(--success,#4ade80);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||{},f=e.columns||1,d=e.layout||"stacked",l=a.some(c=>c.type==="page-break"),y=async c=>{try{const s=t.querySelector('[name="website"]'),u=t.querySelector('[name="_t"]'),r=Object.assign({},c);s!==null&&(r._hp=s.value),u!==null&&(r._t=u.value);const m=await H.post(`/api/forms/submit/${o.slug}`,r);if(m&&m.error){p(t,m.error,!0);const h=new Error(m.error);throw h.formSubmitFailed=!0,h}if(m?.redirect){window.location.href=m.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);p(t,m?.message||e.successMessage||"Thank you for your submission.",!1)}catch(s){throw s.formSubmitFailed||p(t,s.message||"Submission failed. Please try again.",!0),s}};function i(c){const s=c.querySelector("form");if(!s)return;const u=document.createElement("div");u.className="fb-form-honeypot",u.setAttribute("aria-hidden","true");const r=document.createElement("input");r.name="website",r.type="text",r.tabIndex=-1,r.autocomplete="url",r.placeholder="https://",u.appendChild(r);const m=document.createElement("input");m.name="_t",m.type="hidden",m.value=Date.now(),u.appendChild(m),s.appendChild(u)}function n(){if(!window.FormLogicEngine||!a.some(s=>s.logic))return;const c=new window.FormLogicEngine.FormLogicRuntime(o,t);if(c.init(),t._formLogicRuntime=c,t.parentNode&&typeof MutationObserver<"u"){const s=new MutationObserver(function(u){for(const r of u)for(const m of r.removedNodes)if(m===t||m.nodeType===1&&m.contains&&m.contains(t)){c.destroy(),s.disconnect();return}});s.observe(t.parentNode,{childList:!0,subtree:!1})}}if(l&&F.wizard){const c=[];let s=[],u=o.title||"Step 1",r="";a.forEach(h=>{h.type==="page-break"?(c.push({title:u,description:r,fields:b(s,f)}),s=[],u=h.label||`Step ${c.length+1}`,r=h.description||""):h.type!=="spacer"&&s.push(h)}),c.push({title:u,description:r,fields:b(s,f)});const m=F.wizard(t,{schema:{steps:c},onSubmit:y});Promise.resolve(m).then(function(){w(t,a),e.honeypot!==!1&&i(t),n()})}else if(F.render){const c=F.render(t,b(a,f),v(a),{submitText:e.submitText||"Submit",layout:d,columns:f,onSubmit:y});Promise.resolve(c).then(function(){if(d==="grid"&&e.submitSpan==="full"){const s=t.querySelector(".form-buttons");s&&s.classList.add("col-span-full")}w(t,a),e.honeypot!==!1&&i(t),n()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
|
@@ -98,6 +98,88 @@ function buildTopPages(daily, days = 7) {
|
|
|
98
98
|
.slice(0, 5);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// ── Journeys / spikes — ported verbatim from plugins/analytics so the core
|
|
102
|
+
// dashboard can compute them off disk without the plugin's encapsulated
|
|
103
|
+
// `fastify.analytics` decorator (which it cannot see). Keep in sync with
|
|
104
|
+
// plugins/analytics/plugin.js.
|
|
105
|
+
const REALTIME_WINDOW_MS = 5 * 60 * 1000;
|
|
106
|
+
|
|
107
|
+
function eventsInRange(journeys, start, end) {
|
|
108
|
+
const startMs = start.getTime(), endMs = end.getTime();
|
|
109
|
+
const out = [];
|
|
110
|
+
for (const [day, events] of Object.entries(journeys)) {
|
|
111
|
+
const dayMs = new Date(day + 'T00:00:00Z').getTime();
|
|
112
|
+
if (dayMs < startMs || dayMs > endMs) continue;
|
|
113
|
+
if (Array.isArray(events)) for (const ev of events) out.push(ev);
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function groupBySession(events) {
|
|
119
|
+
const sessions = new Map();
|
|
120
|
+
for (const ev of events) {
|
|
121
|
+
if (!sessions.has(ev.sid)) sessions.set(ev.sid, []);
|
|
122
|
+
sessions.get(ev.sid).push(ev);
|
|
123
|
+
}
|
|
124
|
+
for (const arr of sessions.values()) arr.sort((a, b) => a.t - b.t);
|
|
125
|
+
return sessions;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function aggregateJourneys(events) {
|
|
129
|
+
const sessions = groupBySession(events);
|
|
130
|
+
const entry = new Map(), exit = new Map(), paths = new Map();
|
|
131
|
+
let bouncedSessions = 0;
|
|
132
|
+
for (const arr of sessions.values()) {
|
|
133
|
+
if (arr.length === 0) continue;
|
|
134
|
+
entry.set(arr[0].url, (entry.get(arr[0].url) || 0) + 1);
|
|
135
|
+
exit.set(arr[arr.length - 1].url, (exit.get(arr[arr.length - 1].url) || 0) + 1);
|
|
136
|
+
if (arr.length === 1) bouncedSessions += 1;
|
|
137
|
+
for (let i = 0; i < arr.length - 1; i += 1) {
|
|
138
|
+
const from = arr[i].url, to = arr[i + 1].url;
|
|
139
|
+
const key = from + '\x00' + to;
|
|
140
|
+
const existing = paths.get(key);
|
|
141
|
+
if (existing) existing.count += 1;
|
|
142
|
+
else paths.set(key, { from, to, count: 1 });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const toSorted = (m) => Array.from(m.entries())
|
|
146
|
+
.map(([url, count]) => ({ url, count }))
|
|
147
|
+
.sort((a, b) => b.count - a.count).slice(0, 10);
|
|
148
|
+
const totalSessions = sessions.size;
|
|
149
|
+
return {
|
|
150
|
+
entry: toSorted(entry),
|
|
151
|
+
exit: toSorted(exit),
|
|
152
|
+
paths: Array.from(paths.values()).sort((a, b) => b.count - a.count).slice(0, 10),
|
|
153
|
+
bounceRate: totalSessions === 0 ? 0 : +(bouncedSessions / totalSessions).toFixed(3),
|
|
154
|
+
totalSessions
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function detectSpikes(daily, now = new Date()) {
|
|
159
|
+
const todayStr = todayKey(now);
|
|
160
|
+
const currentHour = now.getUTCHours();
|
|
161
|
+
const todayUrls = daily[todayStr] || {};
|
|
162
|
+
const elapsedHours = Math.max(1, currentHour + 1);
|
|
163
|
+
const flagged = [];
|
|
164
|
+
for (const [url, totalToday] of Object.entries(todayUrls)) {
|
|
165
|
+
const currentHits = totalToday / elapsedHours;
|
|
166
|
+
const samples = [];
|
|
167
|
+
for (let i = 1; i <= 7; i += 1) {
|
|
168
|
+
const prev = new Date(now);
|
|
169
|
+
prev.setUTCDate(prev.getUTCDate() - i);
|
|
170
|
+
const key = todayKey(prev);
|
|
171
|
+
samples.push((daily[key] && daily[key][url]) ? daily[key][url] / 24 : 0);
|
|
172
|
+
}
|
|
173
|
+
const mean = samples.reduce((a, b) => a + b, 0) / samples.length;
|
|
174
|
+
const variance = samples.reduce((acc, x) => acc + (x - mean) * (x - mean), 0) / samples.length;
|
|
175
|
+
const stddev = Math.sqrt(variance);
|
|
176
|
+
if (currentHits >= 5 && currentHits > mean + 2 * stddev && mean > 0) {
|
|
177
|
+
flagged.push({ url, hits: Math.round(currentHits), baseline: Math.round(mean), ratio: +(currentHits / Math.max(mean, 0.5)).toFixed(2) });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return flagged.sort((a, b) => b.ratio - a.ratio).slice(0, 5);
|
|
181
|
+
}
|
|
182
|
+
|
|
101
183
|
/**
|
|
102
184
|
* Recent activity feed — combines page version events with recent collection
|
|
103
185
|
* entries (form submissions). Returns at most 10 items, newest first.
|
|
@@ -193,7 +275,18 @@ export async function dashboardRoutes(fastify, opts = {}) {
|
|
|
193
275
|
};
|
|
194
276
|
|
|
195
277
|
const analytics = fastify.analytics;
|
|
196
|
-
|
|
278
|
+
// The analytics plugin decorates `analytics` inside its own prefixed,
|
|
279
|
+
// encapsulated scope, so `fastify.analytics` is undefined here in the
|
|
280
|
+
// core dashboard route — traffic/topPages always read 0 even though hits
|
|
281
|
+
// are recorded. Read the plugin's daily counters straight off disk (same
|
|
282
|
+
// pattern as buildActivity reading versions/collections below).
|
|
283
|
+
const daily = (await safe('analytics.daily', async () => {
|
|
284
|
+
for (const rel of ['plugins/analytics/data/daily.json', 'plugins/analytics/daily.json']) {
|
|
285
|
+
try { return JSON.parse(await fs.readFile(path.join(ROOT, rel), 'utf8')); }
|
|
286
|
+
catch { /* try next location */ }
|
|
287
|
+
}
|
|
288
|
+
return {};
|
|
289
|
+
})) || {};
|
|
197
290
|
|
|
198
291
|
const today = todayKey();
|
|
199
292
|
const yesterday = isoDaysAgo(1);
|
|
@@ -210,12 +303,22 @@ export async function dashboardRoutes(fastify, opts = {}) {
|
|
|
210
303
|
previousWeek: sumRange(daily, isoDaysAgo(13), isoDaysAgo(7))
|
|
211
304
|
};
|
|
212
305
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
306
|
+
// Session events read straight off disk too (same reason as daily above).
|
|
307
|
+
const journeysData = (await safe('analytics.journeysData', async () => {
|
|
308
|
+
for (const rel of ['plugins/analytics/data/journeys.json', 'plugins/analytics/journeys.json']) {
|
|
309
|
+
try { return JSON.parse(await fs.readFile(path.join(ROOT, rel), 'utf8')); }
|
|
310
|
+
catch { /* try next location */ }
|
|
311
|
+
}
|
|
312
|
+
return {};
|
|
313
|
+
})) || {};
|
|
314
|
+
const realtime = (await safe('analytics.realtime', async () => {
|
|
315
|
+
const evs = journeysData[today] || [];
|
|
316
|
+
const cutoff = Date.now() - REALTIME_WINDOW_MS;
|
|
317
|
+
const sids = new Set();
|
|
318
|
+
for (const ev of evs) if (ev.t >= cutoff) sids.add(ev.sid);
|
|
319
|
+
return { activeSessions: sids.size, windowMinutes: REALTIME_WINDOW_MS / 60000 };
|
|
320
|
+
})) || { activeSessions: 0 };
|
|
321
|
+
const spikes = (await safe('analytics.spikes', () => detectSpikes(daily))) || [];
|
|
219
322
|
const health = await safe('health', () => getHealth()) || { status: 'ok', checks: [] };
|
|
220
323
|
|
|
221
324
|
if (lite) {
|
|
@@ -229,9 +332,11 @@ export async function dashboardRoutes(fastify, opts = {}) {
|
|
|
229
332
|
}
|
|
230
333
|
|
|
231
334
|
const topPages = buildTopPages(daily);
|
|
232
|
-
const journeys = analytics
|
|
233
|
-
|
|
234
|
-
|
|
335
|
+
const journeys = (await safe('analytics.journeys', () => {
|
|
336
|
+
const end = new Date(); end.setUTCHours(0, 0, 0, 0);
|
|
337
|
+
const start = new Date(end); start.setUTCDate(start.getUTCDate() - 6);
|
|
338
|
+
return aggregateJourneys(eventsInRange(journeysData, start, end));
|
|
339
|
+
})) || null;
|
|
235
340
|
const activity = (await safe('activity', () => buildActivity())) || [];
|
|
236
341
|
|
|
237
342
|
return { traffic, topPages, journeys, spikes, realtime, health, activity, warnings };
|
package/server/services/menus.js
CHANGED
|
@@ -13,6 +13,19 @@ import path from 'path';
|
|
|
13
13
|
import {fileURLToPath} from 'url';
|
|
14
14
|
import {checkVisibility} from '../middleware/auth.js';
|
|
15
15
|
import {listEntries} from './collections.js';
|
|
16
|
+
import * as cache from './cache/index.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Bust the response-cache tags that menu changes affect. Public pages are
|
|
20
|
+
* cached with the `nav` and `site` tags (see routes/public.js), so any menu
|
|
21
|
+
* mutation — content, behaviour/style overrides, or slot mapping — must clear
|
|
22
|
+
* them or the navbar/footer/[menu] output stays stale until TTL or restart.
|
|
23
|
+
* No-op when the cache is disabled (e.g. dev / boot-time seeding).
|
|
24
|
+
*/
|
|
25
|
+
async function invalidateNavCache() {
|
|
26
|
+
try { await cache.invalidateTags(['nav', 'site']); }
|
|
27
|
+
catch { /* cache not ready (boot) — safe to ignore */ }
|
|
28
|
+
}
|
|
16
29
|
|
|
17
30
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
31
|
const __dirname = path.dirname(__filename);
|
|
@@ -124,6 +137,8 @@ async function readMenuFile(slug) {
|
|
|
124
137
|
async function writeMenuFile(menu) {
|
|
125
138
|
await ensureMenusDir();
|
|
126
139
|
await fs.writeFile(menuPath(menu.slug), JSON.stringify(menu, null, 2) + '\n', 'utf8');
|
|
140
|
+
// Menu content / behaviour / style changed — drop the cached public pages.
|
|
141
|
+
await invalidateNavCache();
|
|
127
142
|
}
|
|
128
143
|
|
|
129
144
|
/**
|
|
@@ -263,6 +278,7 @@ export async function deleteMenu(slug) {
|
|
|
263
278
|
if (!validateSlug(slug)) return false;
|
|
264
279
|
try {
|
|
265
280
|
await fs.unlink(menuPath(slug));
|
|
281
|
+
await invalidateNavCache();
|
|
266
282
|
return true;
|
|
267
283
|
} catch (err) {
|
|
268
284
|
if (err.code === 'ENOENT') return false;
|
|
@@ -477,6 +493,8 @@ export async function setLocations(map) {
|
|
|
477
493
|
}
|
|
478
494
|
}
|
|
479
495
|
await fs.writeFile(LOCATIONS_FILE, JSON.stringify(map, null, 2) + '\n', 'utf8');
|
|
496
|
+
// Slot → menu mapping changed (e.g. a different menu now drives the navbar).
|
|
497
|
+
await invalidateNavCache();
|
|
480
498
|
}
|
|
481
499
|
|
|
482
500
|
// ---------------------------------------------------------------------------
|
|
@@ -97,6 +97,7 @@ export async function renderPage(page, opts = {}) {
|
|
|
97
97
|
items: navItemsDecorated,
|
|
98
98
|
variant: navMenu.variant || 'default',
|
|
99
99
|
position: navMenu.position || 'sticky',
|
|
100
|
+
...(navMenu.appearOnHover && {appearOnHover: true}),
|
|
100
101
|
...(navMenu.style && {style: navMenu.style})
|
|
101
102
|
}
|
|
102
103
|
: (await readLegacyNavBackup()) || {brand: site.brand || {}, items: []};
|
|
@@ -481,6 +482,7 @@ export async function renderBlogPage(templatePath, data = {}, seoMeta = {}) {
|
|
|
481
482
|
items: navItemsDecorated,
|
|
482
483
|
variant: navMenu.variant || 'default',
|
|
483
484
|
position: navMenu.position || 'sticky',
|
|
485
|
+
...(navMenu.appearOnHover && {appearOnHover: true}),
|
|
484
486
|
...(navMenu.style && {style: navMenu.style})
|
|
485
487
|
}
|
|
486
488
|
: (await readLegacyNavBackup()) || {brand: site.brand || {}, items: []};
|