domma-cms 0.99.0 → 0.100.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/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/index.html +2 -2
- package/admin/js/lib/collection-editor-arrange.js +2 -2
- package/admin/js/lib/page-editor-reference.js +6 -4
- package/admin/js/lib/shortcode-registry.js +1 -1
- package/admin/js/templates/docs/api-forms.html +21 -0
- package/admin/js/templates/docs/usage-collections.html +2 -1
- package/admin/js/templates/docs/usage-shortcodes.html +16 -0
- package/admin/js/templates/form-editor.html +126 -12
- package/admin/js/views/collection-editor.js +19 -4
- package/admin/js/views/collection-entries.js +5 -5
- package/admin/js/views/form-editor.js +7 -7
- package/package.json +2 -2
- package/public/css/forms.css +1 -1
- package/public/css/site.css +1 -1
- package/public/js/form-logic-engine.js +1 -1
- package/public/js/form-submit.mjs +1 -0
- package/public/js/forms.js +2 -2
- package/public/js/site.js +1 -1
- package/server/routes/api/forms.js +11 -0
- package/server/services/forms.js +5 -0
- package/server/services/markdown.js +73 -2
- package/server/templates/page.html +5 -5
package/public/js/forms.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{buildWizardSteps as
|
|
2
|
-
`).replace(/<[^>]+>/g,""))}function _(n){return String(n).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function q(n,o){let t=!1;n.addEventListener("dm-form-end",function(e){const r=e.detail&&e.detail.validation;if(r&&!r.valid){markInvalidFields(n,r.missing),showValidationPopup(r);return}t||(t=!0,n.classList.add("fb-form-loading"),Promise.resolve(o(e.detail&&e.detail.values||{})).catch(function(){}).finally(function(){t=!1,n.classList.remove("fb-form-loading")}))})}function C(n,o,t){const e=n.querySelector(".fb-form-success, .fb-form-error");e&&e.remove();const r=document.createElement("div");r.className=t==="success"?"fb-form-success":"fb-form-error",r.textContent=o,n.appendChild(r)}function j(n,o){if(n._formLogicRuntime=o,n._formLogicObserver&&(n._formLogicObserver.disconnect(),n._formLogicObserver=null),!n.parentNode||typeof MutationObserver>"u"||n._formLogicObserved)return;n._formLogicObserved=!0;const t=new MutationObserver(function(e){for(const r of e)for(const a of r.removedNodes)if(a===n||a.nodeType===1&&a.contains&&a.contains(n)){n._formLogicRuntime&&n._formLogicRuntime.destroy(),t.disconnect();return}});t.observe(n.parentNode,{childList:!0,subtree:!1}),n._formLogicObserver=t}function J(n){const o=n&&n.logic;return!!(o&&(o.visibility||o.requirement))}function A(n,o){const t={};return n.forEach(function(e){if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const r=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),a=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":r?"chooser":e.type,u={...e.formConfig||{}};u.span==="full"&&o&&(u.span=o);const i={type:a,label:e.label||e.name,required:J(e)?!1:e.required||!1,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{helperText:e.helper},...e.tooltip&&{tooltip:e.tooltip},...u}};e.minLength!==void 0&&(i.minLength=e.minLength),e.maxLength!==void 0&&(i.maxLength=e.maxLength),e.min!==void 0&&(i.min=e.min),e.max!==void 0&&(i.max=e.max),r&&(e.variant&&(i.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(i.multiple=!0),e.density&&(i.density=e.density),e.columns&&(i.columns=e.columns),e.accent&&(i.accent=e.accent),e.accentStyle&&(i.accentStyle=e.accentStyle),e.glow&&(i.glow=!0),e.glowColour&&(i.glowColour=e.glowColour),e.shadow&&(i.shadow=e.shadow),e.shadowColour&&(i.shadowColour=e.shadowColour)),t[e.name]=i}),t}function U(n){const o={};return n.forEach(function(t){if(!(!t.name||t.type==="page-break"||t.type==="spacer")&&(t.type==="select"||t.type==="multiselect")&&t.required){const e=(t.options||[])[0];e&&(o[t.name]=typeof e=="object"?e.value:e)}}),o}function R(n,o){(o||[]).forEach(function(t){if(t.type!=="date"||!t.name)return;const e=n.querySelector('[name="'+t.name+'"]');e&&e.type!=="date"&&(e.type="date")})}const O="padding:.45rem .7rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);background:transparent;color:inherit;cursor:pointer;font:inherit;font-size:.85em;flex-shrink:0;line-height:1.2;";function B(n){const o=document.createElement("div");o.style.cssText="position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;padding:20px;";const t=document.createElement("div");t.style.cssText="background:var(--dm-surface-overlay,var(--dm-surface));color:var(--dm-text);border:1px solid var(--dm-border);border-radius:10px;width:100%;max-width:560px;max-height:75vh;display:flex;flex-direction:column;gap:.6rem;padding:1rem;box-shadow:var(--dm-shadow-lg,0 12px 40px rgba(0,0,0,.35));";const e=document.createElement("div");e.style.cssText="display:flex;align-items:center;justify-content:space-between;gap:.5rem;";const r=document.createElement("strong");r.textContent=n;const a=document.createElement("button");a.type="button",a.textContent="\xD7",a.setAttribute("aria-label","Close"),a.style.cssText=O+"font-size:1rem;padding:.15rem .55rem;",e.appendChild(r),e.appendChild(a),t.appendChild(e);const u=document.createElement("div");u.style.cssText="display:flex;flex-direction:column;gap:.6rem;overflow:hidden;min-height:0;",t.appendChild(u),o.appendChild(t);const i=d=>{d.key==="Escape"&&s()};function s(){document.removeEventListener("keydown",i),o.remove()}return a.addEventListener("click",s),o.addEventListener("click",d=>{d.target===o&&s()}),document.addEventListener("keydown",i),document.body.appendChild(o),{body:u,close:s}}function H(n){const{body:o,close:t}=B("Choose an icon"),e=document.createElement("input");e.type="text",e.placeholder="Search icons\u2026",e.className="form-input",e.style.cssText="padding:.45rem .6rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);background:var(--dm-input-bg,transparent);color:var(--dm-text);",o.appendChild(e);const r=document.createElement("div");r.style.cssText="display:grid;grid-template-columns:repeat(auto-fill,minmax(46px,1fr));gap:4px;overflow-y:auto;min-height:0;",o.appendChild(r);const a=window.I&&typeof I.list=="function"?I.list():[],u=a.map(i=>{const s=document.createElement("button");s.type="button",s.title=i,s.dataset.iconName=i,s.style.cssText="display:flex;align-items:center;justify-content:center;padding:.55rem 0;border:1px solid transparent;border-radius:6px;background:transparent;color:inherit;cursor:pointer;";const d=document.createElement("span");return d.setAttribute("data-icon",i),d.setAttribute("data-icon-size","18"),s.appendChild(d),s.addEventListener("mouseenter",()=>{s.style.borderColor="var(--dm-border)"}),s.addEventListener("mouseleave",()=>{s.style.borderColor="transparent"}),s.addEventListener("click",()=>{t(),n(i)}),r.appendChild(s),s});if(!a.length){const i=document.createElement("p");i.textContent="Icon engine unavailable - type an icon name into the field instead.",r.appendChild(i)}window.I?.scan?.(r),e.addEventListener("input",()=>{const i=e.value.trim().toLowerCase();u.forEach(s=>{s.style.display=!i||s.dataset.iconName.includes(i)?"":"none"})}),e.focus()}async function K(){const n=typeof S<"u"&&S.get?S.get("auth_token"):null;if(!n)return{error:"auth"};const o=r=>fetch("/api/media",{headers:{Authorization:"Bearer "+r}});let t;try{if(t=await o(n),t.status===401&&S.get("auth_refresh_token")){const r=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:S.get("auth_refresh_token")})});if(r.ok){const{token:a}=await r.json();S.set("auth_token",a),t=await o(a)}}}catch{return{error:"load"}}if(!t.ok)return{error:t.status===401||t.status===403?"auth":"load"};const e=await t.json().catch(()=>[]);return{files:(Array.isArray(e)?e:[]).filter(r=>/\.(png|jpe?g|gif|webp|svg|avif)$/i.test(r.name||r.url||""))}}function Y(n){const{body:o,close:t}=B("Choose an image"),e=document.createElement("p");e.textContent="Loading media library\u2026",e.style.cssText="margin:0;font-size:.85em;color:var(--dm-text-muted);",o.appendChild(e),K().then(({files:r,error:a})=>{if(a){e.textContent=a==="auth"?"Sign in with media access to browse the library - or paste an image URL into the field.":"Could not load the media library - paste an image URL into the field instead.";return}if(!r.length){e.textContent="No images in the media library yet.";return}e.remove();const u=document.createElement("div");u.style.cssText="display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:8px;overflow-y:auto;min-height:0;",r.forEach(i=>{const s=document.createElement("button");s.type="button",s.title=i.name||i.url,s.style.cssText="display:flex;flex-direction:column;gap:.25rem;padding:.35rem;border:1px solid var(--dm-border);border-radius:8px;background:transparent;color:inherit;cursor:pointer;min-width:0;";const d=document.createElement("img");d.src=i.url,d.alt=i.name||"",d.loading="lazy",d.style.cssText="width:100%;height:72px;object-fit:cover;border-radius:5px;";const c=document.createElement("span");c.textContent=i.name||i.url,c.style.cssText="font-size:.68em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;",s.appendChild(d),s.appendChild(c),s.addEventListener("click",()=>{t(),n(i.url)}),u.appendChild(s)}),o.appendChild(u)})}function z(n,o){n.value=o,n.dispatchEvent(new Event("input",{bubbles:!0})),n.dispatchEvent(new Event("change",{bubbles:!0}))}function P(n){const o=document.createElement("div");return o.style.cssText="display:flex;align-items:center;gap:.5rem;",n.parentNode.insertBefore(o,n),o.appendChild(n),n.style.flex="1",n.style.minWidth="0",o}function D(n){const o=document.createElement("button");return o.type="button",o.textContent=n,o.style.cssText=O,o}function $(n){if(!window.I||typeof I.list!="function")return;const o=P(n),t=document.createElement("span");t.style.cssText="display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;flex-shrink:0;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);";const e=()=>{t.textContent="";const a=n.value.trim();if(!a)return;const u=document.createElement("span");u.setAttribute("data-icon",a),u.setAttribute("data-icon-size","16"),t.appendChild(u),window.I?.scan?.(t)};n.addEventListener("input",e),e();const r=D("Browse");r.addEventListener("click",()=>H(a=>{z(n,a),e()})),o.appendChild(t),o.appendChild(r)}function G(n){const o=P(n),t=document.createElement("img");t.alt="",t.style.cssText="width:2rem;height:2rem;flex-shrink:0;object-fit:cover;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);display:none;";const e=()=>{const a=n.value.trim();t.style.display=a?"":"none",a&&(t.src=a)};n.addEventListener("input",e),e();const r=D("Browse");r.addEventListener("click",()=>Y(a=>{z(n,a),e()})),o.appendChild(t),o.appendChild(r)}export function enhancePickerFields(n,o){const t=typeof n=="string"?document.querySelector(n):n;t&&(o||[]).forEach(function(e){if(!e.name||e.type!=="icon"&&e.type!=="image")return;const r=t.querySelector('[name="'+e.name+'"]');!r||r.dataset.pickerEnhanced||(r.dataset.pickerEnhanced="1",e.type==="icon"?$(r):G(r))})}function F(n,o){const t=document.createElement("div");t.className="fb-form-honeypot",t.setAttribute("aria-hidden","true");const e=document.createElement("input");e.name=n.querySelector('[name="website"]')?"_website":"website",t.dataset.hpName=e.name,e.type="text",e.tabIndex=-1,e.autocomplete="url",e.placeholder="https://",t.appendChild(e);const r=document.createElement("input");r.name="_t",r.type="hidden",r.value=o||Date.now(),t.appendChild(r),n.appendChild(t)}function M(n,o){const t=n.querySelector("form");if(!t)return;const e=Array.from(t.querySelectorAll(".form-group"));let r=0;o.forEach(function(a){if(a.type==="spacer"){const u=document.createElement("div");u.className="fb-spacer";const i=e[r];if(i)t.insertBefore(u,i);else{const s=t.querySelector('[type="submit"]');s?t.insertBefore(u,s):t.appendChild(u)}}else a.type!=="page-break"&&r++})}function L(n,o){!n||!o||n.querySelectorAll('input[type="file"]').forEach(function(t){t.name&&t.files&&t.files.length&&o.set(t.name,Array.from(t.files))})}function x(n,o,t,e,r,a){const u=r||e,i=u.querySelector(".fb-form-honeypot"),s=i?.querySelector('input[type="text"]')?.value||"",d=i?.querySelector('[name="_t"]')?.value||"",c=Object.assign({},o);delete c._t,i?.dataset.hpName&&delete c[i.dataset.hpName];const m=new Map(a||[]);L(u,m);const l=m.size>0;let b,y;if(l){const f=new FormData;for(const[g,v]of Object.entries(c))v!=null&&(m.has(g)||f.append(g,typeof v=="object"?JSON.stringify(v):String(v)));for(const[g,v]of m)for(const w of v)f.append(g,w,w.name);f.append("_hp",s),f.append("_t",d),b=f,y={}}else{const f=Object.assign({},c,{_hp:s,_t:d});b=JSON.stringify(f),y={"Content-Type":"application/json"}}return fetch("/api/forms/submit/"+n,{method:"POST",headers:y,body:b}).then(f=>f.json().then(g=>({ok:f.ok,body:g}))).then(f=>{f.ok&&f.body.ok?(e.textContent="",C(e,f.body.message||t.successMessage||"Thank you.","success")):C(e,f.body.error||"Something went wrong.","error")}).catch(()=>{C(e,"Unable to submit. Please check your connection.","error")})}function Q(n,o,t,e,r){const a=document.createElement("form");a.noValidate=!0,o.forEach(function(i){const s=document.createElement("div");s.className="form-group",s.style.marginBottom="1.25rem";const d=document.createElement("label");if(d.className="form-label",d.textContent=i.label||i.name,i.required){const m=document.createElement("span");m.textContent=" *",m.style.color="var(--dm-danger)",d.appendChild(m)}let c;i.type==="textarea"?(c=document.createElement("textarea"),c.rows=i.formConfig?.rows||4,c.className="form-input"):i.type==="select"?(c=document.createElement("select"),c.className="form-input",(i.options||[]).forEach(function(m){const l=document.createElement("option");l.value=typeof m=="string"?m:m.value??"",l.textContent=typeof m=="string"?m:m.label||l.value,c.appendChild(l)})):(c=document.createElement("input"),c.type=i.type==="icon"||i.type==="image"?"text":i.type||"text",c.className="form-input",i.placeholder&&(c.placeholder=i.placeholder),i.type==="file"&&(i.file?.accept&&(c.accept=i.file.accept),i.multiple&&(c.multiple=!0))),c.name=i.name,c.required=i.required||!1,s.appendChild(d),s.appendChild(c),a.appendChild(s)}),t.honeypot&&F(a);const u=document.createElement("button");if(u.type="submit",u.className="btn btn-primary",u.textContent=t.submitText||"Submit",a.appendChild(u),a.addEventListener("submit",function(i){i.preventDefault();const s={};if(o.forEach(function(d){const c=a.querySelector('[name="'+d.name+'"]');c&&(s[d.name]=c.value)}),window.FormLogicEngine&&r){const d=window.FormLogicEngine,c=[],m=[];if(o.forEach(function(l){if(d.evaluateFieldVisibility(l,s)==="hidden"){delete s[l.name];return}const y=d.evaluateFieldRequirement(l,s),f=s[l.name];y&&(!f||!String(f).trim())&&c.push(l.label||l.name);const g=d.validateField(l,f||"",s);g.length&&m.push(g[0].message)}),c.length||m.length){const l=[];c.length&&l.push("Required: "+c.join(", ")),m.length&&l.push(m.join("; ")),C(n,l.join(". "),"error");return}}if(n.dataset.triggerBlocked==="true"){const d=n._formLogicRuntime,c=d&&d.submitBlock?d.submitBlock().reasons:[];C(n,c.join(" ")||"This form cannot be submitted yet.","error");return}n.classList.add("fb-form-loading"),u.disabled=!0,x(e,s,t,n,a).finally(function(){n.classList.remove("fb-form-loading"),u.disabled=!1})}),n.appendChild(a),enhancePickerFields(n,o),q(n,function(i){return x(e,i,t,n,a)}),window.FormLogicEngine&&r&&T(o)){const i=new window.FormLogicEngine.FormLogicRuntime(r,n);i.init(),n._formLogicRuntime=i}}function X(n){const o=n.getAttribute("data-form");o&&fetch("/api/forms/"+o+"/public").then(t=>{if(!t.ok)throw new Error("Form not found: "+o);return t.json()}).then(t=>{const e=t.fields||[],r=t.settings||{},a=document.createElement("div");a.className="fb-form-wrapper",n.appendChild(a);const u=e.some(i=>i.type==="page-break");if(typeof Domma<"u"&&Domma.forms){const i=Number(r.columns)===3?3:2,s=r.layout||"grid";if(u&&Domma.forms.wizard){const d=V(e,t.title),c=d.map(p=>p.fields),m=d.map(function(p){return{title:p.title,description:p.description,fields:A(p.fields,i)}});let l=0,b=null,y=null;const f=Date.now(),g=new Map,v=function(p){const h=window.FormLogicEngine;if(!h||!h.validateFields)return;const k=h.validateFields(a,c[l]||[]);k.valid||(p.preventDefault(),p.stopImmediatePropagation(),markInvalidFields(a,k.missing),showValidationPopup(k))},w=function(){const p=c[l]||e;if(R(a,p),enhancePickerFields(a,p),p.some(h=>h.type==="spacer")&&M(a,p),r.honeypot){const h=a.querySelector("form");h&&F(h,f)}window.FormLogicEngine&&T(e)&&(b&&b.destroy(),b=new window.FormLogicEngine.FormLogicRuntime(t,a,{baseValues:y&&y.getStepData?y.getStepData():{}}),b.init(),j(a,b)),a.querySelectorAll(".wizard-next, .wizard-submit").forEach(h=>h.addEventListener("click",v,!0))};y=Domma.forms.wizard(a,{schema:{steps:m},layout:s,columns:i,finishText:r.submitText||"Finish",onStepChange:function(p){L(a,g),l=p,setTimeout(w,0)},onSubmit:function(p){return L(a,g),x(o,p,r,a,null,g)}}),q(a,function(p){return L(a,g),x(o,p,r,a,null,g)}),a.addEventListener("dm-form-trigger",function(p){if(!p.detail||p.detail.type!=="goto-step"||!y||!y.goToStep)return;const h=Number(p.detail.action.step);!isFinite(h)||h<0||h>=m.length||h!==l&&y.goToStep(h)}),Promise.resolve(y).then(w)}else if(Domma.forms.render){const d=A(e,i),c=U(e);q(a,function(l){return x(o,l,r,a,null)});const m=Domma.forms.render(a,d,c,{submitText:r.submitText||"Submit",layout:s,columns:i,onSubmit:function(l){return x(o,l,r,a,null)}});Promise.resolve(m).then(function(){if(R(a,e),enhancePickerFields(a,e),window.FormLogicEngine&&T(e)){const l=new window.FormLogicEngine.FormLogicRuntime(t,a);l.init(),j(a,l)}if(e.some(l=>l.type==="spacer")&&M(a,e),s==="grid"&&r.submitSpan==="full"){const l=a.querySelector(".form-buttons");l&&l.classList.add("col-span-full")}if(r.honeypot){const l=a.querySelector("form");l&&F(l)}})}}else Q(a,e.filter(i=>i.type!=="page-break"&&i.type!=="spacer"),r,o,t)}).catch(t=>{const e=document.createElement("p");e.textContent="Form unavailable.",e.style.cssText="color:var(--dm-danger);font-style:italic;",n.appendChild(e),console.warn("[forms]",t.message)})}
|
|
1
|
+
import{buildWizardSteps as W}from"/public/js/form-steps.mjs";import{applySubmitButtons as N,applyWizardButtons as J,resolveSecondaryButton as U,submitWrapperClasses as H}from"/public/js/form-submit.mjs";const _=document.querySelectorAll("[data-form]");_.length&&_.forEach(ne);function T(n){return(n||[]).some(o=>o.logic||Array.isArray(o.triggers)&&o.triggers.length)}function K(n,o){const t=n.querySelector('[name="'+o+'"], [name="'+o+'[]"]')||n.querySelector('[data-chooser-field="'+o+'"]');if(!t)return null;const e=t.closest("form")||n;let r=t;for(;r.parentNode&&r.parentNode!==e;)r=r.parentNode;return r}export function markInvalidFields(n,o){n.querySelectorAll(".fb-field-invalid").forEach(e=>e.classList.remove("fb-field-invalid"));let t=null;(o||[]).forEach(function(e){const r=K(n,e.name);r&&(r.classList.add("fb-field-invalid"),t||(t=r))}),t&&t.scrollIntoView&&t.scrollIntoView({behavior:"smooth",block:"center"})}export function showValidationPopup(n){const o=(n.missing||[]).map(e=>"<strong>"+j(e.label)+"</strong> is required.").concat((n.errors||[]).map(j)),t=o.join("<br>");if(typeof E<"u"&&E.alert){E.alert(t,{title:"Please complete this step"});return}window.alert(o.join(`
|
|
2
|
+
`).replace(/<[^>]+>/g,""))}function j(n){return String(n).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function F(n,o){let t=!1;n.addEventListener("dm-form-end",function(e){const r=e.detail&&e.detail.validation;if(r&&!r.valid){markInvalidFields(n,r.missing),showValidationPopup(r);return}t||(t=!0,n.classList.add("fb-form-loading"),Promise.resolve(o(e.detail&&e.detail.values||{})).catch(function(){}).finally(function(){t=!1,n.classList.remove("fb-form-loading")}))})}function C(n,o,t){const e=n.querySelector(".fb-form-success, .fb-form-error");e&&e.remove();const r=document.createElement("div");r.className=t==="success"?"fb-form-success":"fb-form-error",r.textContent=o,n.appendChild(r)}function A(n,o){if(n._formLogicRuntime=o,n._formLogicObserver&&(n._formLogicObserver.disconnect(),n._formLogicObserver=null),!n.parentNode||typeof MutationObserver>"u"||n._formLogicObserved)return;n._formLogicObserved=!0;const t=new MutationObserver(function(e){for(const r of e)for(const i of r.removedNodes)if(i===n||i.nodeType===1&&i.contains&&i.contains(n)){n._formLogicRuntime&&n._formLogicRuntime.destroy(),t.disconnect();return}});t.observe(n.parentNode,{childList:!0,subtree:!1}),n._formLogicObserver=t}function Y(n){const o=n&&n.logic;return!!(o&&(o.visibility||o.requirement))}function B(n,o){const t={};return n.forEach(function(e){if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const r=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),i=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":r?"chooser":e.type,c={...e.formConfig||{}};c.span==="full"&&o&&(c.span=o);const s={type:i,label:e.label||e.name,required:Y(e)?!1:e.required||!1,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{helperText:e.helper},...e.tooltip&&{tooltip:e.tooltip},...c}};e.minLength!==void 0&&(s.minLength=e.minLength),e.maxLength!==void 0&&(s.maxLength=e.maxLength),e.min!==void 0&&(s.min=e.min),e.max!==void 0&&(s.max=e.max),r&&(e.variant&&(s.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(s.multiple=!0),e.density&&(s.density=e.density),e.columns&&(s.columns=e.columns),e.accent&&(s.accent=e.accent),e.accentStyle&&(s.accentStyle=e.accentStyle),e.glow&&(s.glow=!0),e.glowColour&&(s.glowColour=e.glowColour),e.shadow&&(s.shadow=e.shadow),e.shadowColour&&(s.shadowColour=e.shadowColour)),t[e.name]=s}),t}function $(n){const o={};return n.forEach(function(t){if(!(!t.name||t.type==="page-break"||t.type==="spacer")&&(t.type==="select"||t.type==="multiselect")&&t.required){const e=(t.options||[])[0];e&&(o[t.name]=typeof e=="object"?e.value:e)}}),o}function R(n,o){(o||[]).forEach(function(t){if(t.type!=="date"||!t.name)return;const e=n.querySelector('[name="'+t.name+'"]');e&&e.type!=="date"&&(e.type="date")})}const O="padding:.45rem .7rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);background:transparent;color:inherit;cursor:pointer;font:inherit;font-size:.85em;flex-shrink:0;line-height:1.2;";function z(n){const o=document.createElement("div");o.style.cssText="position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;padding:20px;";const t=document.createElement("div");t.style.cssText="background:var(--dm-surface-overlay,var(--dm-surface));color:var(--dm-text);border:1px solid var(--dm-border);border-radius:10px;width:100%;max-width:560px;max-height:75vh;display:flex;flex-direction:column;gap:.6rem;padding:1rem;box-shadow:var(--dm-shadow-lg,0 12px 40px rgba(0,0,0,.35));";const e=document.createElement("div");e.style.cssText="display:flex;align-items:center;justify-content:space-between;gap:.5rem;";const r=document.createElement("strong");r.textContent=n;const i=document.createElement("button");i.type="button",i.textContent="\xD7",i.setAttribute("aria-label","Close"),i.style.cssText=O+"font-size:1rem;padding:.15rem .55rem;",e.appendChild(r),e.appendChild(i),t.appendChild(e);const c=document.createElement("div");c.style.cssText="display:flex;flex-direction:column;gap:.6rem;overflow:hidden;min-height:0;",t.appendChild(c),o.appendChild(t);const s=l=>{l.key==="Escape"&&a()};function a(){document.removeEventListener("keydown",s),o.remove()}return i.addEventListener("click",a),o.addEventListener("click",l=>{l.target===o&&a()}),document.addEventListener("keydown",s),document.body.appendChild(o),{body:c,close:a}}function G(n){const{body:o,close:t}=z("Choose an icon"),e=document.createElement("input");e.type="text",e.placeholder="Search icons\u2026",e.className="form-input",e.style.cssText="padding:.45rem .6rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);background:var(--dm-input-bg,transparent);color:var(--dm-text);",o.appendChild(e);const r=document.createElement("div");r.style.cssText="display:grid;grid-template-columns:repeat(auto-fill,minmax(46px,1fr));gap:4px;overflow-y:auto;min-height:0;",o.appendChild(r);const i=window.I&&typeof I.list=="function"?I.list():[],c=i.map(s=>{const a=document.createElement("button");a.type="button",a.title=s,a.dataset.iconName=s,a.style.cssText="display:flex;align-items:center;justify-content:center;padding:.55rem 0;border:1px solid transparent;border-radius:6px;background:transparent;color:inherit;cursor:pointer;";const l=document.createElement("span");return l.setAttribute("data-icon",s),l.setAttribute("data-icon-size","18"),a.appendChild(l),a.addEventListener("mouseenter",()=>{a.style.borderColor="var(--dm-border)"}),a.addEventListener("mouseleave",()=>{a.style.borderColor="transparent"}),a.addEventListener("click",()=>{t(),n(s)}),r.appendChild(a),a});if(!i.length){const s=document.createElement("p");s.textContent="Icon engine unavailable - type an icon name into the field instead.",r.appendChild(s)}window.I?.scan?.(r),e.addEventListener("input",()=>{const s=e.value.trim().toLowerCase();c.forEach(a=>{a.style.display=!s||a.dataset.iconName.includes(s)?"":"none"})}),e.focus()}async function Q(){const n=typeof S<"u"&&S.get?S.get("auth_token"):null;if(!n)return{error:"auth"};const o=r=>fetch("/api/media",{headers:{Authorization:"Bearer "+r}});let t;try{if(t=await o(n),t.status===401&&S.get("auth_refresh_token")){const r=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:S.get("auth_refresh_token")})});if(r.ok){const{token:i}=await r.json();S.set("auth_token",i),t=await o(i)}}}catch{return{error:"load"}}if(!t.ok)return{error:t.status===401||t.status===403?"auth":"load"};const e=await t.json().catch(()=>[]);return{files:(Array.isArray(e)?e:[]).filter(r=>/\.(png|jpe?g|gif|webp|svg|avif)$/i.test(r.name||r.url||""))}}function X(n){const{body:o,close:t}=z("Choose an image"),e=document.createElement("p");e.textContent="Loading media library\u2026",e.style.cssText="margin:0;font-size:.85em;color:var(--dm-text-muted);",o.appendChild(e),Q().then(({files:r,error:i})=>{if(i){e.textContent=i==="auth"?"Sign in with media access to browse the library - or paste an image URL into the field.":"Could not load the media library - paste an image URL into the field instead.";return}if(!r.length){e.textContent="No images in the media library yet.";return}e.remove();const c=document.createElement("div");c.style.cssText="display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:8px;overflow-y:auto;min-height:0;",r.forEach(s=>{const a=document.createElement("button");a.type="button",a.title=s.name||s.url,a.style.cssText="display:flex;flex-direction:column;gap:.25rem;padding:.35rem;border:1px solid var(--dm-border);border-radius:8px;background:transparent;color:inherit;cursor:pointer;min-width:0;";const l=document.createElement("img");l.src=s.url,l.alt=s.name||"",l.loading="lazy",l.style.cssText="width:100%;height:72px;object-fit:cover;border-radius:5px;";const m=document.createElement("span");m.textContent=s.name||s.url,m.style.cssText="font-size:.68em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;",a.appendChild(l),a.appendChild(m),a.addEventListener("click",()=>{t(),n(s.url)}),c.appendChild(a)}),o.appendChild(c)})}function P(n,o){n.value=o,n.dispatchEvent(new Event("input",{bubbles:!0})),n.dispatchEvent(new Event("change",{bubbles:!0}))}function D(n){const o=document.createElement("div");return o.style.cssText="display:flex;align-items:center;gap:.5rem;",n.parentNode.insertBefore(o,n),o.appendChild(n),n.style.flex="1",n.style.minWidth="0",o}function M(n){const o=document.createElement("button");return o.type="button",o.textContent=n,o.style.cssText=O,o}function Z(n){if(!window.I||typeof I.list!="function")return;const o=D(n),t=document.createElement("span");t.style.cssText="display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;flex-shrink:0;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);";const e=()=>{t.textContent="";const i=n.value.trim();if(!i)return;const c=document.createElement("span");c.setAttribute("data-icon",i),c.setAttribute("data-icon-size","16"),t.appendChild(c),window.I?.scan?.(t)};n.addEventListener("input",e),e();const r=M("Browse");r.addEventListener("click",()=>G(i=>{P(n,i),e()})),o.appendChild(t),o.appendChild(r)}function ee(n){const o=D(n),t=document.createElement("img");t.alt="",t.style.cssText="width:2rem;height:2rem;flex-shrink:0;object-fit:cover;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);display:none;";const e=()=>{const i=n.value.trim();t.style.display=i?"":"none",i&&(t.src=i)};n.addEventListener("input",e),e();const r=M("Browse");r.addEventListener("click",()=>X(i=>{P(n,i),e()})),o.appendChild(t),o.appendChild(r)}export function enhancePickerFields(n,o){const t=typeof n=="string"?document.querySelector(n):n;t&&(o||[]).forEach(function(e){if(!e.name||e.type!=="icon"&&e.type!=="image")return;const r=t.querySelector('[name="'+e.name+'"]');!r||r.dataset.pickerEnhanced||(r.dataset.pickerEnhanced="1",e.type==="icon"?Z(r):ee(r))})}function q(n,o){const t=document.createElement("div");t.className="fb-form-honeypot",t.setAttribute("aria-hidden","true");const e=document.createElement("input");e.name=n.querySelector('[name="website"]')?"_website":"website",t.dataset.hpName=e.name,e.type="text",e.tabIndex=-1,e.autocomplete="url",e.placeholder="https://",t.appendChild(e);const r=document.createElement("input");r.name="_t",r.type="hidden",r.value=o||Date.now(),t.appendChild(r),n.appendChild(t)}function V(n,o){const t=n.querySelector("form");if(!t)return;const e=Array.from(t.querySelectorAll(".form-group"));let r=0;o.forEach(function(i){if(i.type==="spacer"){const c=document.createElement("div");c.className="fb-spacer";const s=e[r];if(s)t.insertBefore(c,s);else{const a=t.querySelector('[type="submit"]');a?t.insertBefore(c,a):t.appendChild(c)}}else i.type!=="page-break"&&r++})}function L(n,o){!n||!o||n.querySelectorAll('input[type="file"]').forEach(function(t){t.name&&t.files&&t.files.length&&o.set(t.name,Array.from(t.files))})}function x(n,o,t,e,r,i){const c=r||e,s=c.querySelector(".fb-form-honeypot"),a=s?.querySelector('input[type="text"]')?.value||"",l=s?.querySelector('[name="_t"]')?.value||"",m=Object.assign({},o);delete m._t,s?.dataset.hpName&&delete m[s.dataset.hpName];const d=new Map(i||[]);L(c,d);const u=d.size>0;let p,b;if(u){const h=new FormData;for(const[g,v]of Object.entries(m))v!=null&&(d.has(g)||h.append(g,typeof v=="object"?JSON.stringify(v):String(v)));for(const[g,v]of d)for(const w of v)h.append(g,w,w.name);h.append("_hp",a),h.append("_t",l),p=h,b={}}else{const h=Object.assign({},m,{_hp:a,_t:l});p=JSON.stringify(h),b={"Content-Type":"application/json"}}return fetch("/api/forms/submit/"+n,{method:"POST",headers:b,body:p}).then(h=>h.json().then(g=>({ok:h.ok,body:g}))).then(h=>{h.ok&&h.body.ok?(e.textContent="",C(e,h.body.message||t.successMessage||"Thank you.","success")):C(e,h.body.error||"Something went wrong.","error")}).catch(()=>{C(e,"Unable to submit. Please check your connection.","error")})}function te(n,o,t,e,r){const i=document.createElement("form");i.noValidate=!0,o.forEach(function(a){const l=document.createElement("div");l.className="form-group",l.style.marginBottom="1.25rem";const m=document.createElement("label");if(m.className="form-label",m.textContent=a.label||a.name,a.required){const u=document.createElement("span");u.textContent=" *",u.style.color="var(--dm-danger)",m.appendChild(u)}let d;a.type==="textarea"?(d=document.createElement("textarea"),d.rows=a.formConfig?.rows||4,d.className="form-input"):a.type==="select"?(d=document.createElement("select"),d.className="form-input",(a.options||[]).forEach(function(u){const p=document.createElement("option");p.value=typeof u=="string"?u:u.value??"",p.textContent=typeof u=="string"?u:u.label||p.value,d.appendChild(p)})):(d=document.createElement("input"),d.type=a.type==="icon"||a.type==="image"?"text":a.type||"text",d.className="form-input",a.placeholder&&(d.placeholder=a.placeholder),a.type==="file"&&(a.file?.accept&&(d.accept=a.file.accept),a.multiple&&(d.multiple=!0))),d.name=a.name,d.required=a.required||!1,l.appendChild(m),l.appendChild(d),i.appendChild(l)}),t.honeypot&&q(i);const c=document.createElement("button");if(c.type="submit",c.className="btn btn-primary",c.textContent=t.submitText||"Submit",H(t,"stacked").length||U(t)){const a=document.createElement("div");a.className="form-group form-buttons",a.appendChild(c),i.appendChild(a)}else i.appendChild(c);if(i.addEventListener("submit",function(a){a.preventDefault();const l={};if(o.forEach(function(m){const d=i.querySelector('[name="'+m.name+'"]');d&&(l[m.name]=d.value)}),window.FormLogicEngine&&r){const m=window.FormLogicEngine,d=[],u=[];if(o.forEach(function(p){if(m.evaluateFieldVisibility(p,l)==="hidden"){delete l[p.name];return}const h=m.evaluateFieldRequirement(p,l),g=l[p.name];h&&(!g||!String(g).trim())&&d.push(p.label||p.name);const v=m.validateField(p,g||"",l);v.length&&u.push(v[0].message)}),d.length||u.length){const p=[];d.length&&p.push("Required: "+d.join(", ")),u.length&&p.push(u.join("; ")),C(n,p.join(". "),"error");return}}if(n.dataset.triggerBlocked==="true"){const m=n._formLogicRuntime,d=m&&m.submitBlock?m.submitBlock().reasons:[];C(n,d.join(" ")||"This form cannot be submitted yet.","error");return}n.classList.add("fb-form-loading"),c.disabled=!0,x(e,l,t,n,i).finally(function(){n.classList.remove("fb-form-loading"),c.disabled=!1})}),n.appendChild(i),N(i,t,"stacked"),enhancePickerFields(n,o),F(n,function(a){return x(e,a,t,n,i)}),window.FormLogicEngine&&r&&T(o)){const a=new window.FormLogicEngine.FormLogicRuntime(r,n);a.init(),n._formLogicRuntime=a}}function ne(n){const o=n.getAttribute("data-form");o&&fetch("/api/forms/"+o+"/public").then(t=>{if(!t.ok)throw new Error("Form not found: "+o);return t.json()}).then(t=>{const e=t.fields||[],r=t.settings||{},i=document.createElement("div");i.className="fb-form-wrapper",n.appendChild(i);const c=e.some(s=>s.type==="page-break");if(typeof Domma<"u"&&Domma.forms){const s=Number(r.columns)===3?3:2,a=r.layout||"grid";if(c&&Domma.forms.wizard){const l=W(e,t.title),m=l.map(f=>f.fields),d=l.map(function(f){return{title:f.title,description:f.description,fields:B(f.fields,s)}});let u=0,p=null,b=null;const h=Date.now(),g=new Map,v=function(f){const y=window.FormLogicEngine;if(!y||!y.validateFields)return;const k=y.validateFields(i,m[u]||[]);k.valid||(f.preventDefault(),f.stopImmediatePropagation(),markInvalidFields(i,k.missing),showValidationPopup(k))},w=function(){const f=m[u]||e;if(J(i,r),R(i,f),enhancePickerFields(i,f),f.some(y=>y.type==="spacer")&&V(i,f),r.honeypot){const y=i.querySelector("form");y&&q(y,h)}window.FormLogicEngine&&T(e)&&(p&&p.destroy(),p=new window.FormLogicEngine.FormLogicRuntime(t,i,{baseValues:b&&b.getStepData?b.getStepData():{}}),p.init(),A(i,p)),i.querySelectorAll(".wizard-next, .wizard-submit").forEach(y=>y.addEventListener("click",v,!0))};b=Domma.forms.wizard(i,{schema:{steps:d},layout:a,columns:s,finishText:r.submitText||"Finish",onStepChange:function(f){L(i,g),u=f,setTimeout(w,0)},onSubmit:function(f){return L(i,g),x(o,f,r,i,null,g)}}),F(i,function(f){return L(i,g),x(o,f,r,i,null,g)}),i.addEventListener("dm-form-trigger",function(f){if(!f.detail||f.detail.type!=="goto-step"||!b||!b.goToStep)return;const y=Number(f.detail.action.step);!isFinite(y)||y<0||y>=d.length||y!==u&&b.goToStep(y)}),Promise.resolve(b).then(w)}else if(Domma.forms.render){const l=B(e,s),m=$(e);F(i,function(u){return x(o,u,r,i,null)});const d=Domma.forms.render(i,l,m,{submitText:r.submitText||"Submit",layout:a,columns:s,onSubmit:function(u){return x(o,u,r,i,null)}});Promise.resolve(d).then(function(){if(N(i,r,a),R(i,e),enhancePickerFields(i,e),window.FormLogicEngine&&T(e)){const u=new window.FormLogicEngine.FormLogicRuntime(t,i);u.init(),A(i,u)}if(e.some(u=>u.type==="spacer")&&V(i,e),r.honeypot){const u=i.querySelector("form");u&&q(u)}})}}else te(i,e.filter(s=>s.type!=="page-break"&&s.type!=="spacer"),r,o,t)}).catch(t=>{const e=document.createElement("p");e.textContent="Form unavailable.",e.style.cssText="color:var(--dm-danger);font-style:italic;",n.appendChild(e),console.warn("[forms]",t.message)})}
|
package/public/js/site.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{makeBadgeEl as M,applyColour as O,applyPill as V,applyFloat as J,applyHighlight as N,applyItemHighlight as W,itemHighlightAttrs as G,isCurrentItem as B,menuAlign as X}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{initFloatingMenus as Y}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as j,markInvalidFields as R,showValidationPopup as P}from"/public/js/forms.js?v=20260926-form-end";import{buildWizardSteps as U}from"/public/js/form-steps.mjs";$(()=>{const w=window.__CMS_NAV__||{},g=window.__CMS_SITE__||{};if(g.autoTheme?.enabled){let t=function(e){const s=(e||"07:00").split(":");return+s[0]*60+(+s[1]||0)},n=function(){const e=new Date,s=e.getHours()*60+e.getMinutes();return s>=t(o.dayStart)&&s<t(o.nightStart)?o.dayTheme:o.nightTheme},i=function(e){const s=o.apply&&o.apply[e]||{theme:e,custom:""};Domma.theme.set(s.theme);for(const p of[...document.body.classList])p.startsWith("dm-custom-")&&document.body.classList.remove(p);s.custom&&document.body.classList.add(s.custom)};const o=g.autoTheme;i(n()),setInterval(()=>i(n()),6e4)}const _=$("#site-navbar");if(_.length&&w.brand){let n=function(c,h=0){return Array.isArray(c)?c.filter(r=>!(r&&r.type==="spacer")).filter(r=>!(r&&r.type==="separator"&&h===0)).map(r=>r&&r.type==="separator"?{divider:!0}:Array.isArray(r?.items)?{...r,items:n(r.items,h+1)}:r):c},i=function(c){return Array.isArray(c)?c.filter(h=>!(h&&(h.type==="separator"||h.type==="spacer"))):[]};const o={...w.brand},t=o.size&&o.size!=="md"?` navbar-brand-${o.size}`:"";if(o.logo||o.icon||o.tagline){let c="";o.logo?c+=`<img src="${o.logo}" class="navbar-brand-logo" alt="${o.text||""}">`:o.icon&&(c+=`<span data-icon="${o.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),o.text&&(c+=`<span class="navbar-brand-text${t}">${o.text}</span>`),o.tagline&&(c+=`<small class="navbar-brand-tagline">${o.tagline}</small>`),o.html=c}else t&&o.text&&(o.html=`<span class="navbar-brand-text${t}">${o.text}</span>`);const e=w.position==="floating",s=w.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:o,items:n(w.items||[]),variant:w.variant||"dark",position:e?"static":w.position||"sticky",collapsible:!0,appearOnHover:!!w.appearOnHover}),Domma.icons.scan("#site-navbar");const p=_.get(0);p.classList.add(`dm-nav-align-${X(w)||"centre"}`),s&&_.get(0).classList.add("dm-nav-vertical",w.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),N(p,w),e&&(J(_.get(0),w),Y(_.get(0).parentNode||document));const y=p.querySelector(".navbar-toggle"),u=p.querySelector(".navbar-collapse");if(y&&u){let c=null;const h=()=>{u.classList.contains("show")&&y.click()},r=()=>{c&&(c.remove(),c=null),document.body.classList.remove("dm-drawer-open")},a=()=>{c||(c=document.createElement("div"),c.className="dm-navbar-backdrop",c.addEventListener("click",h),document.body.appendChild(c),document.body.classList.add("dm-drawer-open"))};y.addEventListener("click",()=>{requestAnimationFrame(()=>{u.classList.contains("show")?a():r()})}),u.addEventListener("click",l=>{l.target.closest("a")&&requestAnimationFrame(r)}),document.addEventListener("keydown",l=>{l.key==="Escape"&&u.classList.contains("show")&&h()})}const m=p.querySelector(".navbar-nav");if(m){const c=Array.from(m.children);let h=0;for(const r of w.items||[])if(!(r&&r.type==="separator")){if(r&&r.type==="spacer"){const a=document.createElement("li"),l=["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md";a.className=`dm-menu-spacer dm-menu-spacer--${l}`,a.setAttribute("aria-hidden","true"),m.insertBefore(a,c[h]||null);continue}h++}}const x=i(w.items||[]);p.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(c=>{const h=x[parseInt(c.dataset.index,10)];if(!h)return;const r=c.dataset.subindex!=null,a=r&&Array.isArray(h.items)?h.items[parseInt(c.dataset.subindex,10)]:h;if(!a)return;a.colour&&O(c,a.colour),a.pill&&!r&&V(c,a.pill),W(c,a);const l=M(a.badge);l&&c.appendChild(l),B(a.url,location.pathname)&&(c.classList.add("active"),c.setAttribute("aria-current","page"))})}const C=$("#site-footer");if(C.length){let e=function(a){return String(a??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},s=function(a){return e(a).replace(/'/g,"'")},p=function(a){if(a&&a.type==="separator")return'<li class="footer-sep"><hr></li>';if(a&&a.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(a.size)?a.size:"md"}" aria-hidden="true"></li>`;const l=s(a.url||"#"),d=e(a.text||""),b=Array.isArray(a.items)&&a.items.length?`<ul>${a.items.map(p).join("")}</ul>`:"",v=G(a),I=[v.hover&&` data-menu-item-hover="${s(v.hover)}"`,v.active&&` data-menu-item-active="${s(v.active)}"`].filter(Boolean).join(""),q=v.accent?` style="--dm-menu-highlight:${s(v.accent)}"`:"";return`<li><a href="${l}"${I}${q}>${d}</a>${b}</li>`},y=function(a,l){return(a||[]).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 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 i=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(i.copyright&&(n+=`<p>${e(i.copyright)}</p>`),i.primary.length&&(n+=`<nav class="footer-nav footer-nav--primary"><ul>${i.primary.map(p).join("")}</ul></nav>`),i.legal.length&&(n+=`<nav class="footer-nav footer-nav--legal"><ul>${i.legal.map(p).join("")}</ul></nav>`),g.footer){const a=Object.keys(t).filter(l=>o[l]);a.length&&(n+='<div class="footer-social">',a.forEach(l=>{const{label:d,svg:b}=t[l];n+=`<a href="${o[l]}" target="_blank" rel="noopener noreferrer" aria-label="${d}" class="footer-social-link">${b}</a>`}),n+="</div>")}n+="</div>",C.html(n);const u=[...y(i.primary,[]),...y(i.legal,[])];C.get(0).querySelectorAll(".footer-nav a").forEach((a,l)=>{const d=u[l];if(!d)return;d.colour&&O(a,d.colour);const b=M(d.badge);b&&a.appendChild(b),B(d.url,location.pathname)&&(a.classList.add("is-active"),a.setAttribute("aria-current","page"))}),N(C.get(0).querySelector(".footer-nav--primary"),{highlight:i.primaryHighlight}),N(C.get(0).querySelector(".footer-nav--legal"),{highlight:i.legalHighlight});const x=g.footer?.showMotionToggle!==!1,c=S.get("reduced_motion"),h=c!==null?!!c:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),r=C.get(0).querySelector(".footer-inner");if(x&&r){const a=document.createElement("input");a.type="checkbox",a.className="form-switch-input",a.id="dm-motion-switch",a.checked=h,a.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(a),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 f=$(".page-body");if(f.length){f.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),f.find(".tabs").each(function(){Domma.elements.tabs(this)}),f.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)}),f.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)}),f.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),f.find(".dm-progression").each(function(){const o=Array.from(this.querySelectorAll(".dm-progression-item")).map(i=>{const e={title:i.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:i.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return i.dataset.date&&(e.date=i.dataset.date),i.dataset.status&&(e.status=i.dataset.status),i.dataset.entryId&&(e._entryId=i.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",i=parseInt(o.dataset.revealDuration,10)||400,e=parseInt(o.dataset.revealStagger,10)||60,s=parseInt(o.dataset.revealDelay,10)||0,p=o.dataset.revealDirection==="rtl",y=Array.from(o.children),u={"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,x)=>{m.style.opacity="0",m.style.transform=u[n]||"",m.style.transition=`opacity ${i}ms ease, transform ${i}ms ease`;const c=p?y.length-1-x:x;m.style.transitionDelay=t==="stagger"?`${s+c*e}ms`:`${s}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const m=new IntersectionObserver(x=>{x.forEach(c=>{c.isIntersecting&&(c.target.offsetWidth,c.target.style.opacity="1",c.target.style.transform="none",m.unobserve(c.target))})},{threshold:.1});y.forEach(x=>m.observe(x))}))}),f.find(".card[data-collapsible]").each(function(){const o=this.querySelector(".card-header");o&&o.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),f.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),i=JSON.parse(n);Object.assign(o,i)}catch{}}),Object.keys(o).length>0){const t={};for(const[n,i]of Object.entries(o)){const e=i?.events?.click,{confirm:s,toast:p,alert:y,prompt:u,...m}=e||{};s||p||y||u?($(n).on("click",async function(c){if(c.preventDefault(),s&&!await E.confirm(s))return;let h=null;if(!(u&&(h=await E.prompt(u,{inputPlaceholder:m.promptPlaceholder||"",inputValue:m.promptDefault||""}),h===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),h!==null&&(m.setText&&r.text(h),m.setVal&&r.val(h),m.setAttr&&r.attr(m.setAttr,h))}m.href&&(window.location.href=m.href),p&&E.toast(p,{type:m.toastType||"success"}),y&&E.alert(y)}}),Object.keys(m).length&&(t[n]={...i,events:{...i.events,click:m}})):t[n]=i}$.setup(t)}}f.length&&D(f.get(0))}),window.wireCTAButtons=D;function D(w){w.querySelectorAll(".dm-cta-trigger").forEach(g=>{g.__ctaWired||(g.__ctaWired=!0,g.addEventListener("click",async()=>{const _=g.dataset.action,C=g.dataset.entry,A=g.dataset.confirm;let f=S.get("auth_token");if(!f){E.toast("Please log in to perform this action.",{type:"warning"});return}if(A&&!await E.confirm(A))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/${_}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify(g.dataset.scope?{entryId:C,scope:g.dataset.scope}:{entryId:C})});try{let n=await t(f);if(n.status===401){const e=S.get("auth_refresh_token");if(e){const s=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(s.ok){const{token:p}=await s.json();S.set("auth_token",p),f=p,n=await t(f)}}}const i=await n.json().catch(()=>({}));if(!n.ok)throw new Error(i.error||i.message||`Error ${n.status}`);E.toast(i.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 _=S.get("auth_token");function C(f,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 i=document.createElement("a");i.href="/login",i.textContent="sign in",n.appendChild(i),n.appendChild(document.createTextNode(" to view your entries."))}else n.textContent=f;return t.appendChild(n),t}function A(f,o){const t=document.createElement("template");t.innerHTML=o||"",f.replaceWith(t.content)}g.forEach(async f=>{const o=f.dataset.collectionAttrs;if(!o)return;if(!_){f.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=_,i=await t(n);if(i.status===401){const s=S.get("auth_refresh_token");if(s){const p=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:s})});if(p.ok){const{token:y}=await p.json();S.set("auth_token",y),n=y,i=await t(n)}}}if(!i.ok){const{error:s}=await i.json().catch(()=>({}));f.replaceChildren(C(s||"Unable to load entries.",!1));return}const{html:e}=await i.json();A(f,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{f.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(_=>{let C;try{C=JSON.parse(atob(_.dataset.payload))}catch{return}const{columns:A,rows:f,search:o,sortable:t,exportable:n,pageSize:i,empty:e,ctaConfig:s,slug:p,exportCfg:y}=C;if(!A?.length)return;if(s){const x=A.findIndex(c=>c.key==="_cta");x!==-1&&(A[x]={key:"_cta",title:"",render:(c,h)=>{const r=document.createElement("button");if(r.className=`btn btn-${s.style||"primary"} dm-cta-trigger`,r.dataset.action=s.action||"",s.scope&&(r.dataset.scope=s.scope),r.dataset.entry=h._entryId||"",s.confirm&&(r.dataset.confirm=s.confirm),s.icon){const a=document.createElement("span");a.dataset.icon=s.icon,r.appendChild(a),r.appendChild(document.createTextNode(" "))}return r.appendChild(document.createTextNode(s.label||"Run")),r}})}const u="col-table-"+Math.random().toString(36).slice(2,7),m=document.createElement("div");m.id=u,_.replaceChildren(m),T.create("#"+u,{data:f,columns:A,search:o,sortable:t,exportable:n,pageSize:i,emptyMessage:e}),s&&D(m),_._dmTable={columns:A,rows:f,slug:p,exportCfg:y,render(x){T.create("#"+u,{data:x,columns:A,search:o,sortable:t,exportable:n,pageSize:i,emptyMessage:e}),s&&D(m)}}})})(),(function(){const g=document.querySelectorAll("[data-form-inline]");if(!g.length||typeof F>"u")return;function _(t){const n=t&&t.logic;return!!(n&&(n.visibility||n.requirement))}function C(t,n){const i={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const s=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),p=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":s?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&n&&(y.span=n);const u={type:p,required:_(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...e.tooltip&&{tooltip:e.tooltip},...y}};e.minLength!==void 0&&(u.minLength=e.minLength),e.maxLength!==void 0&&(u.maxLength=e.maxLength),e.min!==void 0&&(u.min=e.min),e.max!==void 0&&(u.max=e.max),s&&(e.variant&&(u.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(u.multiple=!0),e.density&&(u.density=e.density),e.columns&&(u.columns=e.columns),e.accent&&(u.accent=e.accent),e.accentStyle&&(u.accentStyle=e.accentStyle),e.glow&&(u.glow=!0),e.glowColour&&(u.glowColour=e.glowColour),e.shadow&&(u.shadow=e.shadow),e.shadowColour&&(u.shadowColour=e.shadowColour)),i[e.name]=u}),i}function A(t){const n={};return(t||[]).forEach(i=>{if(!(!i.name||i.type==="page-break"||i.type==="spacer")&&(i.type==="select"||i.type==="multiselect")&&i.required){const e=(i.options||[])[0];e&&(n[i.name]=typeof e=="object"?e.value:e)}}),n}function f(t,n){(n||[]).forEach(i=>{if(i.type!=="date"||!i.name)return;const e=t.querySelector(`[name="${i.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function o(t,n,i){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=i?"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 i=n.fields||[],e=n.settings||{},s=Number(e.columns)===3?3:2,p=e.layout||"grid",y=i.some(r=>r.type==="page-break"),u=async r=>{if(t.dataset.triggerBlocked==="true"){const a=t._formLogicRuntime,l=a&&a.submitBlock?a.submitBlock().reasons:[];o(t,l.join(" ")||"This form cannot be submitted yet.",!0);const d=new Error("Blocked by a form trigger");throw d.formSubmitFailed=!0,d}try{const a=t.querySelector(".fb-form-honeypot"),l=a?.querySelector('input[type="text"]')||null,d=a?.querySelector('[name="_t"]')||null,b=Object.assign({},r);delete b._t,a?.dataset.hpName&&delete b[a.dataset.hpName],l!==null&&(b._hp=l.value),d!==null&&(b._t=d.value);const v=await H.post(`/api/forms/submit/${n.slug}`,b);if(v&&v.error){o(t,v.error,!0);const I=new Error(v.error);throw I.formSubmitFailed=!0,I}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(a){throw a.formSubmitFailed||o(t,a.message||"Submission failed. Please try again.",!0),a}},m=Date.now();function x(r){const a=r.querySelector("form");if(!a||a.querySelector(".fb-form-honeypot"))return;const l=document.createElement("div");l.className="fb-form-honeypot",l.setAttribute("aria-hidden","true");const d=document.createElement("input");d.name=a.querySelector('[name="website"]')?"_website":"website",l.dataset.hpName=d.name,d.type="text",d.tabIndex=-1,d.autocomplete="url",d.placeholder="https://",l.appendChild(d);const b=document.createElement("input");b.name="_t",b.type="hidden",b.value=m,l.appendChild(b),a.appendChild(l)}function c(r){if(!window.FormLogicEngine||!i.some(l=>l.logic||Array.isArray(l.triggers)&&l.triggers.length))return null;const a=new window.FormLogicEngine.FormLogicRuntime(n,t,r?{baseValues:r}:void 0);if(a.init(),t._formLogicRuntime=a,t._formLogicObserved)return a;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const l=new MutationObserver(function(d){for(const b of d)for(const v of b.removedNodes)if(v===t||v.nodeType===1&&v.contains&&v.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),l.disconnect();return}});l.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=l}return a}let h=!1;if(t.addEventListener("dm-form-end",r=>{const a=r.detail?.validation;if(a&&!a.valid){R(t,a.missing),P(a);return}h||(h=!0,u(r.detail?.values||{}).catch(()=>{}).finally(()=>{h=!1}))}),y&&F.wizard){const r=U(i,n.title),a=r.map(k=>({title:k.title,description:k.description,fields:C(k.fields,s)})),l=r.map(k=>k.fields);let d=0,b=null,v=null;const I=k=>{const L=window.FormLogicEngine;if(!L||!L.validateFields)return;const z=L.validateFields(t,l[d]||[]);z.valid||(k.preventDefault(),k.stopImmediatePropagation(),R(t,z.missing),P(z))},q=()=>{const k=l[d]||i;f(t,k),j(t,k),e.honeypot!==!1&&x(t),b&&b.destroy(),b=c(v?.getStepData?v.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(L=>L.addEventListener("click",I,!0))};v=F.wizard(t,{schema:{steps:a},layout:p,columns:s,finishText:e.submitText||"Finish",onSubmit:u,onStepChange:k=>{d=k,setTimeout(q,0)}}),t.addEventListener("dm-form-trigger",k=>{if(k.detail?.type!=="goto-step"||!v?.goToStep)return;const L=Number(k.detail.action.step);!Number.isFinite(L)||L<0||L>=a.length||L!==d&&v.goToStep(L)}),Promise.resolve(v).then(q)}else if(F.render){const r=F.render(t,C(i,s),A(i),{submitText:e.submitText||"Submit",layout:p,columns:s,onSubmit:u});Promise.resolve(r).then(function(){if(p==="grid"&&e.submitSpan==="full"){const a=t.querySelector(".form-buttons");a&&a.classList.add("col-span-full")}f(t,i),j(t,i),e.honeypot!==!1&&x(t),c()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
|
1
|
+
import{makeBadgeEl as M,applyColour as O,applyPill as V,applyFloat as J,applyHighlight as N,applyItemHighlight as W,itemHighlightAttrs as X,isCurrentItem as B,menuAlign as G}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{initFloatingMenus as Y}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as j,markInvalidFields as R,showValidationPopup as P}from"/public/js/forms.js?v=20260927-submit-btn";import{buildWizardSteps as U}from"/public/js/form-steps.mjs";import{applySubmitButtons as K,applyWizardButtons as Q}from"/public/js/form-submit.mjs";$(()=>{const w=window.__CMS_NAV__||{},g=window.__CMS_SITE__||{};if(g.autoTheme?.enabled){let t=function(e){const s=(e||"07:00").split(":");return+s[0]*60+(+s[1]||0)},n=function(){const e=new Date,s=e.getHours()*60+e.getMinutes();return s>=t(o.dayStart)&&s<t(o.nightStart)?o.dayTheme:o.nightTheme},i=function(e){const s=o.apply&&o.apply[e]||{theme:e,custom:""};Domma.theme.set(s.theme);for(const p of[...document.body.classList])p.startsWith("dm-custom-")&&document.body.classList.remove(p);s.custom&&document.body.classList.add(s.custom)};const o=g.autoTheme;i(n()),setInterval(()=>i(n()),6e4)}const _=$("#site-navbar");if(_.length&&w.brand){let n=function(c,h=0){return Array.isArray(c)?c.filter(r=>!(r&&r.type==="spacer")).filter(r=>!(r&&r.type==="separator"&&h===0)).map(r=>r&&r.type==="separator"?{divider:!0}:Array.isArray(r?.items)?{...r,items:n(r.items,h+1)}:r):c},i=function(c){return Array.isArray(c)?c.filter(h=>!(h&&(h.type==="separator"||h.type==="spacer"))):[]};const o={...w.brand},t=o.size&&o.size!=="md"?` navbar-brand-${o.size}`:"";if(o.logo||o.icon||o.tagline){let c="";o.logo?c+=`<img src="${o.logo}" class="navbar-brand-logo" alt="${o.text||""}">`:o.icon&&(c+=`<span data-icon="${o.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),o.text&&(c+=`<span class="navbar-brand-text${t}">${o.text}</span>`),o.tagline&&(c+=`<small class="navbar-brand-tagline">${o.tagline}</small>`),o.html=c}else t&&o.text&&(o.html=`<span class="navbar-brand-text${t}">${o.text}</span>`);const e=w.position==="floating",s=w.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:o,items:n(w.items||[]),variant:w.variant||"dark",position:e?"static":w.position||"sticky",collapsible:!0,appearOnHover:!!w.appearOnHover}),Domma.icons.scan("#site-navbar");const p=_.get(0);p.classList.add(`dm-nav-align-${G(w)||"centre"}`),s&&_.get(0).classList.add("dm-nav-vertical",w.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),N(p,w),e&&(J(_.get(0),w),Y(_.get(0).parentNode||document));const y=p.querySelector(".navbar-toggle"),u=p.querySelector(".navbar-collapse");if(y&&u){let c=null;const h=()=>{u.classList.contains("show")&&y.click()},r=()=>{c&&(c.remove(),c=null),document.body.classList.remove("dm-drawer-open")},a=()=>{c||(c=document.createElement("div"),c.className="dm-navbar-backdrop",c.addEventListener("click",h),document.body.appendChild(c),document.body.classList.add("dm-drawer-open"))};y.addEventListener("click",()=>{requestAnimationFrame(()=>{u.classList.contains("show")?a():r()})}),u.addEventListener("click",l=>{l.target.closest("a")&&requestAnimationFrame(r)}),document.addEventListener("keydown",l=>{l.key==="Escape"&&u.classList.contains("show")&&h()})}const m=p.querySelector(".navbar-nav");if(m){const c=Array.from(m.children);let h=0;for(const r of w.items||[])if(!(r&&r.type==="separator")){if(r&&r.type==="spacer"){const a=document.createElement("li"),l=["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md";a.className=`dm-menu-spacer dm-menu-spacer--${l}`,a.setAttribute("aria-hidden","true"),m.insertBefore(a,c[h]||null);continue}h++}}const x=i(w.items||[]);p.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(c=>{const h=x[parseInt(c.dataset.index,10)];if(!h)return;const r=c.dataset.subindex!=null,a=r&&Array.isArray(h.items)?h.items[parseInt(c.dataset.subindex,10)]:h;if(!a)return;a.colour&&O(c,a.colour),a.pill&&!r&&V(c,a.pill),W(c,a);const l=M(a.badge);l&&c.appendChild(l),B(a.url,location.pathname)&&(c.classList.add("active"),c.setAttribute("aria-current","page"))})}const C=$("#site-footer");if(C.length){let e=function(a){return String(a??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},s=function(a){return e(a).replace(/'/g,"'")},p=function(a){if(a&&a.type==="separator")return'<li class="footer-sep"><hr></li>';if(a&&a.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(a.size)?a.size:"md"}" aria-hidden="true"></li>`;const l=s(a.url||"#"),d=e(a.text||""),b=Array.isArray(a.items)&&a.items.length?`<ul>${a.items.map(p).join("")}</ul>`:"",v=X(a),I=[v.hover&&` data-menu-item-hover="${s(v.hover)}"`,v.active&&` data-menu-item-active="${s(v.active)}"`].filter(Boolean).join(""),q=v.accent?` style="--dm-menu-highlight:${s(v.accent)}"`:"";return`<li><a href="${l}"${I}${q}>${d}</a>${b}</li>`},y=function(a,l){return(a||[]).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 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 i=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(i.copyright&&(n+=`<p>${e(i.copyright)}</p>`),i.primary.length&&(n+=`<nav class="footer-nav footer-nav--primary"><ul>${i.primary.map(p).join("")}</ul></nav>`),i.legal.length&&(n+=`<nav class="footer-nav footer-nav--legal"><ul>${i.legal.map(p).join("")}</ul></nav>`),g.footer){const a=Object.keys(t).filter(l=>o[l]);a.length&&(n+='<div class="footer-social">',a.forEach(l=>{const{label:d,svg:b}=t[l];n+=`<a href="${o[l]}" target="_blank" rel="noopener noreferrer" aria-label="${d}" class="footer-social-link">${b}</a>`}),n+="</div>")}n+="</div>",C.html(n);const u=[...y(i.primary,[]),...y(i.legal,[])];C.get(0).querySelectorAll(".footer-nav a").forEach((a,l)=>{const d=u[l];if(!d)return;d.colour&&O(a,d.colour);const b=M(d.badge);b&&a.appendChild(b),B(d.url,location.pathname)&&(a.classList.add("is-active"),a.setAttribute("aria-current","page"))}),N(C.get(0).querySelector(".footer-nav--primary"),{highlight:i.primaryHighlight}),N(C.get(0).querySelector(".footer-nav--legal"),{highlight:i.legalHighlight});const x=g.footer?.showMotionToggle!==!1,c=S.get("reduced_motion"),h=c!==null?!!c:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),r=C.get(0).querySelector(".footer-inner");if(x&&r){const a=document.createElement("input");a.type="checkbox",a.className="form-switch-input",a.id="dm-motion-switch",a.checked=h,a.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(a),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 f=$(".page-body");if(f.length){f.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),f.find(".tabs").each(function(){Domma.elements.tabs(this)}),f.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)}),f.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)}),f.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),f.find(".dm-progression").each(function(){const o=Array.from(this.querySelectorAll(".dm-progression-item")).map(i=>{const e={title:i.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:i.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return i.dataset.date&&(e.date=i.dataset.date),i.dataset.status&&(e.status=i.dataset.status),i.dataset.entryId&&(e._entryId=i.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",i=parseInt(o.dataset.revealDuration,10)||400,e=parseInt(o.dataset.revealStagger,10)||60,s=parseInt(o.dataset.revealDelay,10)||0,p=o.dataset.revealDirection==="rtl",y=Array.from(o.children),u={"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,x)=>{m.style.opacity="0",m.style.transform=u[n]||"",m.style.transition=`opacity ${i}ms ease, transform ${i}ms ease`;const c=p?y.length-1-x:x;m.style.transitionDelay=t==="stagger"?`${s+c*e}ms`:`${s}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const m=new IntersectionObserver(x=>{x.forEach(c=>{c.isIntersecting&&(c.target.offsetWidth,c.target.style.opacity="1",c.target.style.transform="none",m.unobserve(c.target))})},{threshold:.1});y.forEach(x=>m.observe(x))}))}),f.find(".card[data-collapsible]").each(function(){const o=this.querySelector(".card-header");o&&o.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),f.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),i=JSON.parse(n);Object.assign(o,i)}catch{}}),Object.keys(o).length>0){const t={};for(const[n,i]of Object.entries(o)){const e=i?.events?.click,{confirm:s,toast:p,alert:y,prompt:u,...m}=e||{};s||p||y||u?($(n).on("click",async function(c){if(c.preventDefault(),s&&!await E.confirm(s))return;let h=null;if(!(u&&(h=await E.prompt(u,{inputPlaceholder:m.promptPlaceholder||"",inputValue:m.promptDefault||""}),h===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),h!==null&&(m.setText&&r.text(h),m.setVal&&r.val(h),m.setAttr&&r.attr(m.setAttr,h))}m.href&&(window.location.href=m.href),p&&E.toast(p,{type:m.toastType||"success"}),y&&E.alert(y)}}),Object.keys(m).length&&(t[n]={...i,events:{...i.events,click:m}})):t[n]=i}$.setup(t)}}f.length&&D(f.get(0))}),window.wireCTAButtons=D;function D(w){w.querySelectorAll(".dm-cta-trigger").forEach(g=>{g.__ctaWired||(g.__ctaWired=!0,g.addEventListener("click",async()=>{const _=g.dataset.action,C=g.dataset.entry,A=g.dataset.confirm;let f=S.get("auth_token");if(!f){E.toast("Please log in to perform this action.",{type:"warning"});return}if(A&&!await E.confirm(A))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/${_}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify(g.dataset.scope?{entryId:C,scope:g.dataset.scope}:{entryId:C})});try{let n=await t(f);if(n.status===401){const e=S.get("auth_refresh_token");if(e){const s=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(s.ok){const{token:p}=await s.json();S.set("auth_token",p),f=p,n=await t(f)}}}const i=await n.json().catch(()=>({}));if(!n.ok)throw new Error(i.error||i.message||`Error ${n.status}`);E.toast(i.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 _=S.get("auth_token");function C(f,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 i=document.createElement("a");i.href="/login",i.textContent="sign in",n.appendChild(i),n.appendChild(document.createTextNode(" to view your entries."))}else n.textContent=f;return t.appendChild(n),t}function A(f,o){const t=document.createElement("template");t.innerHTML=o||"",f.replaceWith(t.content)}g.forEach(async f=>{const o=f.dataset.collectionAttrs;if(!o)return;if(!_){f.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=_,i=await t(n);if(i.status===401){const s=S.get("auth_refresh_token");if(s){const p=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:s})});if(p.ok){const{token:y}=await p.json();S.set("auth_token",y),n=y,i=await t(n)}}}if(!i.ok){const{error:s}=await i.json().catch(()=>({}));f.replaceChildren(C(s||"Unable to load entries.",!1));return}const{html:e}=await i.json();A(f,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{f.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(_=>{let C;try{C=JSON.parse(atob(_.dataset.payload))}catch{return}const{columns:A,rows:f,search:o,sortable:t,exportable:n,pageSize:i,empty:e,ctaConfig:s,slug:p,exportCfg:y}=C;if(!A?.length)return;if(s){const x=A.findIndex(c=>c.key==="_cta");x!==-1&&(A[x]={key:"_cta",title:"",render:(c,h)=>{const r=document.createElement("button");if(r.className=`btn btn-${s.style||"primary"} dm-cta-trigger`,r.dataset.action=s.action||"",s.scope&&(r.dataset.scope=s.scope),r.dataset.entry=h._entryId||"",s.confirm&&(r.dataset.confirm=s.confirm),s.icon){const a=document.createElement("span");a.dataset.icon=s.icon,r.appendChild(a),r.appendChild(document.createTextNode(" "))}return r.appendChild(document.createTextNode(s.label||"Run")),r}})}const u="col-table-"+Math.random().toString(36).slice(2,7),m=document.createElement("div");m.id=u,_.replaceChildren(m),T.create("#"+u,{data:f,columns:A,search:o,sortable:t,exportable:n,pageSize:i,emptyMessage:e}),s&&D(m),_._dmTable={columns:A,rows:f,slug:p,exportCfg:y,render(x){T.create("#"+u,{data:x,columns:A,search:o,sortable:t,exportable:n,pageSize:i,emptyMessage:e}),s&&D(m)}}})})(),(function(){const g=document.querySelectorAll("[data-form-inline]");if(!g.length||typeof F>"u")return;function _(t){const n=t&&t.logic;return!!(n&&(n.visibility||n.requirement))}function C(t,n){const i={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const s=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),p=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":s?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&n&&(y.span=n);const u={type:p,required:_(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...e.tooltip&&{tooltip:e.tooltip},...y}};e.minLength!==void 0&&(u.minLength=e.minLength),e.maxLength!==void 0&&(u.maxLength=e.maxLength),e.min!==void 0&&(u.min=e.min),e.max!==void 0&&(u.max=e.max),s&&(e.variant&&(u.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(u.multiple=!0),e.density&&(u.density=e.density),e.columns&&(u.columns=e.columns),e.accent&&(u.accent=e.accent),e.accentStyle&&(u.accentStyle=e.accentStyle),e.glow&&(u.glow=!0),e.glowColour&&(u.glowColour=e.glowColour),e.shadow&&(u.shadow=e.shadow),e.shadowColour&&(u.shadowColour=e.shadowColour)),i[e.name]=u}),i}function A(t){const n={};return(t||[]).forEach(i=>{if(!(!i.name||i.type==="page-break"||i.type==="spacer")&&(i.type==="select"||i.type==="multiselect")&&i.required){const e=(i.options||[])[0];e&&(n[i.name]=typeof e=="object"?e.value:e)}}),n}function f(t,n){(n||[]).forEach(i=>{if(i.type!=="date"||!i.name)return;const e=t.querySelector(`[name="${i.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function o(t,n,i){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=i?"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 i=n.fields||[],e=n.settings||{},s=Number(e.columns)===3?3:2,p=e.layout||"grid",y=i.some(r=>r.type==="page-break"),u=async r=>{if(t.dataset.triggerBlocked==="true"){const a=t._formLogicRuntime,l=a&&a.submitBlock?a.submitBlock().reasons:[];o(t,l.join(" ")||"This form cannot be submitted yet.",!0);const d=new Error("Blocked by a form trigger");throw d.formSubmitFailed=!0,d}try{const a=t.querySelector(".fb-form-honeypot"),l=a?.querySelector('input[type="text"]')||null,d=a?.querySelector('[name="_t"]')||null,b=Object.assign({},r);delete b._t,a?.dataset.hpName&&delete b[a.dataset.hpName],l!==null&&(b._hp=l.value),d!==null&&(b._t=d.value);const v=await H.post(`/api/forms/submit/${n.slug}`,b);if(v&&v.error){o(t,v.error,!0);const I=new Error(v.error);throw I.formSubmitFailed=!0,I}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(a){throw a.formSubmitFailed||o(t,a.message||"Submission failed. Please try again.",!0),a}},m=Date.now();function x(r){const a=r.querySelector("form");if(!a||a.querySelector(".fb-form-honeypot"))return;const l=document.createElement("div");l.className="fb-form-honeypot",l.setAttribute("aria-hidden","true");const d=document.createElement("input");d.name=a.querySelector('[name="website"]')?"_website":"website",l.dataset.hpName=d.name,d.type="text",d.tabIndex=-1,d.autocomplete="url",d.placeholder="https://",l.appendChild(d);const b=document.createElement("input");b.name="_t",b.type="hidden",b.value=m,l.appendChild(b),a.appendChild(l)}function c(r){if(!window.FormLogicEngine||!i.some(l=>l.logic||Array.isArray(l.triggers)&&l.triggers.length))return null;const a=new window.FormLogicEngine.FormLogicRuntime(n,t,r?{baseValues:r}:void 0);if(a.init(),t._formLogicRuntime=a,t._formLogicObserved)return a;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const l=new MutationObserver(function(d){for(const b of d)for(const v of b.removedNodes)if(v===t||v.nodeType===1&&v.contains&&v.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),l.disconnect();return}});l.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=l}return a}let h=!1;if(t.addEventListener("dm-form-end",r=>{const a=r.detail?.validation;if(a&&!a.valid){R(t,a.missing),P(a);return}h||(h=!0,u(r.detail?.values||{}).catch(()=>{}).finally(()=>{h=!1}))}),y&&F.wizard){const r=U(i,n.title),a=r.map(k=>({title:k.title,description:k.description,fields:C(k.fields,s)})),l=r.map(k=>k.fields);let d=0,b=null,v=null;const I=k=>{const L=window.FormLogicEngine;if(!L||!L.validateFields)return;const z=L.validateFields(t,l[d]||[]);z.valid||(k.preventDefault(),k.stopImmediatePropagation(),R(t,z.missing),P(z))},q=()=>{const k=l[d]||i;Q(t,e),f(t,k),j(t,k),e.honeypot!==!1&&x(t),b&&b.destroy(),b=c(v?.getStepData?v.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(L=>L.addEventListener("click",I,!0))};v=F.wizard(t,{schema:{steps:a},layout:p,columns:s,finishText:e.submitText||"Finish",onSubmit:u,onStepChange:k=>{d=k,setTimeout(q,0)}}),t.addEventListener("dm-form-trigger",k=>{if(k.detail?.type!=="goto-step"||!v?.goToStep)return;const L=Number(k.detail.action.step);!Number.isFinite(L)||L<0||L>=a.length||L!==d&&v.goToStep(L)}),Promise.resolve(v).then(q)}else if(F.render){const r=F.render(t,C(i,s),A(i),{submitText:e.submitText||"Submit",layout:p,columns:s,onSubmit:u});Promise.resolve(r).then(function(){K(t,e,p),f(t,i),j(t,i),e.honeypot!==!1&&x(t),c()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
|
@@ -25,6 +25,8 @@ import {fileURLToPath} from 'url';
|
|
|
25
25
|
import path from 'path';
|
|
26
26
|
import {deleteForm, ensureCollectionForForm, ensureFormsDir, listForms, readForm, slugify, writeForm} from '../../services/forms.js';
|
|
27
27
|
import {executeAction} from '../../services/actions.js';
|
|
28
|
+
// The same rules the browser draws the buttons by (a Cancel link must be safe).
|
|
29
|
+
import {buttonSettingsProblems, publicButtonSettings} from '../../../public/js/form-submit.mjs';
|
|
28
30
|
import {createTransport, sendFormEmail} from '../../services/email.js';
|
|
29
31
|
import {formActionFailed, formTriggerNotice} from '../../services/notification-sources.js';
|
|
30
32
|
import {
|
|
@@ -427,6 +429,8 @@ export async function formsRoutes(fastify) {
|
|
|
427
429
|
|
|
428
430
|
const now = new Date().toISOString();
|
|
429
431
|
const body = request.body || {};
|
|
432
|
+
const buttonProblems = buttonSettingsProblems(body.settings);
|
|
433
|
+
if (buttonProblems.length) return reply.status(400).send({ error: buttonProblems[0] });
|
|
430
434
|
const form = {
|
|
431
435
|
slug,
|
|
432
436
|
title: title.trim(),
|
|
@@ -499,6 +503,9 @@ export async function formsRoutes(fastify) {
|
|
|
499
503
|
try {
|
|
500
504
|
const form = await readForm(request.params.slug);
|
|
501
505
|
const { actions: _actions, ...safe } = form;
|
|
506
|
+
// A Cancel link saved before the check (or written by hand) is
|
|
507
|
+
// not handed to the page.
|
|
508
|
+
if (safe.settings) safe.settings = publicButtonSettings(safe.settings);
|
|
502
509
|
return safe;
|
|
503
510
|
} catch {
|
|
504
511
|
return reply.status(404).send({ error: 'Form not found.' });
|
|
@@ -518,6 +525,10 @@ export async function formsRoutes(fastify) {
|
|
|
518
525
|
}
|
|
519
526
|
|
|
520
527
|
const body = request.body || {};
|
|
528
|
+
// Only a Cancel link's address can do harm; refuse it rather than
|
|
529
|
+
// store something the page will not draw.
|
|
530
|
+
const buttonProblems = buttonSettingsProblems(body.settings);
|
|
531
|
+
if (buttonProblems.length) return reply.status(400).send({ error: buttonProblems[0] });
|
|
521
532
|
const updated = {
|
|
522
533
|
...existing,
|
|
523
534
|
...body,
|
package/server/services/forms.js
CHANGED
|
@@ -7,6 +7,7 @@ import fs from 'fs/promises';
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import {fileURLToPath} from 'url';
|
|
9
9
|
import * as cache from './cache/index.js';
|
|
10
|
+
import {buttonSettingsProblems} from '../../public/js/form-submit.mjs';
|
|
10
11
|
|
|
11
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
const ROOT = path.resolve(__dirname, '..', '..');
|
|
@@ -132,6 +133,10 @@ export async function createForm(data) {
|
|
|
132
133
|
throw new Error('Could not derive a valid slug from the provided title or slug.');
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
// A Cancel link to javascript: and the like (public/js/form-submit.mjs).
|
|
137
|
+
const buttonProblems = buttonSettingsProblems(settings);
|
|
138
|
+
if (buttonProblems.length) throw new Error(buttonProblems[0]);
|
|
139
|
+
|
|
135
140
|
// Throw if a form with this slug already exists
|
|
136
141
|
try {
|
|
137
142
|
await readForm(slug);
|
|
@@ -16,6 +16,7 @@ import {buildMenuNav} from './menuRender.js';
|
|
|
16
16
|
import {checkVisibility} from '../middleware/auth.js';
|
|
17
17
|
import {getRoleHierarchy, getRoleLevel} from './roles.js';
|
|
18
18
|
import {buildTextStyle} from '../../public/js/text-style.mjs';
|
|
19
|
+
import {publicButtonSettings} from '../../public/js/form-submit.mjs';
|
|
19
20
|
|
|
20
21
|
const __dirname_md = path.dirname(fileURLToPath(import.meta.url));
|
|
21
22
|
const BLOCKS_DIR = path.resolve(__dirname_md, '../../content/blocks');
|
|
@@ -2792,6 +2793,15 @@ function processInlineShortcodes(markdown) {
|
|
|
2792
2793
|
* renders its own body through processCardBlocks - a [card] inside a [box] and a
|
|
2793
2794
|
* [box] inside a [card] both work. Like [card], a [box] cannot hold another [box].
|
|
2794
2795
|
*
|
|
2796
|
+
* Colour follows the theme: a bare box is drawn from the theme's card tokens
|
|
2797
|
+
* (Domma's .card), and `color="primary|secondary|success|info|warning|danger"`
|
|
2798
|
+
* tints it from the matching --dm-* token (site.css, .dm-box-tone). A hex
|
|
2799
|
+
* colour (`color="#0f766e"`) is the author's own and is kept as given, with
|
|
2800
|
+
* black or white text - whichever reads better on it - worked out here, since
|
|
2801
|
+
* CSS cannot pick a contrasting colour yet. Anything else in `color` is ignored;
|
|
2802
|
+
* a valid one replaces `variant` (their colours would clash).
|
|
2803
|
+
* `variant="primary"` gives the primary border, as on a classic [card].
|
|
2804
|
+
*
|
|
2795
2805
|
* @param {string} markdown
|
|
2796
2806
|
* @returns {string}
|
|
2797
2807
|
*/
|
|
@@ -2805,12 +2815,71 @@ function processBoxBlocks(markdown) {
|
|
|
2805
2815
|
for (const k of ['title', 'subtitle', 'icon', 'icon-layout', 'image', 'layout']) delete attrs[k];
|
|
2806
2816
|
if (!('shadow' in attrs)) attrs.shadow = 'none';
|
|
2807
2817
|
if (!('rounded' in attrs)) attrs.rounded = 'xs';
|
|
2808
|
-
|
|
2809
|
-
|
|
2818
|
+
const tone = boxTone(attrs.color ?? attrs.colour);
|
|
2819
|
+
const extra = [];
|
|
2820
|
+
// A colour replaces the variant's own colours: the two together
|
|
2821
|
+
// would pair one's background with the other's text.
|
|
2822
|
+
if (tone) {
|
|
2823
|
+
delete attrs.variant;
|
|
2824
|
+
delete attrs.gradient;
|
|
2825
|
+
extra.push(...tone.classes);
|
|
2826
|
+
} else if (typeof attrs.variant === 'string' && attrs.variant.trim() === 'primary') {
|
|
2827
|
+
extra.push('card-primary');
|
|
2828
|
+
}
|
|
2829
|
+
attrs.class = ['dm-box', ...extra, typeof attrs.class === 'string' ? attrs.class.trim() : ''].filter(Boolean).join(' ');
|
|
2830
|
+
const html = LAYOUT_RENDERERS['no-header-footer'](attrs, processCardBlocks(restore(body)));
|
|
2831
|
+
// cardRoot writes no style attribute, so the custom colour goes on
|
|
2832
|
+
// the first tag (the box root) here.
|
|
2833
|
+
return tone?.style ? html.replace(/^<div /, `<div style="${tone.style}" `) : html;
|
|
2810
2834
|
}
|
|
2811
2835
|
));
|
|
2812
2836
|
}
|
|
2813
2837
|
|
|
2838
|
+
/** Theme colours a [box] can be tinted with - each maps to the --dm-<name> token. */
|
|
2839
|
+
const BOX_TONES = ['primary', 'secondary', 'success', 'info', 'warning', 'danger'];
|
|
2840
|
+
|
|
2841
|
+
/**
|
|
2842
|
+
* Resolve a [box] `color` attribute.
|
|
2843
|
+
*
|
|
2844
|
+
* A theme colour name gives the tint classes; a hex colour gives a custom
|
|
2845
|
+
* class plus inline custom properties for the background and a legible text
|
|
2846
|
+
* colour, plus the opposite of that text colour for shading the footer and
|
|
2847
|
+
* code away from it. Only #rgb and #rrggbb are accepted, so nothing but a
|
|
2848
|
+
* colour can reach the style attribute.
|
|
2849
|
+
*
|
|
2850
|
+
* @param {unknown} value
|
|
2851
|
+
* @returns {{classes: string[], style?: string}|null}
|
|
2852
|
+
*/
|
|
2853
|
+
function boxTone(value) {
|
|
2854
|
+
if (typeof value !== 'string') return null;
|
|
2855
|
+
const v = value.trim().toLowerCase();
|
|
2856
|
+
if (BOX_TONES.includes(v)) return {classes: ['dm-box-tone', `dm-box-${v}`]};
|
|
2857
|
+
const m = v.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/);
|
|
2858
|
+
if (!m) return null;
|
|
2859
|
+
const hex = m[1].length === 3 ? m[1].replace(/./g, '$&$&') : m[1];
|
|
2860
|
+
const fg = readableOn(hex);
|
|
2861
|
+
const shade = fg === '#ffffff' ? '#000000' : '#ffffff';
|
|
2862
|
+
return {classes: ['dm-box-custom'], style: `--cms-box-bg:#${hex};--cms-box-fg:${fg};--cms-box-shade:${shade}`};
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
/**
|
|
2866
|
+
* Black-ish or white text for a background colour, whichever has the higher
|
|
2867
|
+
* WCAG contrast ratio against it.
|
|
2868
|
+
*
|
|
2869
|
+
* @param {string} hex - six hex digits, no #
|
|
2870
|
+
* @returns {string}
|
|
2871
|
+
*/
|
|
2872
|
+
function readableOn(hex) {
|
|
2873
|
+
const lin = (c) => { const s = c / 255; return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4; };
|
|
2874
|
+
const lum = (h) => [0, 2, 4].map(i => lin(parseInt(h.slice(i, i + 2), 16)))
|
|
2875
|
+
.reduce((sum, c, i) => sum + c * [0.2126, 0.7152, 0.0722][i], 0);
|
|
2876
|
+
const dark = '111827';
|
|
2877
|
+
const bg = lum(hex);
|
|
2878
|
+
const whiteText = 1.05 / (bg + 0.05);
|
|
2879
|
+
const darkText = (bg + 0.05) / (lum(dark) + 0.05);
|
|
2880
|
+
return darkText >= whiteText ? `#${dark}` : '#ffffff';
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2814
2883
|
function processCardBlocks(markdown) {
|
|
2815
2884
|
markdown = processBoxBlocks(markdown);
|
|
2816
2885
|
const {scrubbed, restore} = scrubCodeRegions(markdown);
|
|
@@ -3863,6 +3932,8 @@ async function processFormBlocks(markdown, tagSet) {
|
|
|
3863
3932
|
// measure, so strip them exactly as GET /forms/:slug/public does - the
|
|
3864
3933
|
// browser never reads them, the submit route resolves them server-side.
|
|
3865
3934
|
const {actions: _actions, ...publicForm} = form;
|
|
3935
|
+
// A Cancel link that is not safe to draw stays out of the page source.
|
|
3936
|
+
if (publicForm.settings) publicForm.settings = publicButtonSettings(publicForm.settings);
|
|
3866
3937
|
const encoded = Buffer.from(JSON.stringify(publicForm)).toString('base64');
|
|
3867
3938
|
const extraClass = attrs.class ? ` ${escapeAttr(attrs.class)}` : '';
|
|
3868
3939
|
const idAttr = attrs.id ? ` id="${escapeAttr(attrs.id)}"` : '';
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
<link rel="stylesheet" href="/dist/domma/themes/domma-themes.css">
|
|
25
25
|
|
|
26
26
|
<!-- Site CSS -->
|
|
27
|
-
<link rel="stylesheet" href="/public/css/site.css?v=20260927-
|
|
28
|
-
<link rel="stylesheet" href="/public/css/forms.css?v=
|
|
27
|
+
<link rel="stylesheet" href="/public/css/site.css?v=20260927-link-colour">
|
|
28
|
+
<link rel="stylesheet" href="/public/css/forms.css?v=20260927-submit-btn-css">
|
|
29
29
|
<!-- Menu item hover / current-page effects. Loaded after site.css so a
|
|
30
30
|
menu's own highlight wins over the blanket link styling there. -->
|
|
31
31
|
<link rel="stylesheet" href="/public/css/menu-highlight.css?v=20260905-menu-highlight">
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
</script>
|
|
137
137
|
|
|
138
138
|
<!-- Site initialisation -->
|
|
139
|
-
<script src="/public/js/site.js?v=20260927-
|
|
139
|
+
<script src="/public/js/site.js?v=20260927-submit-btn" type="module"></script>
|
|
140
140
|
<script src="/public/js/collection-context.js?v=20260927-ctx" type="module"></script>
|
|
141
141
|
{{ctxMenusModule}}
|
|
142
142
|
|
|
@@ -150,8 +150,8 @@
|
|
|
150
150
|
<script src="/public/js/collection-browser.js?v=20260927-scope-mine"></script>
|
|
151
151
|
|
|
152
152
|
<!-- Core Forms (logic engine must load before renderer) -->
|
|
153
|
-
<script src="/public/js/form-logic-engine.js?v=20260927-
|
|
154
|
-
<script src="/public/js/forms.js?v=
|
|
153
|
+
<script src="/public/js/form-logic-engine.js?v=20260927-submit-btn"></script>
|
|
154
|
+
<script src="/public/js/forms.js?v=20260927-submit-btn" type="module"></script>
|
|
155
155
|
|
|
156
156
|
<!-- Core effects runtime -->
|
|
157
157
|
<script src="/public/js/effects.js"></script>
|