domma-cms 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/js/lib/context-menu-editor-arrange.js +1 -1
- package/admin/js/lib/site-settings-arrange.js +1 -1
- package/admin/js/templates/context-menu-editor.html +5 -0
- package/admin/js/templates/settings.html +15 -0
- package/admin/js/views/context-menu-editor.js +10 -5
- package/package.json +2 -2
- package/public/js/context-menus.js +1 -1
- package/public/js/site.js +1 -1
- package/server/routes/api/settings.js +10 -1
- package/server/services/contextMenus.js +6 -0
- package/server/services/markdown.js +30 -10
- package/server/services/renderer.js +6 -4
- package/server/templates/page.html +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Domma Tools CSS v0.51.
|
|
2
|
+
* Domma Tools CSS v0.51.7
|
|
3
3
|
* Dynamic Object Manipulation & Modeling API
|
|
4
4
|
* (c) 2026 Darryl Waterhouse & DCBW-IT
|
|
5
|
-
* Built: 2026-10-
|
|
6
|
-
* Commit:
|
|
5
|
+
* Built: 2026-10-09T12:01:14.388Z
|
|
6
|
+
* Commit: a5d495a
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/*!
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Domma Tools v0.51.
|
|
2
|
+
* Domma Tools v0.51.7
|
|
3
3
|
* Developer tools: Theme Roller & Page Roller
|
|
4
4
|
* (c) 2026 Darryl Waterhouse & DCBW-IT
|
|
5
|
-
* Built: 2026-10-
|
|
6
|
-
* Commit:
|
|
5
|
+
* Built: 2026-10-09T12:01:02.448Z
|
|
6
|
+
* Commit: a5d495a
|
|
7
7
|
*
|
|
8
8
|
* Requires: domma.min.js
|
|
9
9
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const ACTION_GROUPS=[{label:"This page",actions:{navigate:"Go to a URL",reload:"Reload the page",back:"Back",forward:"Forward",print:"Print","copy-link":"Copy link to this view","site-search":"Open site search"}},{label:"Clipboard and selection",actions:{copy:"Copy the selection",cut:"Cut",paste:"Paste","select-all":"Select all"}},{label:"Collection displays",actions:{filter:"Filter the display",search:"Search this display",sort:"Sort the display",export:"Export the display","mark-as-spam":"Mark as spam"}},{label:"This site",actions:{"open-modal":"Open a block in a modal","run-action":"Run an Action"}}],ACTION_LABELS=Object.assign({},...ACTION_GROUPS.map(e=>e.actions)),SELF_DISABLING={back:"greys out when there is no history behind the page",forward:"greys out unless you have gone back",copy:"greys out with nothing selected",cut:"greys out outside an editable field, or with nothing selected",paste:"greys out outside an editable field","site-search":"greys out on a site where search is unavailable",search:"greys out unless the click lands in a collection display",filter:"greys out unless the click lands in a collection display",sort:"greys out unless the click lands in a collection display",export:"greys out unless the click lands in a collection display"},NEEDS_TARGET={"mark-as-spam":"needs a right-click on an entry inside a collection display"},ACTION_ARG={navigate:{key:"url",label:"URL",placeholder:"/shop",required:!0},"run-action":{key:"slug",label:"Action slug",placeholder:"archive-entry",required:!0},"open-modal":{key:"block",label:"Block name",placeholder:"contact-card",required:!0},export:{key:"format",label:"Format",placeholder:"csv"},sort:{key:"field",label:"Field",placeholder:"price"},filter:{key:"field",label:"Field",placeholder:"category"},"mark-as-spam":{key:"field",label:"Flag field (optional)",placeholder:"spam"}},SLUG_RE=/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/,ACCENTS=["primary","success","danger","warning","info"];const
|
|
1
|
+
export const ACTION_GROUPS=[{label:"This page",actions:{navigate:"Go to a URL",reload:"Reload the page",back:"Back",forward:"Forward",print:"Print","copy-link":"Copy link to this view","site-search":"Open site search"}},{label:"Clipboard and selection",actions:{copy:"Copy the selection",cut:"Cut",paste:"Paste","select-all":"Select all"}},{label:"Collection displays",actions:{filter:"Filter the display",search:"Search this display",sort:"Sort the display",export:"Export the display","mark-as-spam":"Mark as spam"}},{label:"This site",actions:{"open-modal":"Open a block in a modal","run-action":"Run an Action"}}],ACTION_LABELS=Object.assign({},...ACTION_GROUPS.map(e=>e.actions)),SELF_DISABLING={back:"greys out when there is no history behind the page",forward:"greys out unless you have gone back",copy:"greys out with nothing selected",cut:"greys out outside an editable field, or with nothing selected",paste:"greys out outside an editable field","site-search":"greys out on a site where search is unavailable",search:"greys out unless the click lands in a collection display",filter:"greys out unless the click lands in a collection display",sort:"greys out unless the click lands in a collection display",export:"greys out unless the click lands in a collection display"},NEEDS_TARGET={"mark-as-spam":"needs a right-click on an entry inside a collection display"},ACTION_ARG={navigate:{key:"url",label:"URL",placeholder:"/shop",required:!0},"run-action":{key:"slug",label:"Action slug",placeholder:"archive-entry",required:!0},"open-modal":{key:"block",label:"Block name",placeholder:"contact-card",required:!0},export:{key:"format",label:"Format",placeholder:"csv"},sort:{key:"field",label:"Field",placeholder:"price"},filter:{key:"field",label:"Field",placeholder:"category"},"mark-as-spam":{key:"field",label:"Flag field (optional)",placeholder:"spam"}},SLUG_RE=/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/,ACCENTS=["primary","success","danger","warning","info"];const m=/^#[0-9a-fA-F]{6}$/,b=new Set(["*","html","body",":root","head"]);export const EDITABLE_TYPES=["item","divider","header"];const f={item:"Item",divider:"Divider",header:"Heading",checkbox:"Checkbox",radio:"Radio"},S=["accent","transition","density","radius","shadow","opacity"];export function slugify(e){return String(e||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64).replace(/-+$/,"")}export function splitList(e){return String(e||"").split(",").map(t=>t.trim()).filter(Boolean)}export function formFromMenu(e=null){const t=e||{},n=t.theme||{};return{name:String(t.name||""),slug:String(t.slug||""),selector:String(t.target?.selector||""),match:String(t.target?.match||""),exclude:String(t.target?.exclude||""),inherit:t.inherit===!1?"false":String(t.inherit||"append"),enabled:t.enabled!==!1,accent:String(n.accent||""),transition:String(n.transition||""),density:String(n.density||""),radius:String(n.radius||""),shadow:String(n.shadow||""),opacity:n.opacity===void 0||n.opacity===null?"":String(n.opacity),className:String(t.class||""),pages:(t.binding?.pages||[]).join(", "),projects:(t.binding?.projects||[]).join(", ")}}export function themeFromForm(e){const t={};for(const n of S){const i=e[n];i===""||i===null||i===void 0||(t[n]=n==="opacity"?Number(i):i)}return t}export function recordFromForm(e,t){return{slug:String(e.slug||"").trim(),name:String(e.name||"").trim(),enabled:e.enabled!==!1,target:{selector:String(e.selector||"").trim(),match:String(e.match||"").trim(),exclude:String(e.exclude||"").trim()},inherit:e.inherit==="false"?!1:e.inherit||"append",theme:themeFromForm(e),class:String(e.className||"").trim(),binding:{...splitList(e.pages).length&&{pages:splitList(e.pages)},...splitList(e.projects).length&&{projects:splitList(e.projects)}},items:Array.isArray(t)?t:[]}}export function itemProblem(e){if(!e||typeof e!="object")return"Not an item";const t=e.type||"item";if(t==="divider")return"";if(!String(e.text||"").trim())return t==="header"?"The heading needs text":"Needs text";if(t==="header"||Array.isArray(e.submenu)&&e.submenu.length)return"";const n=e.action;if(!n||!n.type)return"Needs something to do";if(!ACTION_LABELS[n.type])return`"${n.type}" is not an action this site knows`;const i=ACTION_ARG[n.type];return i?.required&&!String(n[i.key]||"").trim()?`Needs a ${i.label.toLowerCase()}`:""}export function checkMenu(e,t,{isNew:n}){const i={},r=String(e.slug||"").trim();n&&(r?(r.length>64||!SLUG_RE.test(r))&&(i.slug="Lowercase letters, numbers and hyphens; no hyphen at either end."):i.slug="A slug is required."),String(e.name||"").trim()||(i.name="A name is required.");const o=String(e.selector||"").trim();if(o?b.has(o)&&(i.selector=`"${o}" would bind the whole page and swallow every other menu.`):i.selector="Say which region it covers.",e.opacity!==""&&e.opacity!==null&&e.opacity!==void 0){const a=Number(e.opacity);(!Number.isFinite(a)||a<20||a>100)&&(i.opacity="Between 20 and 100.")}e.accent&&!ACCENTS.includes(e.accent)&&!m.test(e.accent)&&(i.accent="A preset or #rrggbb.");const c=Array.isArray(t)?t:[],l=c.map(itemProblem);return c.some(a=>!["divider","header"].includes(a?.type||"item"))?l.some(Boolean)&&(i.items=`${l.filter(Boolean).length} item${l.filter(Boolean).length===1?"":"s"} not finished.`):i.items="Add at least one item that does something - headings and dividers alone open an empty menu.",{errors:i,itemErrors:l,ok:Object.keys(i).length===0}}export function itemRow(e,t,n,i=""){const r=e?.type||"item",o=Array.isArray(e?.submenu)&&e.submenu.length?e.submenu.length:0;let c="";if(r==="item"||r==="checkbox"||r==="radio"){if(o)c=`Opens a submenu of ${o}`;else if(e?.action?.type){const a=ACTION_ARG[e.action.type],s=a?String(e.action[a.key]||"").trim():"";c=(ACTION_LABELS[e.action.type]||e.action.type)+(s?`: ${s}`:"")}}const l=SELF_DISABLING[e?.action?.type]||NEEDS_TARGET[e?.action?.type]||"";return{key:`${t}`,index:t,type:r,typeLabel:f[r]||r,text:r==="divider"?"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500":String(e?.text||"").trim()||"(no text)",icon:String(e?.icon||""),does:c,note:l,danger:!!e?.danger,problem:i,first:t===0,last:t===n-1,cls:[r==="divider"&&"is-divider",r==="header"&&"is-header",i&&"is-bad"].filter(Boolean).join(" ")}}export function previewItems(e){const t=[];for(const n of e||[]){const i=n?.type||"item";if(i==="divider"){t.push({type:"divider"});continue}if(i==="header"){t.push({type:"header",header:n.text||""});continue}if(Array.isArray(n.submenu)&&n.submenu.length){const r=previewItems(n.submenu);r.length&&t.push({label:n.text||"(no text)",icon:n.icon||void 0,danger:!!n.danger,className:n.class||void 0,submenu:r});continue}t.push({label:n.text||"(no text)",icon:n.icon||void 0,danger:!!n.danger,className:n.class||void 0})}return t}export const previewable=e=>previewItems(e).some(t=>t.label);export function moveItem(e,t,n){const i=[...e];if(t<0||t>=i.length||n<0||n>=i.length||t===n)return i;const[r]=i.splice(t,1);return i.splice(n,0,r),i}export function duplicateItem(e,t){const n=[...e];return n[t]&&n.splice(t+1,0,JSON.parse(JSON.stringify(n[t]))),n}export function removeItem(e,t){return e.filter((n,i)=>i!==t)}export function insertItem(e,t,n){const i=[...e];return i.splice(Math.max(0,Math.min(t,i.length)),0,n),i}export const newItem=(e="item")=>e==="divider"?{type:"divider"}:e==="header"?{type:"header",text:"Heading"}:{type:"item",text:"New item",action:{type:"navigate",url:""}};export function itemForm(e){const t=e?.type||"item",n=e?.action?.type||"navigate",i=ACTION_ARG[n];return{type:t,text:String(e?.text||""),icon:String(e?.icon||""),action:n,arg:i?String(e?.action?.[i.key]||""):"",danger:!!e?.danger,className:String(e?.class||"")}}export function itemFromForm(e,t={}){const n=e.type||"item",{text:i,icon:r,action:o,danger:c,type:l,class:a,...s}=t||{};if(n==="divider")return{...s,type:"divider"};const h=String(e.text||"").trim();if(n==="header")return{...s,type:"header",text:h};const d={...s,type:n,text:h};if(String(e.icon||"").trim()&&(d.icon=String(e.icon).trim()),e.danger&&(d.danger=!0),String(e.className||"").trim()&&(d.class=String(e.className).trim()),!(Array.isArray(s.submenu)&&s.submenu.length)){const u=e.action||"navigate",p=t?.action?.type===u?{...t.action}:{type:u},g=ACTION_ARG[u];if(g){const y=String(e.arg||"").trim();y?p[g.key]=y:delete p[g.key]}d.action=p}return d}export function snapshot(e,t){return JSON.stringify([recordFromForm(e,t),e.slug])}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const TAB_FIELDS={general:["title","tagline","siteUrl","adminHome","spacerSize"],seo:["seoTitle","seoSeparator","seoDescription"],footer:["footerCopyright","footerMotionToggle","socialTwitter","socialFacebook","socialInstagram","socialLinkedin","socialGithub","socialYoutube"],email:["smtpHost","smtpPort","smtpUser","smtpPass","smtpSecure","smtpFromAddress","smtpFromName"],backToTop:["bttEnabled","bttSmooth","bttLabel","bttThreshold","bttPosition","bttOffset","bttBottomOffset"],cookies:["ccEnabled","ccMessage","ccAcceptAll","ccRejectAll","ccCustomize","ccSavePrefs","ccPrivacyText","ccPrivacyUrl","ccCookieText","ccCookieUrl","ccPosition","ccLayout","ccTheme","ccShowFunctional","ccShowAnalytics","ccShowMarketing","ccVersion"],breadcrumbs:["bcEnabled","bcHomeLabel","bcPosition","bcOffsetX","bcOffsetY"],css:["css"]},SECRET_FIELDS=["smtpPass"],BREADCRUMB_POSITIONS=["TL","TR","BL","BR"];const e=(t,
|
|
1
|
+
export const TAB_FIELDS={general:["title","tagline","siteUrl","adminHome","spacerSize","bodyClass"],seo:["seoTitle","seoSeparator","seoDescription"],footer:["footerCopyright","footerMotionToggle","footerClass","footerSocialClass","socialTwitter","socialFacebook","socialInstagram","socialLinkedin","socialGithub","socialYoutube"],email:["smtpHost","smtpPort","smtpUser","smtpPass","smtpSecure","smtpFromAddress","smtpFromName"],backToTop:["bttEnabled","bttSmooth","bttLabel","bttThreshold","bttPosition","bttOffset","bttBottomOffset"],cookies:["ccEnabled","ccMessage","ccAcceptAll","ccRejectAll","ccCustomize","ccSavePrefs","ccPrivacyText","ccPrivacyUrl","ccCookieText","ccCookieUrl","ccPosition","ccLayout","ccTheme","ccShowFunctional","ccShowAnalytics","ccShowMarketing","ccVersion"],breadcrumbs:["bcEnabled","bcHomeLabel","bcPosition","bcOffsetX","bcOffsetY"],css:["css"]},SECRET_FIELDS=["smtpPass"],BREADCRUMB_POSITIONS=["TL","TR","BL","BR"];const e=(t,s="")=>t==null?s:String(t),i=(t,s)=>{const c=Number.parseInt(t,10);return Number.isFinite(c)?c:s};export function homeValue(t){return/^\/[A-Za-z0-9\-_/]*$/.test(t||"")&&String(t).replace(/\/+$/,"")||"/"}export function formFromSettings(t={},s=""){const c=t.backToTop||{},o=t.cookieConsent||{},r=t.breadcrumbs||{};return{title:e(t.title),tagline:e(t.tagline),siteUrl:e(t.baseUrl),adminHome:homeValue(t.adminHome),spacerSize:e(t.layoutOptions?.spacerSize??40),bodyClass:e(t.bodyClass),seoTitle:e(t.seo?.defaultTitle),seoSeparator:e(t.seo?.titleSeparator," | "),seoDescription:e(t.seo?.defaultDescription),footerCopyright:e(t.footer?.copyright),footerMotionToggle:t.footer?.showMotionToggle!==!1,footerClass:e(t.footer?.class),footerSocialClass:e(t.footer?.socialClass),socialTwitter:e(t.social?.twitter),socialFacebook:e(t.social?.facebook),socialInstagram:e(t.social?.instagram),socialLinkedin:e(t.social?.linkedin),socialGithub:e(t.social?.github),socialYoutube:e(t.social?.youtube),smtpHost:e(t.smtp?.host),smtpPort:e(t.smtp?.port||587),smtpUser:e(t.smtp?.user),smtpPass:"",smtpSecure:t.smtp?.secure===!0,smtpFromAddress:e(t.smtp?.fromAddress),smtpFromName:e(t.smtp?.fromName),bttEnabled:c.enabled!==!1,bttSmooth:c.smooth!==!1,bttLabel:e(c.label),bttThreshold:e(c.scrollThreshold??300),bttPosition:e(c.position,"bottom-right"),bttOffset:e(c.offset??32),bttBottomOffset:e(c.bottomOffset??c.offset??32),ccEnabled:o.enabled!==!1,ccMessage:e(o.message),ccAcceptAll:e(o.acceptAllText,"Accept All"),ccRejectAll:e(o.rejectAllText,"Reject All"),ccCustomize:e(o.customizeText,"Customize"),ccSavePrefs:e(o.savePreferencesText,"Save Preferences"),ccPrivacyText:e(o.privacyPolicyText,"Privacy Policy"),ccPrivacyUrl:e(o.privacyPolicyUrl),ccCookieText:e(o.cookiePolicyText,"Cookie Policy"),ccCookieUrl:e(o.cookiePolicyUrl),ccPosition:e(o.position,"bottom"),ccLayout:e(o.layout,"bar"),ccTheme:e(o.theme,"dark"),ccShowFunctional:o.showFunctional!==!1,ccShowAnalytics:o.showAnalytics!==!1,ccShowMarketing:o.showMarketing!==!1,ccVersion:e(o.consentVersion,"1.0"),bcEnabled:r.enabled===!0,bcHomeLabel:e(r.homeLabel,"Home"),bcPosition:BREADCRUMB_POSITIONS.includes(r.position)?r.position:"TL",bcOffsetX:e(r.offsetX??8),bcOffsetY:e(r.offsetY??8),css:e(s)}}export function payloadFromForm(t,s={},{adminHomeReady:c=!0}={}){const o=r=>e(r).trim();return{title:o(t.title),tagline:o(t.tagline),baseUrl:o(t.siteUrl).replace(/\/+$/,""),...c&&{adminHome:o(t.adminHome)||"/"},layoutOptions:{spacerSize:i(t.spacerSize,40)||40},bodyClass:o(t.bodyClass),seo:{defaultTitle:o(t.seoTitle),titleSeparator:e(t.seoSeparator)||" | ",defaultDescription:o(t.seoDescription)},footer:{copyright:o(t.footerCopyright),showMotionToggle:t.footerMotionToggle===!0,links:s.footer?.links||[],class:o(t.footerClass),socialClass:o(t.footerSocialClass)},social:{twitter:o(t.socialTwitter),facebook:o(t.socialFacebook),instagram:o(t.socialInstagram),linkedin:o(t.socialLinkedin),github:o(t.socialGithub),youtube:o(t.socialYoutube)},smtp:{host:o(t.smtpHost),port:i(t.smtpPort,587)||587,user:o(t.smtpUser),pass:e(t.smtpPass),secure:t.smtpSecure===!0,fromAddress:o(t.smtpFromAddress),fromName:o(t.smtpFromName)},backToTop:{enabled:t.bttEnabled===!0,scrollThreshold:i(t.bttThreshold,300),position:e(t.bttPosition)||"bottom-right",offset:i(t.bttOffset,32),bottomOffset:i(t.bttBottomOffset,32),label:o(t.bttLabel),smooth:t.bttSmooth===!0},cookieConsent:{enabled:t.ccEnabled===!0,message:o(t.ccMessage),acceptAllText:o(t.ccAcceptAll),rejectAllText:o(t.ccRejectAll),customizeText:o(t.ccCustomize),savePreferencesText:o(t.ccSavePrefs),privacyPolicyText:o(t.ccPrivacyText),privacyPolicyUrl:o(t.ccPrivacyUrl),cookiePolicyText:o(t.ccCookieText),cookiePolicyUrl:o(t.ccCookieUrl),position:e(t.ccPosition)||"bottom",layout:e(t.ccLayout)||"bar",theme:e(t.ccTheme)||"dark",showFunctional:t.ccShowFunctional===!0,showAnalytics:t.ccShowAnalytics===!0,showMarketing:t.ccShowMarketing===!0,consentVersion:o(t.ccVersion)||"1.0"},breadcrumbs:{enabled:t.bcEnabled===!0,homeLabel:o(t.bcHomeLabel)||"Home",position:BREADCRUMB_POSITIONS.includes(t.bcPosition)?t.bcPosition:"TL",offsetX:i(t.bcOffsetX,8),offsetY:i(t.bcOffsetY,8)}}}export function siteUrlError(t){const s=e(t).trim().replace(/\/+$/,"");return s?/^https?:\/\//i.test(s)?/^https?:\/\/([a-z0-9.\-]+|\[[0-9a-f:.]+\])(:\d{1,5})?$/i.test(s)?"":"Just the address, like https://example.com - no page path after it.":"Start with https:// (or http://).":""}const a=t=>typeof t=="boolean"?t:e(t);export function snapshot(t){return JSON.stringify(Object.keys(t).sort().map(s=>[s,a(t[s])]))}export function changedTabs(t,s){const c=[];for(const[o,r]of Object.entries(TAB_FIELDS))r.some(n=>a(t[n])!==a(s[n]))&&c.push(o);return c}export function draftOf(t){return Object.fromEntries(Object.entries(t).filter(([s])=>!SECRET_FIELDS.includes(s)))}export function settingsProblems(t){const s=[];return e(t.smtpHost).trim()?e(t.smtpFromAddress).trim()||s.push({tone:"warn",tab:"email",text:"No From address - mail has no sender and many servers refuse it."}):s.push({tone:"danger",tab:"email",text:"No SMTP host - mail goes to a throwaway test inbox and reaches no one (password resets, form notifications)."}),t.ccEnabled===!0&&!e(t.ccPrivacyUrl).trim()&&s.push({tone:"warn",tab:"cookies",text:"The cookie banner is on but links to no privacy policy."}),s}export function describeCacheKey(t){const s=/^page:(.*?):m\d+:role([^:]*):/.exec(String(t||""));return s?{path:s[1]||"/",role:s[2]||"anon",page:!0}:{path:String(t||""),role:"",page:!1}}export function expiresIn(t,s=Date.now()){if(t==null)return"no expiry";const c=t-s;return c<=0?"expired":c<6e4?`${Math.round(c/1e3)}s`:c<36e5?`${Math.round(c/6e4)}m`:`${Math.round(c/36e5)}h`}
|
|
@@ -171,6 +171,11 @@
|
|
|
171
171
|
<input class="form-input" id="ctx-opacity" type="number" min="20" max="100" placeholder="100" data-model="opacity.value">
|
|
172
172
|
<small class="cmx-err" data-bind-hidden="!err.value.opacity" data-bind-text="err.value.opacity"></small>
|
|
173
173
|
</div>
|
|
174
|
+
<div>
|
|
175
|
+
<label class="form-label" for="ctx-class">CSS classes
|
|
176
|
+
<span data-help-title="CSS classes" data-help="Your own class names, separated by spaces, added to the menu panel - style them in Custom CSS. Each item can carry classes of its own too, in its editor."></span></label>
|
|
177
|
+
<input class="form-input" id="ctx-class" type="text" placeholder="brand-menu" autocomplete="off" spellcheck="false" data-model="className.value">
|
|
178
|
+
</div>
|
|
174
179
|
</div>
|
|
175
180
|
</div>
|
|
176
181
|
</div>
|
|
@@ -66,6 +66,11 @@
|
|
|
66
66
|
<span data-help="The height a [spacer] shortcode gets when it is inserted from the page editor's toolbar."></span></label>
|
|
67
67
|
<input id="stx-spacer" type="number" class="form-input" min="0" max="500" placeholder="40" data-model="spacerSize.value">
|
|
68
68
|
</div>
|
|
69
|
+
<div class="stx-wide">
|
|
70
|
+
<label class="form-label" for="stx-body-class">Body CSS classes
|
|
71
|
+
<span data-help-title="Body CSS classes" data-help="Your own class names, separated by spaces, added to the body of every public page - a hook for site-wide styling in Custom CSS. Letters, digits, hyphens and underscores; no dots."></span></label>
|
|
72
|
+
<input id="stx-body-class" type="text" class="form-input" placeholder="brand-acme rounded-ui" autocomplete="off" spellcheck="false" data-model="bodyClass.value">
|
|
73
|
+
</div>
|
|
69
74
|
</div>
|
|
70
75
|
</div>
|
|
71
76
|
<div class="stx-card stx-moved">
|
|
@@ -116,6 +121,16 @@
|
|
|
116
121
|
<span data-help="An accessibility switch in the footer that lets visitors turn animations off. Turning this off only hides the switch - visitors who already chose it, and anyone whose browser asks for reduced motion, still get a calmer site."></span></label>
|
|
117
122
|
<input id="stx-motion" type="checkbox" class="form-switch" data-model="footerMotionToggle.value">
|
|
118
123
|
</div>
|
|
124
|
+
<div>
|
|
125
|
+
<label class="form-label" for="stx-footer-class">Footer CSS classes
|
|
126
|
+
<span data-help-title="Footer CSS classes" data-help="Your own class names, separated by spaces, added to the site footer. Style them in Custom CSS. The footer's menus take classes of their own in Menus."></span></label>
|
|
127
|
+
<input id="stx-footer-class" type="text" class="form-input" placeholder="footer-dark" autocomplete="off" spellcheck="false" data-model="footerClass.value">
|
|
128
|
+
</div>
|
|
129
|
+
<div>
|
|
130
|
+
<label class="form-label" for="stx-social-class">Social icon CSS classes
|
|
131
|
+
<span data-help-title="Social icon CSS classes" data-help="Your own class names, separated by spaces, added to every social media icon link in the footer."></span></label>
|
|
132
|
+
<input id="stx-social-class" type="text" class="form-input" placeholder="social-round" autocomplete="off" spellcheck="false" data-model="footerSocialClass.value">
|
|
133
|
+
</div>
|
|
119
134
|
</div>
|
|
120
135
|
</div>
|
|
121
136
|
<div class="stx-card">
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as de}from"../api.js";import{addBannerAction as je}from"../lib/plugin-chrome.js";import{helpPopovers as qe}from"../lib/help-popover.js";import{onceClosed as it,openMenuAt as Ne}from"../lib/tool-kit.js";import{makeIconInput as rt}from"/admin/js/lib/shortcode-modal.js";import{makePageInput as ct}from"/admin/js/lib/page-picker.js";import{ACCENTS as lt,ACTION_ARG as
|
|
1
|
+
import{api as de}from"../api.js";import{addBannerAction as je}from"../lib/plugin-chrome.js";import{helpPopovers as qe}from"../lib/help-popover.js";import{onceClosed as it,openMenuAt as Ne}from"../lib/tool-kit.js";import{makeIconInput as rt}from"/admin/js/lib/shortcode-modal.js";import{makePageInput as ct}from"/admin/js/lib/page-picker.js";import{ACCENTS as lt,ACTION_ARG as B,ACTION_GROUPS as dt,EDITABLE_TYPES as ut,NEEDS_TARGET as mt,SELF_DISABLING as pt,checkMenu as vt,duplicateItem as ft,formFromMenu as Re,insertItem as ht,itemForm as bt,itemFromForm as Pe,itemRow as yt,moveItem as Fe,newItem as gt,previewItems as xt,previewable as Ke,recordFromForm as kt,removeItem as W,slugify as wt,snapshot as ue,splitList as Q,themeFromForm as Et}from"../lib/context-menu-editor-arrange.js";import{fmtDateTime as St}from"../lib/dates.js";import{rowPlaceKeeper as Mt}from"../lib/menu-drag.js";import{cssClassProblems as At}from"/public/js/css-classes.mjs";const Ct=/^\/context-menus\/(new|edit\/[^/?#]+)\/?(\?.*)?$/,F=l=>`context-menu-editor.draft.${l||"new"}`,It=`
|
|
2
2
|
<div class="cmx-row" data-index="{{index}}" tabindex="0" data-bind-class="cls">
|
|
3
3
|
<span class="cmx-grip" title="Drag to reorder" aria-hidden="true"></span>
|
|
4
4
|
<span class="cmx-icon" aria-hidden="true" data-name="{{icon}}"></span>
|
|
@@ -10,7 +10,7 @@ import{api as de}from"../api.js";import{addBannerAction as je}from"../lib/plugin
|
|
|
10
10
|
<button type="button" class="cmx-down" data-bind-disabled="last" aria-label="Move down" title="Move down (Alt+\u2193)">\u2193</button>
|
|
11
11
|
</span>
|
|
12
12
|
<button type="button" class="cmx-kebab" aria-label="Actions for {{text}}" title="Actions">\u22EE</button>
|
|
13
|
-
</div>`,
|
|
13
|
+
</div>`,Tt='<span class="cmx-chip" data-bind-class="cls">{{text}}</span>',Lt="<small>{{text}}</small>";function $(l){return String(l??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function $t(l){const u=document.createElement("template");return u.innerHTML=l.trim(),u.content.firstElementChild}let X=null;function me(){if(X){try{X.destroy()}catch{}X=null}const l=document.getElementById("ctx-preview");if(l&&E.contextMenu&&E.contextMenu.registry)for(const u of E.contextMenu.registry(l))try{u.destroy()}catch{}}function pe(l,u){const c=l.querySelector(".dm-plugin-banner-name");if(!c)return;const T=c.querySelector(".dm-plugin-banner-sep");if(T){for(;T.nextSibling;)T.nextSibling.remove();c.appendChild(document.createTextNode(u))}}let w=null,He=!1;const Z=l=>String(l||"").split("?")[0].replace(/\/$/,"");function Dt(){He||!window.R?.use||(He=!0,R.use((l,u,c)=>!(Ct.test(u?.path||"")&&Z(l?.path)!==Z(u?.path))||!w?.dirty()?c():E.confirm("This context menu has unsaved changes. Leave anyway? They are kept as a draft for your next visit.").then(A=>{A?c():history.replaceState(null,"",`#${u.path}`)})),M.subscribe("router:afterChange",({to:l}={})=>{if(w&&Z(l?.path)!==w.path){const u=w;w=null,u.dispose()}}))}export const contextMenuEditorView={templateUrl:"/admin/js/templates/context-menu-editor.html",async onMount(l){if(Dt(),w){const e=w;w=null,e.dispose()}me();const u=[],c=e=>(u.push(e),e);let T=!1;const A=l.get(0),m=A.querySelector(".cmx")||A,_=()=>!T&&m.isConnected,ve=location.hash.match(/^#\/context-menus\/edit\/([^?#]+)/),p=ve?decodeURIComponent(ve[1]):null,h=!p,Be=Z(location.hash.replace(/^#/,""));let x=null;if(!h){const e=E.loader(A,{type:"dots"});try{x=await de.contextMenus.get(p)}catch(n){e.destroy(),E.toast(`Could not load "${p}": ${n.message||n}`,{type:"error"});return}if(e.destroy(),!m.isConnected)return}const D=Re(x),r={};for(const[e,n]of Object.entries(D))r[e]=M.observable(n);const U=()=>Object.fromEntries(Object.keys(r).map(e=>[e,r[e].peek()])),fe=()=>Object.fromEntries(Object.keys(r).map(e=>[e,r[e].value])),s=M.observable(Array.isArray(x?.items)?x.items:[]),z=M.observable(ue(D,s.peek())),G=M.computed(()=>ue(fe(),s.value)!==z.value),V=M.observable(!1),Y=M.observable(!1),K=M.observable([]),O=M.computed(()=>vt(fe(),s.value,{isNew:h})),_e=M.computed(()=>{const e=O.value.errors;if(Y.value)return e;const n={};for(const t of Object.keys(e))t!=="items"&&String(r[t]?.value??"").trim()&&(n[t]=e[t]);return n}),he=M.computed(()=>{const e=Y.value?O.value.errors:{};return{binding:!!(e.name||e.slug||e.selector),items:!!e.items||O.value.itemErrors.some(Boolean),appearance:!!(e.opacity||e.accent)}});let ee=!h;c(M.effect(()=>{const e=r.name.value;ee||(r.slug.value=wt(e))}));const te=M.observable([]);c(M.effect(()=>{const e=s.value,n=O.value.itemErrors;te.value=e.map((t,i)=>({...yt(t,i,e.length,n[i]||""),key:`${i}:${JSON.stringify(t)}`}))}));const b=m.querySelector("#ctx-items");c(M.effect(()=>{te.value,requestAnimationFrame(()=>{b?.isConnected&&(b.querySelectorAll(".cmx-icon[data-name]").forEach(e=>{const n=e.dataset.name;if(!n||e.dataset.drawn===n)return;e.dataset.drawn=n;const t=document.createElement("span");t.setAttribute("data-icon",n),t.setAttribute("data-icon-size","14"),e.replaceChildren(t)}),I.scan(b))})}));const Ue=M.computed(()=>{const e=[];e.push(r.enabled.value?{text:"On",cls:"is-ok"}:{text:"Off",cls:"is-warn"});const n=Q(r.pages.value).length,t=Q(r.projects.value).length;e.push({text:n||t?`${n+t} scope rule${n+t===1?"":"s"}`:"Site-wide",cls:""});const i=s.value.filter(a=>!["divider","header"].includes(a?.type||"item")).length;return e.push({text:`${i} item${i===1?"":"s"} to click`,cls:i?"":"is-danger"}),r.selector.value.trim()&&e.push({text:r.selector.value.trim(),cls:""}),e});let ne=null;function ze(){if(!_())return;me();const e=s.peek();if(!Ke(e))return;const n=Et(U());X=E.contextMenu("#ctx-preview",{cursor:"glyph",items:xt(e),inherit:!1,accent:n.accent,transition:n.transition,density:n.density,radius:n.radius,shadow:n.shadow,opacity:n.opacity})}c(M.effect(()=>{s.value,r.accent.value,r.transition.value,r.density.value,r.radius.value,r.shadow.value,r.opacity.value,clearTimeout(ne),ne=setTimeout(ze,120)})),c(()=>{clearTimeout(ne),me()});const Ge=M.computed(()=>Ke(s.value)?"Right-click here to try the menu":"Add an item to try the menu here"),ae=e=>be(e),k=e=>{s.value=e};function j(e){requestAnimationFrame(()=>b?.querySelector(`.cmx-row[data-index="${e}"]`)?.focus())}function q(e,n){n<0||n>=s.peek().length||(k(Fe(s.peek(),e,n)),j(n))}function N(e,n=s.peek().length){const t=gt(e);k(ht(s.peek(),n,t)),e!=="divider"&&be(n,{isAdd:!0})}function Ve(e){const n=s.peek(),t=n[e];if(!t)return[];const i=t.type||"item";return[{header:i==="divider"?"Divider":t.text||"(no text)"},{label:i==="divider"?"Change\u2026":"Edit\u2026",icon:"edit",action:()=>ae(e)},{type:"divider"},{label:"Move up",icon:"arrow-up",shortcut:"Alt+\u2191",disabled:e===0,action:()=>q(e,e-1)},{label:"Move down",icon:"arrow-down",shortcut:"Alt+\u2193",disabled:e===n.length-1,action:()=>q(e,e+1)},{label:"Duplicate",icon:"copy",action:()=>{k(ft(s.peek(),e)),j(e+1)}},{label:"Add below",icon:"plus",submenu:[{label:"Item",action:()=>N("item",e+1)},{label:"Heading",action:()=>N("header",e+1)},{label:"Divider",action:()=>N("divider",e+1)}]},{type:"divider"},{label:"Remove",icon:"trash",danger:!0,action:()=>{k(W(s.peek(),e)),j(Math.max(0,e-1))}}]}let v=null;function be(e,{isAdd:n=!1}={}){const t=s.peek()[e];if(!t)return;if(v)try{v.so.close(!0)}catch{}const i=bt(t),a={};for(const[o,d]of Object.entries(i))a[o]=M.observable(d);const J=()=>Object.fromEntries(Object.keys(a).map(o=>[o,a[o].peek()])),Ze=JSON.stringify(i),se=()=>JSON.stringify(J())!==Ze,Ae=!ut.includes(i.type),ie=Array.isArray(t.submenu)&&t.submenu.length,Ce={...a,locked:Ae,notLocked:!Ae,hasSub:ie,subText:ie?`Opens a submenu of ${t.submenu.length} - kept as it is (edit it in the file).`:"",isItem:M.computed(()=>a.type.value!=="divider"&&a.type.value!=="header"),hasText:M.computed(()=>a.type.value!=="divider"),showAction:M.computed(()=>a.type.value!=="divider"&&a.type.value!=="header"&&!ie),argLabel:M.computed(()=>B[a.action.value]?.label||""),note:M.computed(()=>pt[a.action.value]||mt[a.action.value]||""),problem:M.computed(()=>{const o=Pe(Object.fromEntries(Object.keys(a).map(L=>[L,a[L].value])),t),d=At(o.class)[0];return o.type==="divider"?"":String(o.text||"").trim()?d?`CSS classes: ${d}.`:B[o.action?.type]?.required&&!String(o.action?.[B[o.action.type].key]||"").trim()?`Needs a ${B[o.action.type].label.toLowerCase()}.`:"":"Needs text."})},g=E.slideover({title:n?"New item":i.type==="divider"?"Divider":`Edit ${$(i.text||"item")}`,size:"sm",position:"right"}),et=dt.map(o=>`<optgroup label="${$(o.label)}">${Object.entries(o.actions).map(([d,L])=>`<option value="${$(d)}">${$(L)}</option>`).join("")}</optgroup>`).join(""),C=$t(`
|
|
14
14
|
<div class="cmx-form">
|
|
15
15
|
<div data-bind-hidden="locked">
|
|
16
16
|
<label class="form-label" for="cmx-i-type">Type
|
|
@@ -19,7 +19,7 @@ import{api as de}from"../api.js";import{addBannerAction as je}from"../lib/plugin
|
|
|
19
19
|
<option value="item">Item</option><option value="header">Heading</option><option value="divider">Divider</option>
|
|
20
20
|
</select>
|
|
21
21
|
</div>
|
|
22
|
-
<p class="cmx-note" data-bind-hidden="notLocked">A ${
|
|
22
|
+
<p class="cmx-note" data-bind-hidden="notLocked">A ${$(i.type)} item - its text, icon and action can be changed here; its type is kept.</p>
|
|
23
23
|
<div data-bind-hidden="!hasText.value">
|
|
24
24
|
<label class="form-label" for="cmx-i-text">Text</label>
|
|
25
25
|
<input id="cmx-i-text" type="text" class="form-input" maxlength="80" data-model="text.value">
|
|
@@ -28,7 +28,7 @@ import{api as de}from"../api.js";import{addBannerAction as je}from"../lib/plugin
|
|
|
28
28
|
<label class="form-label" for="cmx-i-icon">Icon <small class="cmx-hint-inline">(optional)</small></label>
|
|
29
29
|
<div class="cmx-icon-mount"></div>
|
|
30
30
|
</div>
|
|
31
|
-
<p class="cmx-note" data-bind-hidden="!hasSub">${
|
|
31
|
+
<p class="cmx-note" data-bind-hidden="!hasSub">${$(Ce.subText)}</p>
|
|
32
32
|
<div data-bind-hidden="!showAction.value">
|
|
33
33
|
<label class="form-label" for="cmx-i-action">Does
|
|
34
34
|
<span data-help-title="What it does" data-help="A fixed set of actions, on purpose: nothing typed here runs as script on your pages. Some grey themselves out when they do not apply to the click - the note under the choice says when."></span></label>
|
|
@@ -39,6 +39,11 @@ import{api as de}from"../api.js";import{addBannerAction as je}from"../lib/plugin
|
|
|
39
39
|
<label class="form-label" for="cmx-i-arg" data-bind-text="argLabel.value">Argument</label>
|
|
40
40
|
<div class="cmx-arg-mount"></div>
|
|
41
41
|
</div>
|
|
42
|
+
<div data-bind-hidden="!isItem.value">
|
|
43
|
+
<label class="form-label" for="cmx-i-class">CSS classes <small class="cmx-hint-inline">(optional)</small>
|
|
44
|
+
<span data-help="Your own class names, separated by spaces, added to this item - style them in Custom CSS."></span></label>
|
|
45
|
+
<input id="cmx-i-class" type="text" class="form-input" placeholder="menu-cta" autocomplete="off" spellcheck="false" data-model="className.value">
|
|
46
|
+
</div>
|
|
42
47
|
<div class="cmx-setting" data-bind-hidden="!isItem.value">
|
|
43
48
|
<label class="form-label" for="cmx-i-danger">Destructive (red)
|
|
44
49
|
<span data-help="Drawn in red, for things like Delete - a warning, not a guard."></span></label>
|
|
@@ -52,4 +57,4 @@ import{api as de}from"../api.js";import{addBannerAction as je}from"../lib/plugin
|
|
|
52
57
|
<button type="button" class="btn btn-sm btn-ghost" data-on-click="cancel">Cancel</button>
|
|
53
58
|
<button type="button" class="btn btn-sm btn-primary" data-on-click="done"><span data-icon="check" data-icon-size="13"></span> Done</button>
|
|
54
59
|
</div>
|
|
55
|
-
</div>`),
|
|
60
|
+
</div>`),P=rt("edit",i.icon);P.input.id="cmx-i-icon",P.input.classList.add("form-input");const Ie=()=>{a.icon.value=P.input.value};P.input.addEventListener("input",Ie),C.querySelector(".cmx-icon-mount").appendChild(P.el);const re=C.querySelector(".cmx-arg-mount");let f=null;const ce=()=>{a.arg.value=f.value};function Te(){f?.removeEventListener("input",ce),re.replaceChildren();const o=B[a.action.peek()];if(!o){f=null;return}if(o.key==="url"){const d=ct(o.placeholder,a.arg.peek());f=d.input,re.appendChild(d.el)}else f=document.createElement("input"),f.type="text",f.className="form-input",f.placeholder=o.placeholder,f.value=a.arg.peek(),re.appendChild(f);f.id="cmx-i-arg",f.addEventListener("input",ce)}let Le=a.action.peek();const tt=M.effect(()=>{const o=a.action.value;o!==Le&&(Le=o,a.arg.value="",Te())});Te();const $e=()=>{const o=Pe(J(),t),d=[...s.peek()],L=d.indexOf(t);L!==-1&&(d[L]=o,k(d))};let H=!1,le=!1;const De={done:()=>{$e(),H=!0,g.close(),j(e)},cancel:()=>g.close(),remove:()=>{const o=s.peek().indexOf(t);o!==-1&&k(W(s.peek(),o)),H=!0,g.close()}};g.element.appendChild(C);const nt=qe(C),at=M.applyBindings(Ce,C,{methods:De}),ot=o=>{(o.ctrlKey||o.metaKey)&&o.key==="Enter"&&(o.preventDefault(),De.done())};C.addEventListener("keydown",ot);const Oe={so:g,dirty:se,apply:$e};v=Oe,it(g,()=>{tt(),at.dispose(),nt(),P.input.removeEventListener("input",Ie),f?.removeEventListener("input",ce),v===Oe&&(v=null)});const st=g.close.bind(g);g.close=async o=>{if(!H&&o!==!0&&se()){if(le)return;le=!0;const d=await E.confirm("Discard the changes to this item?");if(le=!1,!d)return}if(!H&&n&&o!==!0&&!se()){const d=s.peek().indexOf(t);d!==-1&&k(W(s.peek(),d))}return H=!0,st()},I.scan(C),g.open(),requestAnimationFrame(()=>C.querySelector(i.type==="divider"?"#cmx-i-type":"#cmx-i-text")?.focus())}c(()=>{if(v){const e=v;e.dirty()&&e.apply();try{e.so.close(!0)}catch{}}});async function oe(){if(V.peek())return;Y.value=!0,K.value=[];const e=O.peek();if(!e.ok){const t=Object.keys(e.errors).length;E.toast(`Not saved yet - ${t===1?"one thing needs":`${t} things need`} fixing (marked in red).`,{type:"warning"});const i=he.peek(),a=i.binding?0:i.items?1:i.appearance?2:-1;a>=0&&We?.activate?.(a);return}V.value=!0;const n=kt(U(),s.peek());try{if(h)await de.contextMenus.create(n);else{const t=await de.contextMenus.update(p,n);t?.items&&(s.value=t.items)}if(!_())return;z.value=ue(U(),s.peek());try{S.remove?S.remove(F(p)):S.set(F(p),null)}catch{}window.dispatchEvent(new CustomEvent("dm:sidebar-count",{detail:{url:"#/context-menus"}})),h?(E.toast("Context menu created.",{type:"success"}),location.hash="#/context-menus"):(E.toast("Context menu saved.",{type:"success"}),pe(A,r.name.peek()||p))}catch(t){K.value=String(t.message||t).split("; ").map((i,a)=>({key:String(a),text:i})),m.querySelector(".cmx-alert")?.scrollIntoView({behavior:"smooth",block:"nearest"})}finally{V.value=!1}}function Ye(){E.confirm("Throw away your unsaved changes to this menu?").then(e=>{if(!e||!_())return;const n=Re(x);for(const[t,i]of Object.entries(n))r[t].value=i;s.value=Array.isArray(x?.items)?x.items:[],K.value=[]})}m.querySelector("#ctx-items").innerHTML=It,m.querySelector(".cmx-chips").innerHTML=Tt,m.querySelector(".cmx-server-errors").innerHTML=Lt;const ye=m.querySelector("#ctx-accent");if(D.accent&&!lt.includes(D.accent)&&ye){const e=document.createElement("option");e.value=D.accent,e.textContent=`Custom (${D.accent})`,ye.appendChild(e)}const Je={...r,items:s,rows:te,chips:Ue,dirty:G,saving:V,err:_e,tabBad:he,serverErrors:K,isNew:h,notNew:!h,previewText:Ge,serverError:M.computed(()=>K.value.length>0),saveText:M.computed(()=>h?"Create menu":"Save"),problemText:M.computed(()=>Y.value&&!O.value.ok?" - some need fixing before they can be saved":""),scopeText:M.computed(()=>{const e=Q(r.pages.value),n=Q(r.projects.value);return!e.length&&!n.length?"Site-wide: every page carries this menu.":`Only ${[e.length&&`pages matching ${e.join(", ")}`,n.length&&`pages in ${n.join(", ")}`].filter(Boolean).join(", or ")}.`})},We=E.tabs(m.querySelector("#ctx-tabs"),{tabSelector:".tab-item",panelSelector:".tab-panel"}),Qe=M.applyBindings(Je,m,{methods:{save:()=>oe(),discard:()=>Ye(),addItem:()=>N("item"),addHeader:()=>N("header"),addDivider:()=>N("divider")}});c(()=>Qe.dispose()),c(qe(m,{position:"bottom"}));const ge=m.querySelector("#ctx-slug"),xe=()=>{ee=!0};ge?.addEventListener("input",xe),c(()=>ge?.removeEventListener("input",xe));const Xe=E.contextMenu(b,{cursor:"glyph",match:".cmx-row",inherit:!1,density:"compact",items:e=>Ve(Number(e.dataset.index))});c(()=>Xe?.destroy?.());const ke=e=>{const n=e.target.closest(".cmx-row");if(!n)return;const t=Number(n.dataset.index);if(e.target.closest(".cmx-kebab")){Ne(e.target.closest(".cmx-kebab"));return}if(e.target.closest(".cmx-up")){q(t,t-1);return}if(e.target.closest(".cmx-down")){q(t,t+1);return}ae(t)},we=e=>{const n=e.target.closest(".cmx-row");if(!n||e.target!==n)return;const t=Number(n.dataset.index);e.key==="F10"&&e.shiftKey?(e.preventDefault(),Ne(n)):e.key==="Enter"?(e.preventDefault(),ae(t)):e.altKey&&e.key==="ArrowUp"?(e.preventDefault(),q(t,t-1)):e.altKey&&e.key==="ArrowDown"?(e.preventDefault(),q(t,t+1)):e.key==="Delete"&&(e.preventDefault(),k(W(s.peek(),t)),j(Math.max(0,t-1)))};if(b.addEventListener("click",ke),b.addEventListener("keydown",we),c(()=>{b.removeEventListener("click",ke),b.removeEventListener("keydown",we)}),typeof E.sortable=="function"){const e=Mt(),n=E.sortable(b,{items:".cmx-row",key:null,keyboard:!1,onStart:e.start,onEnd:({item:t})=>e.restore(t),onSort:({item:t,from:i,to:a})=>{e.restore(t),k(Fe(s.peek(),i,a)),j(a)}});c(()=>n.destroy())}h?pe(A,"New menu"):pe(A,x?.name||p),je(l,{id:"ctx-editor-back",icon:"arrow-left",title:"All context menus",onClick:()=>{location.hash="#/context-menus"}}),je(l,{id:"ctx-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>oe()});const Ee=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||v||(e.preventDefault(),e.stopImmediatePropagation(),oe())};document.addEventListener("keydown",Ee,!0),c(()=>document.removeEventListener("keydown",Ee,!0));const Se=()=>{if(G.peek())try{S.set(F(p),{form:U(),items:s.peek(),base:z.peek(),at:new Date().toISOString()})}catch{}},Me=e=>{v?.dirty()&&v.apply(),G.peek()&&(Se(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",Me),c(()=>window.removeEventListener("beforeunload",Me)),w={path:Be,dirty:()=>G.peek()||!!v?.dirty(),dispose(){if(T=!0,v){const e=v;e.dirty()&&e.apply();try{e.so.close(!0)}catch{}}Se(),u.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(m);let y=null;try{y=S.get(F(p))}catch{}if(y?.form){try{S.remove?S.remove(F(p)):S.set(F(p),null)}catch{}const e=y.base&&y.base!==z.peek(),n=y.at?` (${St(y.at)})`:"",t=h?"the new context menu you started":`"${$(x?.name||p)}"`,i=e?`You left unsaved changes to ${t}${n}, but it has been saved since. Restore your version anyway?`:`You left unsaved changes to ${t}${n}. Restore them?`;if(await E.confirm(i)&&_()){for(const[a,J]of Object.entries(y.form))r[a]&&a!==(h?"":"slug")&&(r[a].value=J);h&&y.form.slug&&(ee=!0),Array.isArray(y.items)&&(s.value=y.items)}}}};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domma-cms",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/server.js",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"@fastify/rate-limit": "^10.3.0",
|
|
85
85
|
"@fastify/static": "9.1.1",
|
|
86
86
|
"bcryptjs": "^3.0.3",
|
|
87
|
-
"domma-js": "0.51.
|
|
87
|
+
"domma-js": "0.51.7",
|
|
88
88
|
"dotenv": "^17.2.3",
|
|
89
89
|
"fastify": "5.8.5",
|
|
90
90
|
"gray-matter": "^4.0.3",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(){"use strict";const d=window.__CMS_CTXMENUS__;if(!Array.isArray(d)||!d.length||typeof Domma>"u"||!Domma.elements||!Domma.elements.contextMenu)return;const s=(e,t)=>{typeof E<"u"&&E.toast&&E.toast(e,{type:t||"info"})},
|
|
1
|
+
(function(){"use strict";const d=window.__CMS_CTXMENUS__;if(!Array.isArray(d)||!d.length||typeof Domma>"u"||!Domma.elements||!Domma.elements.contextMenu)return;const s=(e,t)=>{typeof E<"u"&&E.toast&&E.toast(e,{type:t||"info"})},m=".site-search-trigger, .site-search-floating-trigger";function f(e){return e&&e.closest?e.closest("[data-ctx], [data-collection-table]"):null}function i(e){if(!e||!e.closest)return null;const t=e.closest('input, textarea, [contenteditable=""], [contenteditable="true"]');return!t||t.disabled||t.readOnly||t.tagName==="INPUT"&&!/^(text|search|url|tel|email|password|number)$/i.test(t.type)?null:t}function u(e){const t=i(e);if(t&&typeof t.selectionStart=="number")return t.value.slice(t.selectionStart,t.selectionEnd);const n=window.getSelection();return n?n.toString():""}function y(){try{const e=performance.getEntriesByType("navigation")[0];return!!e&&e.type==="back_forward"}catch{return!1}}function p(e,t){const n=e.selectionStart,r=e.selectionEnd;e.value=e.value.slice(0,n)+t+e.value.slice(r);const o=n+t.length;e.setSelectionRange(o,o),e.dispatchEvent(new Event("input",{bubbles:!0}))}const l={reload:{run:()=>window.location.reload()},back:{run:()=>window.history.back(),disabled:()=>window.history.length<=1},forward:{run:()=>window.history.forward(),disabled:()=>!y()},print:{run:()=>window.print()},copy:{run:async e=>{const t=u(e);if(t)try{await navigator.clipboard.writeText(t)}catch{s("Your browser would not let the page copy","error")}},disabled:e=>!u(e)},cut:{run:async e=>{const t=i(e);if(!t)return;const n=u(e);if(n)try{await navigator.clipboard.writeText(n),p(t,"")}catch{s("Your browser would not let the page cut","error")}},disabled:e=>!i(e)||!u(e)},paste:{run:async e=>{const t=i(e);if(t)try{const n=await navigator.clipboard.readText();n&&p(t,n)}catch{s("Your browser blocks paste from a menu - use Ctrl+V","warning")}},disabled:e=>!i(e)},"site-search":{run:()=>{if(window.DommaSiteSearch&&window.DommaSiteSearch.open){window.DommaSiteSearch.open();return}const e=document.querySelector(m);if(e){e.click();return}s("Site search is not available on this site","warning")},disabled:()=>!window.DommaSiteSearch&&!document.querySelector(m)},"select-all":{run:e=>{const t=i(e);if(t){t.select();return}const n=e&&e.closest&&e.closest("[data-ctx], .page-body")||document.body,r=document.createRange();r.selectNodeContents(n);const o=window.getSelection();o.removeAllRanges(),o.addRange(r)}}};function w(e){if(!e||!e.type)return null;if(l[e.type])return l[e.type].run;switch(e.type){case"navigate":return e.url?()=>{window.location.href=e.url}:null;case"copy-link":return async()=>{try{await navigator.clipboard.writeText(window.location.href),s("Link copied","success")}catch{s("Could not copy the link","error")}};case"run-action":return e.slug?async t=>{try{const n=await fetch(`/api/actions/${encodeURIComponent(e.slug)}/run`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({entryId:t&&t.dataset?t.dataset.entryId:void 0,url:window.location.pathname})});if(!n.ok)throw new Error(`HTTP ${n.status}`);s("Done","success")}catch(n){s(`Action failed: ${n.message}`,"error")}}:null;case"mark-as-spam":return async t=>{const n=t&&t.closest?t.closest("[data-entry-id]"):null,r=n&&n.dataset?n.dataset.entryId:"",o=t&&t.closest?t.closest("[data-ctx]"):null;let c="";try{o&&o.dataset.ctx&&(c=(JSON.parse(atob(o.dataset.ctx))||{}).slug||"")}catch{}if(!r||!c){s("Right-click an entry in a collection display to mark it","error");return}try{const a=await fetch(`/api/collections/${encodeURIComponent(c)}/entries/${encodeURIComponent(r)}/spam`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"same-origin",body:JSON.stringify({field:e.field||void 0})});if(a.status===401||a.status===403){s("You do not have permission to do that","error");return}if(!a.ok)throw new Error(`HTTP ${a.status}`);s("Marked as spam","success"),n&&n.setAttribute("data-spam","true")}catch(a){s(`Could not mark as spam: ${a.message}`,"error")}};case"open-modal":return e.block?async()=>{try{const t=await fetch(`/api/blocks/${encodeURIComponent(e.block)}/render`);if(!t.ok)throw new Error(`HTTP ${t.status}`);const{html:n}=await t.json(),r=E.modal({title:e.title||"",closeOnBackdrop:!0}),o=document.createElement("div");o.innerHTML=typeof DOMPurify<"u"?DOMPurify.sanitize(n):n,r.element.appendChild(o),r.open()}catch(t){s(`Could not open: ${t.message}`,"error")}}:null;case"search":return t=>{const n=f(t);n&&n.dispatchEvent(new MouseEvent("contextmenu",{bubbles:!0,cancelable:!0,clientX:0,clientY:0}))};case"filter":case"sort":case"export":return t=>{const n=f(t);n&&n.dispatchEvent(new CustomEvent("dm-ctx-request",{bubbles:!0,detail:{...e}}))};default:return null}}const b=new Set(["search","filter","sort","export"]);function g(e){return l[e]&&l[e].disabled?l[e].disabled:b.has(e)?t=>!f(t):null}function h(e){const t=[];for(const n of e||[]){const r=n.type||"item";if(r==="divider"){t.push({type:"divider"});continue}if(r==="header"){t.push({type:"header",header:n.text||""});continue}if(Array.isArray(n.submenu)&&n.submenu.length){const a=h(n.submenu);a.length&&t.push({label:n.text,icon:n.icon,danger:!!n.danger,className:n.class||void 0,submenu:a});continue}const o=w(n.action);if(!o)continue;const c=g(n.action&&n.action.type);t.push({label:n.text,icon:n.icon||void 0,danger:!!n.danger,className:n.class||void 0,shortcut:n.shortcut||void 0,...c?{disabled:c}:{},action:o})}return t.some(n=>n.label)?t:[]}for(const e of d){const t=h(e.items);if(!t.length)continue;const n=e.target||{};if(!n.selector)continue;const r=e.theme||{};try{Domma.elements.contextMenu(n.selector,{cursor:"glyph",match:n.match||null,exclude:n.exclude||null,inherit:e.inherit===void 0?"append":e.inherit,priority:e.priority||0,items:t,accent:r.accent,surface:r.surface,radius:r.radius,shadow:r.shadow,opacity:r.opacity,density:r.density,transition:r.transition,ariaLabel:e.name||"Context menu",className:e.class||""})}catch(o){console.warn(`[context-menus] Skipped "${e.slug}": ${o.message}`)}}})();
|
package/public/js/site.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{makeBadgeEl as O,applyColour as B,applyPill as W,applyFloat as X,applyHighlight as z,applyItemHighlight as G,itemHighlightAttrs as Y,isCurrentItem as j,menuAlign as U,applyClasses as M,cssClasses as K}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{initFloatingMenus as Q}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as R,markInvalidFields as P,showValidationPopup as V}from"/public/js/forms.js?v=20261009-form-classes";import{buildWizardSteps as Z}from"/public/js/form-steps.mjs";import{applyFormClasses as J,applySubmitButtons as ee,applyWizardButtons as te}from"/public/js/form-submit.mjs";$(()=>{const x=window.__CMS_NAV__||{},y=window.__CMS_SITE__||{};if(y.autoTheme?.enabled){let t=function(e){const i=(e||"07:00").split(":");return+i[0]*60+(+i[1]||0)},s=function(){const e=new Date,i=e.getHours()*60+e.getMinutes();return i>=t(n.dayStart)&&i<t(n.nightStart)?n.dayTheme:n.nightTheme},a=function(e){const i=n.apply&&n.apply[e]||{theme:e,custom:""};Domma.theme.set(i.theme);for(const p of[...document.body.classList])p.startsWith("dm-custom-")&&document.body.classList.remove(p);i.custom&&document.body.classList.add(i.custom)};const n=y.autoTheme;a(s()),setInterval(()=>a(s()),6e4)}const _=$("#site-navbar");if(_.length&&x.brand){let s=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:s(r.items,h+1)}:r):c},a=function(c){return Array.isArray(c)?c.filter(h=>!(h&&(h.type==="separator"||h.type==="spacer"))):[]};const n={...x.brand},t=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let c="";n.logo?c+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(c+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(c+=`<span class="navbar-brand-text${t}">${n.text}</span>`),n.tagline&&(c+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=c}else t&&n.text&&(n.html=`<span class="navbar-brand-text${t}">${n.text}</span>`);const e=x.position==="floating",i=x.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:s(x.items||[]),variant:x.variant||"dark",position:e?"static":x.position||"sticky",collapsible:!0,appearOnHover:!!x.appearOnHover}),Domma.icons.scan("#site-navbar");const p=_.get(0);p.classList.add(`dm-nav-align-${U(x)||"centre"}`),i&&_.get(0).classList.add("dm-nav-vertical",x.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),z(p,x),M(p,x.class),e&&(X(_.get(0),x),Q(_.get(0).parentNode||document));const g=p.querySelector(".navbar-toggle"),u=p.querySelector(".navbar-collapse");if(g&&u){let c=null;const h=()=>{u.classList.contains("show")&&g.click()},r=()=>{c&&(c.remove(),c=null),document.body.classList.remove("dm-drawer-open")},o=()=>{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"))};g.addEventListener("click",()=>{requestAnimationFrame(()=>{u.classList.contains("show")?o():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 x.items||[])if(!(r&&r.type==="separator")){if(r&&r.type==="spacer"){const o=document.createElement("li"),l=["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md";o.className=`dm-menu-spacer dm-menu-spacer--${l}`,o.setAttribute("aria-hidden","true"),m.insertBefore(o,c[h]||null);continue}h++}}const b=a(x.items||[]);p.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(c=>{const h=b[parseInt(c.dataset.index,10)];if(!h)return;const r=c.dataset.subindex!=null,o=r&&Array.isArray(h.items)?h.items[parseInt(c.dataset.subindex,10)]:h;if(!o)return;o.colour&&B(c,o.colour),o.pill&&!r&&W(c,o.pill),G(c,o),M(c.closest("li"),o.class);const l=O(o.badge);l&&c.appendChild(l),j(o.url,location.pathname)&&(c.classList.add("active"),c.setAttribute("aria-current","page"))})}const C=$("#site-footer");if(C.length){let e=function(o){return String(o??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},i=function(o){return e(o).replace(/'/g,"'")},p=function(o){if(o&&o.type==="separator")return'<li class="footer-sep"><hr></li>';if(o&&o.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(o.size)?o.size:"md"}" aria-hidden="true"></li>`;const l=i(o.url||"#"),d=e(o.text||""),w=Array.isArray(o.items)&&o.items.length?`<ul>${o.items.map(p).join("")}</ul>`:"",v=Y(o),D=[v.hover&&` data-menu-item-hover="${i(v.hover)}"`,v.active&&` data-menu-item-active="${i(v.active)}"`].filter(Boolean).join(""),q=v.accent?` style="--dm-menu-highlight:${i(v.accent)}"`:"",k=K(o.class).join(" ");return`<li${k?` class="${i(k)}"`:""}><a href="${l}"${D}${q}>${d}</a>${w}</li>`},g=function(o,l){return(o||[]).forEach(d=>{d&&(d.type==="separator"||d.type==="spacer")||(l.push(d),Array.isArray(d.items)&&d.items.length&&g(d.items,l))}),l};const n=y.social||{},t={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let s='<div class="footer-inner container">';const a=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(a.copyright&&(s+=`<p>${e(a.copyright)}</p>`),a.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${a.primary.map(p).join("")}</ul></nav>`),a.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${a.legal.map(p).join("")}</ul></nav>`),y.footer){const o=Object.keys(t).filter(l=>n[l]);o.length&&(s+='<div class="footer-social">',o.forEach(l=>{const{label:d,svg:w}=t[l];s+=`<a href="${n[l]}" target="_blank" rel="noopener noreferrer" aria-label="${d}" class="footer-social-link">${w}</a>`}),s+="</div>")}s+="</div>",C.html(s);const u=[...g(a.primary,[]),...g(a.legal,[])];C.get(0).querySelectorAll(".footer-nav a").forEach((o,l)=>{const d=u[l];if(!d)return;d.colour&&B(o,d.colour);const w=O(d.badge);w&&o.appendChild(w),j(d.url,location.pathname)&&(o.classList.add("is-active"),o.setAttribute("aria-current","page"))}),z(C.get(0).querySelector(".footer-nav--primary"),{highlight:a.primaryHighlight}),z(C.get(0).querySelector(".footer-nav--legal"),{highlight:a.legalHighlight}),M(C.get(0).querySelector(".footer-nav--primary"),a.primaryClass),M(C.get(0).querySelector(".footer-nav--legal"),a.legalClass);const b=y.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(b&&r){const o=document.createElement("input");o.type="checkbox",o.className="form-switch-input",o.id="dm-motion-switch",o.checked=h,o.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(o),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 n={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide"};if(this.dataset.animationDuration){const t=parseInt(this.dataset.animationDuration,10);Number.isFinite(t)&&(n.animationDuration=t)}this.dataset.animationEasing&&(n.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(t=>t.cloneNode(!0)),this._dmCarouselOpts=n),this._dmCarousel=Domma.elements.carousel(this,n)}),f.find(".dm-countdown").each(function(){const n={autoStart:!0},t=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),a=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=a?new Date(+a[1],+a[2]-1,+a[3]).getTime():s?new Date(s).getTime():NaN;Number.isFinite(e)?n.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(n.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const i=/^dd:/i.test(t);n.format=i?"hh:mm:ss":t||(n.duration>=36e5?"hh:mm:ss":"mm:ss");const p=this,g=m=>String(m).padStart(2,"0"),u=m=>{const b=Math.max(0,Math.floor(m/1e3)),c=p.querySelector(".dm-timer-time");c&&(c.textContent=`${g(Math.floor(b/86400))}:${g(Math.floor(b%86400/3600))}:${g(Math.floor(b%3600/60))}:${g(b%60)}`)};i&&(n.onTick=u),Domma.elements.timer(this,n),i&&u(n.duration||0)}),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 n=Array.from(this.querySelectorAll(".dm-progression-item")).map(a=>{const e={title:a.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:a.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return a.dataset.date&&(e.date=a.dataset.date),a.dataset.status&&(e.status=a.dataset.status),a.dataset.entryId&&(e._entryId=a.dataset.entryId),e}),t=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:t,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...n.length?{items:n}:{}});n.length&&(this._dmCtx={instance:s,items:n})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}document.querySelectorAll(".page-body .row[data-reveal]").forEach(n=>{const t=n.dataset.revealMode||"stagger",s=n.dataset.revealAnimation||"slide-up",a=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,i=parseInt(n.dataset.revealDelay,10)||0,p=n.dataset.revealDirection==="rtl",g=Array.from(n.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)"};g.forEach((m,b)=>{m.style.opacity="0",m.style.transform=u[s]||"",m.style.transition=`opacity ${a}ms ease, transform ${a}ms ease`;const c=p?g.length-1-b:b;m.style.transitionDelay=t==="stagger"?`${i+c*e}ms`:`${i}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const m=new IntersectionObserver(b=>{b.forEach(c=>{c.isIntersecting&&(c.target.offsetWidth,c.target.style.opacity="1",c.target.style.transform="none",m.unobserve(c.target))})},{threshold:.1});g.forEach(b=>m.observe(b))}))}),f.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),f.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,t=document.getElementById(n);if(!t)return;const s=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",s.setContent(t),s.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const s=atob(t.dataset.config),a=JSON.parse(s);Object.assign(n,a)}catch{}}),Object.keys(n).length>0){const t={};for(const[s,a]of Object.entries(n)){const e=a?.events?.click,{confirm:i,toast:p,alert:g,prompt:u,...m}=e||{};i||p||g||u?($(s).on("click",async function(c){if(c.preventDefault(),i&&!await E.confirm(i))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"}),g&&E.alert(g)}}),Object.keys(m).length&&(t[s]={...a,events:{...a.events,click:m}})):t[s]=a}$.setup(t)}}f.length&&I(f.get(0))}),window.wireCTAButtons=I;function I(x){x.querySelectorAll(".dm-cta-trigger").forEach(y=>{y.__ctaWired||(y.__ctaWired=!0,y.addEventListener("click",async()=>{const _=y.dataset.action,C=y.dataset.entry,A=y.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 n=Array.from(y.childNodes).map(s=>s.cloneNode(!0));y.disabled=!0,y.textContent="Running\u2026";const t=s=>fetch(`/api/actions/${_}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(y.dataset.scope?{entryId:C,scope:y.dataset.scope}:{entryId:C})});try{let s=await t(f);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const i=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(i.ok){const{token:p}=await i.json();S.set("auth_token",p),f=p,s=await t(f)}}}const a=await s.json().catch(()=>({}));if(!s.ok)throw new Error(a.error||a.message||`Error ${s.status}`);E.toast(a.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{y.disabled=!1,y.textContent="",n.forEach(s=>y.appendChild(s)),Domma.icons.scan(y)}}))})}(function(){const y=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!y.length)return;const _=S.get("auth_token");function C(f,n){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(n){s.appendChild(document.createTextNode("Please "));const a=document.createElement("a");a.href="/login",a.textContent="sign in",s.appendChild(a),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=f;return t.appendChild(s),t}function A(f,n){const t=document.createElement("template");t.innerHTML=n||"",f.replaceWith(t.content)}y.forEach(async f=>{const n=f.dataset.collectionAttrs;if(!n)return;if(!_){f.replaceChildren(C("",!0));return}const t=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:n})});try{let s=_,a=await t(s);if(a.status===401){const i=S.get("auth_refresh_token");if(i){const p=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:i})});if(p.ok){const{token:g}=await p.json();S.set("auth_token",g),s=g,a=await t(s)}}}if(!a.ok){const{error:i}=await a.json().catch(()=>({}));f.replaceChildren(C(i||"Unable to load entries.",!1));return}const{html:e}=await a.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(),I(document.body)},100)})(),(function(){const y=document.querySelectorAll("[data-collection-table]");!y.length||typeof T>"u"||!T.create||y.forEach(_=>{let C;try{C=JSON.parse(atob(_.dataset.payload))}catch{return}const{columns:A,rows:f,search:n,sortable:t,exportable:s,pageSize:a,empty:e,ctaConfig:i,slug:p,exportCfg:g}=C;if(!A?.length)return;if(i){const b=A.findIndex(c=>c.key==="_cta");b!==-1&&(A[b]={key:"_cta",title:"",render:(c,h)=>{const r=document.createElement("button");if(r.className=`btn btn-${i.style||"primary"} dm-cta-trigger`,r.dataset.action=i.action||"",i.scope&&(r.dataset.scope=i.scope),r.dataset.entry=h._entryId||"",i.confirm&&(r.dataset.confirm=i.confirm),i.icon){const o=document.createElement("span");o.dataset.icon=i.icon,r.appendChild(o),r.appendChild(document.createTextNode(" "))}return r.appendChild(document.createTextNode(i.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:n,sortable:t,exportable:s,pageSize:a,emptyMessage:e}),i&&I(m),_._dmTable={columns:A,rows:f,slug:p,exportCfg:g,render(b){T.create("#"+u,{data:b,columns:A,search:n,sortable:t,exportable:s,pageSize:a,emptyMessage:e}),i&&I(m)}}})})(),(function(){const y=document.querySelectorAll("[data-form-inline]");if(!y.length||typeof F>"u")return;function _(t){const s=t&&t.logic;return!!(s&&(s.visibility||s.requirement))}function C(t,s){const a={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const i=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":i?"chooser":e.type,g={...e.formConfig||{}};g.span==="full"&&s&&(g.span=s);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},...g}};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),i&&(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)),a[e.name]=u}),a}function A(t){const s={};return(t||[]).forEach(a=>{if(!(!a.name||a.type==="page-break"||a.type==="spacer")&&(a.type==="select"||a.type==="multiselect")&&a.required){const e=(a.options||[])[0];e&&(s[a.name]=typeof e=="object"?e.value:e)}}),s}function f(t,s){(s||[]).forEach(a=>{if(a.type!=="date"||!a.name)return;const e=t.querySelector(`[name="${a.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(t,s,a){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=s,e.style.cssText=a?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}y.forEach(t=>{let s;try{s=JSON.parse(atob(t.dataset.formInline))}catch{return}const a=s.fields||[],e=s.settings||{},i=Number(e.columns)===3?3:2,p=e.layout||"grid",g=a.some(r=>r.type==="page-break"),u=async r=>{if(t.dataset.triggerBlocked==="true"){const o=t._formLogicRuntime,l=o&&o.submitBlock?o.submitBlock().reasons:[];n(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 o=t.querySelector(".fb-form-honeypot"),l=o?.querySelector('input[type="text"]')||null,d=o?.querySelector('[name="_t"]')||null,w=Object.assign({},r);delete w._t,o?.dataset.hpName&&delete w[o.dataset.hpName],l!==null&&(w._hp=l.value),d!==null&&(w._t=d.value);const v=await H.post(`/api/forms/submit/${s.slug}`,w);if(v&&v.error){n(t,v.error,!0);const D=new Error(v.error);throw D.formSubmitFailed=!0,D}if(v?.redirect){window.location.href=v.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);n(t,v?.message||e.successMessage||"Thank you for your submission.",!1)}catch(o){throw o.formSubmitFailed||n(t,o.message||"Submission failed. Please try again.",!0),o}},m=Date.now();function b(r){const o=r.querySelector("form");if(!o||o.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=o.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 w=document.createElement("input");w.name="_t",w.type="hidden",w.value=m,l.appendChild(w),o.appendChild(l)}function c(r){if(!window.FormLogicEngine||!a.some(l=>l.logic||Array.isArray(l.triggers)&&l.triggers.length))return null;const o=new window.FormLogicEngine.FormLogicRuntime(s,t,r?{baseValues:r}:void 0);if(o.init(),t._formLogicRuntime=o,t._formLogicObserved)return o;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const l=new MutationObserver(function(d){for(const w of d)for(const v of w.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 o}let h=!1;if(t.addEventListener("dm-form-end",r=>{const o=r.detail?.validation;if(o&&!o.valid){P(t,o.missing),V(o);return}h||(h=!0,u(r.detail?.values||{}).catch(()=>{}).finally(()=>{h=!1}))}),g&&F.wizard){const r=Z(a,s.title),o=r.map(k=>({title:k.title,description:k.description,fields:C(k.fields,i)})),l=r.map(k=>k.fields);let d=0,w=null,v=null;const D=k=>{const L=window.FormLogicEngine;if(!L||!L.validateFields)return;const N=L.validateFields(t,l[d]||[]);N.valid||(k.preventDefault(),k.stopImmediatePropagation(),P(t,N.missing),V(N))},q=()=>{const k=l[d]||a;te(t,e),J(t,e,k),f(t,k),R(t,k),e.honeypot!==!1&&b(t),w&&w.destroy(),w=c(v?.getStepData?v.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(L=>L.addEventListener("click",D,!0))};v=F.wizard(t,{schema:{steps:o},layout:p,columns:i,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>=o.length||L!==d&&v.goToStep(L)}),Promise.resolve(v).then(q)}else if(F.render){const r=F.render(t,C(a,i),A(a),{submitText:e.submitText||"Submit",layout:p,columns:i,onSubmit:u});Promise.resolve(r).then(function(){ee(t,e,p),J(t,e,a),f(t,a),R(t,a),e.honeypot!==!1&&b(t),c()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
|
1
|
+
import{makeBadgeEl as O,applyColour as B,applyPill as X,applyFloat as G,applyHighlight as z,applyItemHighlight as Y,itemHighlightAttrs as U,isCurrentItem as j,menuAlign as K,applyClasses as I,cssClasses as R}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{initFloatingMenus as Q}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as P,markInvalidFields as V,showValidationPopup as J}from"/public/js/forms.js?v=20261009-form-classes";import{buildWizardSteps as Z}from"/public/js/form-steps.mjs";import{applyFormClasses as W,applySubmitButtons as ee,applyWizardButtons as te}from"/public/js/form-submit.mjs";$(()=>{const x=window.__CMS_NAV__||{},f=window.__CMS_SITE__||{};if(f.autoTheme?.enabled){let t=function(e){const i=(e||"07:00").split(":");return+i[0]*60+(+i[1]||0)},s=function(){const e=new Date,i=e.getHours()*60+e.getMinutes();return i>=t(n.dayStart)&&i<t(n.nightStart)?n.dayTheme:n.nightTheme},a=function(e){const i=n.apply&&n.apply[e]||{theme:e,custom:""};Domma.theme.set(i.theme);for(const p of[...document.body.classList])p.startsWith("dm-custom-")&&document.body.classList.remove(p);i.custom&&document.body.classList.add(i.custom)};const n=f.autoTheme;a(s()),setInterval(()=>a(s()),6e4)}const _=$("#site-navbar");if(_.length&&x.brand){let s=function(c,g=0){return Array.isArray(c)?c.filter(r=>!(r&&r.type==="spacer")).filter(r=>!(r&&r.type==="separator"&&g===0)).map(r=>r&&r.type==="separator"?{divider:!0}:Array.isArray(r?.items)?{...r,items:s(r.items,g+1)}:r):c},a=function(c){return Array.isArray(c)?c.filter(g=>!(g&&(g.type==="separator"||g.type==="spacer"))):[]};const n={...x.brand},t=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let c="";n.logo?c+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(c+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(c+=`<span class="navbar-brand-text${t}">${n.text}</span>`),n.tagline&&(c+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=c}else t&&n.text&&(n.html=`<span class="navbar-brand-text${t}">${n.text}</span>`);const e=x.position==="floating",i=x.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:s(x.items||[]),variant:x.variant||"dark",position:e?"static":x.position||"sticky",collapsible:!0,appearOnHover:!!x.appearOnHover}),Domma.icons.scan("#site-navbar");const p=_.get(0);p.classList.add(`dm-nav-align-${K(x)||"centre"}`),i&&_.get(0).classList.add("dm-nav-vertical",x.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),z(p,x),I(p,x.class),e&&(G(_.get(0),x),Q(_.get(0).parentNode||document));const y=p.querySelector(".navbar-toggle"),u=p.querySelector(".navbar-collapse");if(y&&u){let c=null;const g=()=>{u.classList.contains("show")&&y.click()},r=()=>{c&&(c.remove(),c=null),document.body.classList.remove("dm-drawer-open")},o=()=>{c||(c=document.createElement("div"),c.className="dm-navbar-backdrop",c.addEventListener("click",g),document.body.appendChild(c),document.body.classList.add("dm-drawer-open"))};y.addEventListener("click",()=>{requestAnimationFrame(()=>{u.classList.contains("show")?o():r()})}),u.addEventListener("click",d=>{d.target.closest("a")&&requestAnimationFrame(r)}),document.addEventListener("keydown",d=>{d.key==="Escape"&&u.classList.contains("show")&&g()})}const m=p.querySelector(".navbar-nav");if(m){const c=Array.from(m.children);let g=0;for(const r of x.items||[])if(!(r&&r.type==="separator")){if(r&&r.type==="spacer"){const o=document.createElement("li"),d=["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md";o.className=`dm-menu-spacer dm-menu-spacer--${d}`,o.setAttribute("aria-hidden","true"),m.insertBefore(o,c[g]||null);continue}g++}}const b=a(x.items||[]);p.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(c=>{const g=b[parseInt(c.dataset.index,10)];if(!g)return;const r=c.dataset.subindex!=null,o=r&&Array.isArray(g.items)?g.items[parseInt(c.dataset.subindex,10)]:g;if(!o)return;o.colour&&B(c,o.colour),o.pill&&!r&&X(c,o.pill),Y(c,o),I(c.closest("li"),o.class);const d=O(o.badge);d&&c.appendChild(d),j(o.url,location.pathname)&&(c.classList.add("active"),c.setAttribute("aria-current","page"))})}const C=$("#site-footer");if(C.length){let e=function(o){return String(o??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},i=function(o){return e(o).replace(/'/g,"'")},p=function(o){if(o&&o.type==="separator")return'<li class="footer-sep"><hr></li>';if(o&&o.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(o.size)?o.size:"md"}" aria-hidden="true"></li>`;const d=i(o.url||"#"),l=e(o.text||""),w=Array.isArray(o.items)&&o.items.length?`<ul>${o.items.map(p).join("")}</ul>`:"",v=U(o),D=[v.hover&&` data-menu-item-hover="${i(v.hover)}"`,v.active&&` data-menu-item-active="${i(v.active)}"`].filter(Boolean).join(""),M=v.accent?` style="--dm-menu-highlight:${i(v.accent)}"`:"",k=R(o.class).join(" ");return`<li${k?` class="${i(k)}"`:""}><a href="${d}"${D}${M}>${l}</a>${w}</li>`},y=function(o,d){return(o||[]).forEach(l=>{l&&(l.type==="separator"||l.type==="spacer")||(d.push(l),Array.isArray(l.items)&&l.items.length&&y(l.items,d))}),d};const n=f.social||{},t={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let s='<div class="footer-inner container">';const a=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(a.copyright&&(s+=`<p>${e(a.copyright)}</p>`),a.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${a.primary.map(p).join("")}</ul></nav>`),a.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${a.legal.map(p).join("")}</ul></nav>`),f.footer){const o=Object.keys(t).filter(l=>n[l]),d=R(f.footer.socialClass).join(" ");o.length&&(s+='<div class="footer-social">',o.forEach(l=>{const{label:w,svg:v}=t[l];s+=`<a href="${n[l]}" target="_blank" rel="noopener noreferrer" aria-label="${w}" class="footer-social-link${d?` ${i(d)}`:""}">${v}</a>`}),s+="</div>")}s+="</div>",C.html(s),I(C.get(0),f.footer?.class);const u=[...y(a.primary,[]),...y(a.legal,[])];C.get(0).querySelectorAll(".footer-nav a").forEach((o,d)=>{const l=u[d];if(!l)return;l.colour&&B(o,l.colour);const w=O(l.badge);w&&o.appendChild(w),j(l.url,location.pathname)&&(o.classList.add("is-active"),o.setAttribute("aria-current","page"))}),z(C.get(0).querySelector(".footer-nav--primary"),{highlight:a.primaryHighlight}),z(C.get(0).querySelector(".footer-nav--legal"),{highlight:a.legalHighlight}),I(C.get(0).querySelector(".footer-nav--primary"),a.primaryClass),I(C.get(0).querySelector(".footer-nav--legal"),a.legalClass);const b=f.footer?.showMotionToggle!==!1,c=S.get("reduced_motion"),g=c!==null?!!c:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),r=C.get(0).querySelector(".footer-inner");if(b&&r){const o=document.createElement("input");o.type="checkbox",o.className="form-switch-input",o.id="dm-motion-switch",o.checked=g,o.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const d=document.createElement("span");d.className="form-switch-label",d.textContent="Reduce motion";const l=document.createElement("label");l.className="form-switch footer-motion-switch",l.title="Reduce motion",l.appendChild(o),l.appendChild(d),r.appendChild(l)}}$("#site-sidebar").length&&Domma.elements.sidebar("#site-sidebar",{autoGenerate:!0,selector:"h2, h3",collapsible:!1,push:!0,contentSelector:".site-content"}),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const h=$(".page-body");if(h.length){h.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),h.find(".tabs").each(function(){Domma.elements.tabs(this)}),h.find(".carousel").each(function(){const n={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide"};if(this.dataset.animationDuration){const t=parseInt(this.dataset.animationDuration,10);Number.isFinite(t)&&(n.animationDuration=t)}this.dataset.animationEasing&&(n.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(t=>t.cloneNode(!0)),this._dmCarouselOpts=n),this._dmCarousel=Domma.elements.carousel(this,n)}),h.find(".dm-countdown").each(function(){const n={autoStart:!0},t=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),a=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=a?new Date(+a[1],+a[2]-1,+a[3]).getTime():s?new Date(s).getTime():NaN;Number.isFinite(e)?n.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(n.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const i=/^dd:/i.test(t);n.format=i?"hh:mm:ss":t||(n.duration>=36e5?"hh:mm:ss":"mm:ss");const p=this,y=m=>String(m).padStart(2,"0"),u=m=>{const b=Math.max(0,Math.floor(m/1e3)),c=p.querySelector(".dm-timer-time");c&&(c.textContent=`${y(Math.floor(b/86400))}:${y(Math.floor(b%86400/3600))}:${y(Math.floor(b%3600/60))}:${y(b%60)}`)};i&&(n.onTick=u),Domma.elements.timer(this,n),i&&u(n.duration||0)}),h.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),h.find(".dm-progression").each(function(){const n=Array.from(this.querySelectorAll(".dm-progression-item")).map(a=>{const e={title:a.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:a.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return a.dataset.date&&(e.date=a.dataset.date),a.dataset.status&&(e.status=a.dataset.status),a.dataset.entryId&&(e._entryId=a.dataset.entryId),e}),t=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:t,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...n.length?{items:n}:{}});n.length&&(this._dmCtx={instance:s,items:n})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}document.querySelectorAll(".page-body .row[data-reveal]").forEach(n=>{const t=n.dataset.revealMode||"stagger",s=n.dataset.revealAnimation||"slide-up",a=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,i=parseInt(n.dataset.revealDelay,10)||0,p=n.dataset.revealDirection==="rtl",y=Array.from(n.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,b)=>{m.style.opacity="0",m.style.transform=u[s]||"",m.style.transition=`opacity ${a}ms ease, transform ${a}ms ease`;const c=p?y.length-1-b:b;m.style.transitionDelay=t==="stagger"?`${i+c*e}ms`:`${i}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const m=new IntersectionObserver(b=>{b.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(b=>m.observe(b))}))}),h.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),h.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,t=document.getElementById(n);if(!t)return;const s=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",s.setContent(t),s.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const s=atob(t.dataset.config),a=JSON.parse(s);Object.assign(n,a)}catch{}}),Object.keys(n).length>0){const t={};for(const[s,a]of Object.entries(n)){const e=a?.events?.click,{confirm:i,toast:p,alert:y,prompt:u,...m}=e||{};i||p||y||u?($(s).on("click",async function(c){if(c.preventDefault(),i&&!await E.confirm(i))return;let g=null;if(!(u&&(g=await E.prompt(u,{inputPlaceholder:m.promptPlaceholder||"",inputValue:m.promptDefault||""}),g===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),g!==null&&(m.setText&&r.text(g),m.setVal&&r.val(g),m.setAttr&&r.attr(m.setAttr,g))}m.href&&(window.location.href=m.href),p&&E.toast(p,{type:m.toastType||"success"}),y&&E.alert(y)}}),Object.keys(m).length&&(t[s]={...a,events:{...a.events,click:m}})):t[s]=a}$.setup(t)}}h.length&&q(h.get(0))}),window.wireCTAButtons=q;function q(x){x.querySelectorAll(".dm-cta-trigger").forEach(f=>{f.__ctaWired||(f.__ctaWired=!0,f.addEventListener("click",async()=>{const _=f.dataset.action,C=f.dataset.entry,A=f.dataset.confirm;let h=S.get("auth_token");if(!h){E.toast("Please log in to perform this action.",{type:"warning"});return}if(A&&!await E.confirm(A))return;const n=Array.from(f.childNodes).map(s=>s.cloneNode(!0));f.disabled=!0,f.textContent="Running\u2026";const t=s=>fetch(`/api/actions/${_}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(f.dataset.scope?{entryId:C,scope:f.dataset.scope}:{entryId:C})});try{let s=await t(h);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const i=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(i.ok){const{token:p}=await i.json();S.set("auth_token",p),h=p,s=await t(h)}}}const a=await s.json().catch(()=>({}));if(!s.ok)throw new Error(a.error||a.message||`Error ${s.status}`);E.toast(a.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{f.disabled=!1,f.textContent="",n.forEach(s=>f.appendChild(s)),Domma.icons.scan(f)}}))})}(function(){const f=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!f.length)return;const _=S.get("auth_token");function C(h,n){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(n){s.appendChild(document.createTextNode("Please "));const a=document.createElement("a");a.href="/login",a.textContent="sign in",s.appendChild(a),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=h;return t.appendChild(s),t}function A(h,n){const t=document.createElement("template");t.innerHTML=n||"",h.replaceWith(t.content)}f.forEach(async h=>{const n=h.dataset.collectionAttrs;if(!n)return;if(!_){h.replaceChildren(C("",!0));return}const t=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:n})});try{let s=_,a=await t(s);if(a.status===401){const i=S.get("auth_refresh_token");if(i){const p=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:i})});if(p.ok){const{token:y}=await p.json();S.set("auth_token",y),s=y,a=await t(s)}}}if(!a.ok){const{error:i}=await a.json().catch(()=>({}));h.replaceChildren(C(i||"Unable to load entries.",!1));return}const{html:e}=await a.json();A(h,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{h.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(),q(document.body)},100)})(),(function(){const f=document.querySelectorAll("[data-collection-table]");!f.length||typeof T>"u"||!T.create||f.forEach(_=>{let C;try{C=JSON.parse(atob(_.dataset.payload))}catch{return}const{columns:A,rows:h,search:n,sortable:t,exportable:s,pageSize:a,empty:e,ctaConfig:i,slug:p,exportCfg:y}=C;if(!A?.length)return;if(i){const b=A.findIndex(c=>c.key==="_cta");b!==-1&&(A[b]={key:"_cta",title:"",render:(c,g)=>{const r=document.createElement("button");if(r.className=`btn btn-${i.style||"primary"} dm-cta-trigger`,r.dataset.action=i.action||"",i.scope&&(r.dataset.scope=i.scope),r.dataset.entry=g._entryId||"",i.confirm&&(r.dataset.confirm=i.confirm),i.icon){const o=document.createElement("span");o.dataset.icon=i.icon,r.appendChild(o),r.appendChild(document.createTextNode(" "))}return r.appendChild(document.createTextNode(i.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:h,columns:A,search:n,sortable:t,exportable:s,pageSize:a,emptyMessage:e}),i&&q(m),_._dmTable={columns:A,rows:h,slug:p,exportCfg:y,render(b){T.create("#"+u,{data:b,columns:A,search:n,sortable:t,exportable:s,pageSize:a,emptyMessage:e}),i&&q(m)}}})})(),(function(){const f=document.querySelectorAll("[data-form-inline]");if(!f.length||typeof F>"u")return;function _(t){const s=t&&t.logic;return!!(s&&(s.visibility||s.requirement))}function C(t,s){const a={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const i=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":i?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&s&&(y.span=s);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),i&&(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)),a[e.name]=u}),a}function A(t){const s={};return(t||[]).forEach(a=>{if(!(!a.name||a.type==="page-break"||a.type==="spacer")&&(a.type==="select"||a.type==="multiselect")&&a.required){const e=(a.options||[])[0];e&&(s[a.name]=typeof e=="object"?e.value:e)}}),s}function h(t,s){(s||[]).forEach(a=>{if(a.type!=="date"||!a.name)return;const e=t.querySelector(`[name="${a.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(t,s,a){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=s,e.style.cssText=a?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}f.forEach(t=>{let s;try{s=JSON.parse(atob(t.dataset.formInline))}catch{return}const a=s.fields||[],e=s.settings||{},i=Number(e.columns)===3?3:2,p=e.layout||"grid",y=a.some(r=>r.type==="page-break"),u=async r=>{if(t.dataset.triggerBlocked==="true"){const o=t._formLogicRuntime,d=o&&o.submitBlock?o.submitBlock().reasons:[];n(t,d.join(" ")||"This form cannot be submitted yet.",!0);const l=new Error("Blocked by a form trigger");throw l.formSubmitFailed=!0,l}try{const o=t.querySelector(".fb-form-honeypot"),d=o?.querySelector('input[type="text"]')||null,l=o?.querySelector('[name="_t"]')||null,w=Object.assign({},r);delete w._t,o?.dataset.hpName&&delete w[o.dataset.hpName],d!==null&&(w._hp=d.value),l!==null&&(w._t=l.value);const v=await H.post(`/api/forms/submit/${s.slug}`,w);if(v&&v.error){n(t,v.error,!0);const D=new Error(v.error);throw D.formSubmitFailed=!0,D}if(v?.redirect){window.location.href=v.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);n(t,v?.message||e.successMessage||"Thank you for your submission.",!1)}catch(o){throw o.formSubmitFailed||n(t,o.message||"Submission failed. Please try again.",!0),o}},m=Date.now();function b(r){const o=r.querySelector("form");if(!o||o.querySelector(".fb-form-honeypot"))return;const d=document.createElement("div");d.className="fb-form-honeypot",d.setAttribute("aria-hidden","true");const l=document.createElement("input");l.name=o.querySelector('[name="website"]')?"_website":"website",d.dataset.hpName=l.name,l.type="text",l.tabIndex=-1,l.autocomplete="url",l.placeholder="https://",d.appendChild(l);const w=document.createElement("input");w.name="_t",w.type="hidden",w.value=m,d.appendChild(w),o.appendChild(d)}function c(r){if(!window.FormLogicEngine||!a.some(d=>d.logic||Array.isArray(d.triggers)&&d.triggers.length))return null;const o=new window.FormLogicEngine.FormLogicRuntime(s,t,r?{baseValues:r}:void 0);if(o.init(),t._formLogicRuntime=o,t._formLogicObserved)return o;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const d=new MutationObserver(function(l){for(const w of l)for(const v of w.removedNodes)if(v===t||v.nodeType===1&&v.contains&&v.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),d.disconnect();return}});d.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=d}return o}let g=!1;if(t.addEventListener("dm-form-end",r=>{const o=r.detail?.validation;if(o&&!o.valid){V(t,o.missing),J(o);return}g||(g=!0,u(r.detail?.values||{}).catch(()=>{}).finally(()=>{g=!1}))}),y&&F.wizard){const r=Z(a,s.title),o=r.map(k=>({title:k.title,description:k.description,fields:C(k.fields,i)})),d=r.map(k=>k.fields);let l=0,w=null,v=null;const D=k=>{const L=window.FormLogicEngine;if(!L||!L.validateFields)return;const N=L.validateFields(t,d[l]||[]);N.valid||(k.preventDefault(),k.stopImmediatePropagation(),V(t,N.missing),J(N))},M=()=>{const k=d[l]||a;te(t,e),W(t,e,k),h(t,k),P(t,k),e.honeypot!==!1&&b(t),w&&w.destroy(),w=c(v?.getStepData?v.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(L=>L.addEventListener("click",D,!0))};v=F.wizard(t,{schema:{steps:o},layout:p,columns:i,finishText:e.submitText||"Finish",onSubmit:u,onStepChange:k=>{l=k,setTimeout(M,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>=o.length||L!==l&&v.goToStep(L)}),Promise.resolve(v).then(M)}else if(F.render){const r=F.render(t,C(a,i),A(a),{submitText:e.submitText||"Submit",layout:p,columns:i,onSubmit:u});Promise.resolve(r).then(function(){ee(t,e,p),W(t,e,a),h(t,a),P(t,a),e.honeypot!==!1&&b(t),c()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
|
@@ -14,6 +14,7 @@ import path from 'path';
|
|
|
14
14
|
import {fileURLToPath} from 'url';
|
|
15
15
|
import * as cache from '../../services/cache/index.js';
|
|
16
16
|
import {normaliseAdminPath} from '../../services/roles.js';
|
|
17
|
+
import {cssClassProblems} from '../../../public/js/css-classes.mjs';
|
|
17
18
|
|
|
18
19
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
20
|
const CUSTOM_CSS_FILE = path.resolve(__dirname, '../../../content/custom.css');
|
|
@@ -96,7 +97,9 @@ export async function settingsRoutes(fastify) {
|
|
|
96
97
|
// `brand` by the menus migration) and are echoed back by GET /settings.
|
|
97
98
|
// They must be accepted here or any client that round-trips the GET
|
|
98
99
|
// payload into a save gets a 400 for keys it never touched.
|
|
99
|
-
'brand', 'url', 'baseTheme'
|
|
100
|
+
'brand', 'url', 'baseTheme',
|
|
101
|
+
// Designer classes on <body>, every page (Site Settings > Appearance).
|
|
102
|
+
'bodyClass'
|
|
100
103
|
]);
|
|
101
104
|
const unknownKeys = Object.keys(data).filter(k => !ALLOWED_KEYS.has(k));
|
|
102
105
|
if (unknownKeys.length > 0) {
|
|
@@ -111,6 +114,12 @@ export async function settingsRoutes(fastify) {
|
|
|
111
114
|
}
|
|
112
115
|
data.baseUrl = origin;
|
|
113
116
|
}
|
|
117
|
+
// Designer classes - the same rules as menus and forms (css-classes.mjs).
|
|
118
|
+
for (const [label, value] of [['Body CSS classes', data.bodyClass], ['Footer CSS classes', data.footer?.class],
|
|
119
|
+
['Social link CSS classes', data.footer?.socialClass]]) {
|
|
120
|
+
const problems = cssClassProblems(value);
|
|
121
|
+
if (problems.length) return reply.status(400).send({error: `${label}: ${problems[0]}.`});
|
|
122
|
+
}
|
|
114
123
|
if ('adminHome' in data) {
|
|
115
124
|
// An admin route, or blank for the built-in dashboard.
|
|
116
125
|
const home = data.adminHome == null || data.adminHome === '' ? '/' : normaliseAdminPath(data.adminHome);
|
|
@@ -19,6 +19,7 @@ import fs from 'fs/promises';
|
|
|
19
19
|
import path from 'path';
|
|
20
20
|
import {fileURLToPath} from 'url';
|
|
21
21
|
import * as cache from './cache/index.js';
|
|
22
|
+
import {cssClassProblems} from '../../public/js/css-classes.mjs';
|
|
22
23
|
|
|
23
24
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
25
|
const CONFIG_DIR = path.resolve(__dirname, '..', '..', 'config');
|
|
@@ -154,6 +155,7 @@ export function validateContextMenu(menu) {
|
|
|
154
155
|
|
|
155
156
|
if (!isPlainObject(menu)) return ['Context menu must be an object'];
|
|
156
157
|
if (!validateSlug(menu.slug)) errors.push(`Invalid slug: ${menu.slug}`);
|
|
158
|
+
for (const p of cssClassProblems(menu.class)) errors.push(`class: ${p}`);
|
|
157
159
|
if (!menu.name || typeof menu.name !== 'string') errors.push('Name is required');
|
|
158
160
|
|
|
159
161
|
// --- target ---
|
|
@@ -249,6 +251,7 @@ function validateItems(items, errors, trail, depth = 0) {
|
|
|
249
251
|
if (!ITEM_TYPES.has(type)) { errors.push(`${at}.type "${type}" is not recognised`); return; }
|
|
250
252
|
if (type === 'divider') return;
|
|
251
253
|
if (!item.text || typeof item.text !== 'string') errors.push(`${at}.text is required`);
|
|
254
|
+
for (const p of cssClassProblems(item.class)) errors.push(`${at}.class: ${p}`);
|
|
252
255
|
if (type === 'header') return;
|
|
253
256
|
|
|
254
257
|
const hasSub = Array.isArray(item.submenu) && item.submenu.length;
|
|
@@ -292,6 +295,8 @@ function shape(input) {
|
|
|
292
295
|
inherit: input.inherit === undefined ? 'append' : input.inherit,
|
|
293
296
|
...(Number.isInteger(input.priority) && input.priority !== 0 && {priority: input.priority}),
|
|
294
297
|
...(isPlainObject(input.theme) && Object.keys(input.theme).length && {theme: input.theme}),
|
|
298
|
+
// The author's own classes on the panel; '' (sent to clear) is dropped.
|
|
299
|
+
...(typeof input.class === 'string' && input.class.trim() && {class: input.class.trim()}),
|
|
295
300
|
items: Array.isArray(input.items) ? input.items : []
|
|
296
301
|
};
|
|
297
302
|
const binding = normaliseBinding(input.binding);
|
|
@@ -472,6 +477,7 @@ export function toClientPayload(menu) {
|
|
|
472
477
|
inherit: menu.inherit === undefined ? 'append' : menu.inherit,
|
|
473
478
|
...(menu.priority && {priority: menu.priority}),
|
|
474
479
|
...(menu.theme && {theme: menu.theme}),
|
|
480
|
+
...(menu.class && {class: menu.class}),
|
|
475
481
|
items: menu.items || []
|
|
476
482
|
};
|
|
477
483
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Markdown Service
|
|
3
3
|
* Parses frontmatter with gray-matter and renders Markdown to HTML with marked.
|
|
4
4
|
*/
|
|
5
|
+
import {cssClasses} from '../../public/js/css-classes.mjs';
|
|
5
6
|
import matter from 'gray-matter';
|
|
6
7
|
import {marked} from 'marked';
|
|
7
8
|
import sanitizeHtml from 'sanitize-html';
|
|
@@ -358,6 +359,21 @@ function interpolateBlockTemplate(blockTemplate, e) {
|
|
|
358
359
|
});
|
|
359
360
|
}
|
|
360
361
|
|
|
362
|
+
/**
|
|
363
|
+
* The `item-class` attribute of [collection] / [view] - the designer's own
|
|
364
|
+
* classes for every entry's wrapper, as ` a b` ready to append inside a class
|
|
365
|
+
* attribute, or '' for none. Carried on ctxOpts because that already reaches
|
|
366
|
+
* every renderer. Table rows are drawn in the browser and do not take it.
|
|
367
|
+
*
|
|
368
|
+
* @param {object} ctxOpts
|
|
369
|
+
* @returns {string}
|
|
370
|
+
*/
|
|
371
|
+
function entryClassSuffix(ctxOpts) {
|
|
372
|
+
const list = cssClasses(ctxOpts?.itemClass);
|
|
373
|
+
return list.length ? ` ${escapeAttr(list.join(' '))}` : '';
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
|
|
361
377
|
/**
|
|
362
378
|
* Render collection entries using a reusable HTML block template.
|
|
363
379
|
* Replaces {{fieldName}}, {{_id}}, {{_createdAt}}, {{_updatedAt}} placeholders.
|
|
@@ -380,7 +396,8 @@ function renderCollectionBlocks(entries, blockTemplate, emptyMsg, ctaOpts, cols,
|
|
|
380
396
|
const blockAttr = hasCss && blockName ? ` data-block="${escapeAttr(blockName)}"` : '';
|
|
381
397
|
|
|
382
398
|
const items = entries.map(e => {
|
|
383
|
-
const
|
|
399
|
+
const entryCls = entryClassSuffix(ctxOpts).trim();
|
|
400
|
+
const wrapOpen = `<div${entryCls ? ` class="${entryCls}"` : ''}${blockAttr} data-entry-id="${escapeAttr(String(e.id ?? ''))}">`;
|
|
384
401
|
const wrapClose = `</div>`;
|
|
385
402
|
let html = interpolateBlockTemplate(blockTemplate, e);
|
|
386
403
|
|
|
@@ -621,7 +638,7 @@ function renderCollectionCards(entries, visibleFields, titleField, columns, empt
|
|
|
621
638
|
const iconHtml = ctaOpts.icon ? `<span data-icon="${escapeAttr(ctaOpts.icon)}"></span> ` : '';
|
|
622
639
|
footer = `<div class="card-footer"><button class="${btnCls}" ${btnData}>${iconHtml}${escapeHtmlText(ctaOpts.label || 'Run')}</button></div>`;
|
|
623
640
|
}
|
|
624
|
-
return `<div class="card" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${title ? `<div class="card-header">${title}</div>` : ''}<div class="card-body">${body || ' '}</div>${footer}</div>`;
|
|
641
|
+
return `<div class="card${entryClassSuffix(ctxOpts)}" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${title ? `<div class="card-header">${title}</div>` : ''}<div class="card-body">${body || ' '}</div>${footer}</div>`;
|
|
625
642
|
}).join('\n');
|
|
626
643
|
return `<div class="dm-collection-display grid ${responsiveGridCols(cols).join(' ')} gap-4"${buildCtxAttr(visibleFields, entries, ctxOpts)}>\n${cards}\n</div>`;
|
|
627
644
|
}
|
|
@@ -649,7 +666,7 @@ function renderCollectionList(entries, visibleFields, titleField, emptyMsg, ctaO
|
|
|
649
666
|
const iconHtml = ctaOpts.icon ? `<span data-icon="${escapeAttr(ctaOpts.icon)}"></span> ` : '';
|
|
650
667
|
ctaHtml = `<button class="${btnCls}" ${btnData}>${iconHtml}${escapeHtmlText(ctaOpts.label || 'Run')}</button>`;
|
|
651
668
|
}
|
|
652
|
-
return `<div class="dm-collection-list-item" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${title ? `<strong>${title}</strong>` : ''}${rest}${ctaHtml}</div>`;
|
|
669
|
+
return `<div class="dm-collection-list-item${entryClassSuffix(ctxOpts)}" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${title ? `<strong>${title}</strong>` : ''}${rest}${ctaHtml}</div>`;
|
|
653
670
|
}).join('\n');
|
|
654
671
|
return `<div class="dm-collection-display dm-collection-list"${buildCtxAttr(visibleFields, entries, ctxOpts)}>\n${items}\n</div>`;
|
|
655
672
|
}
|
|
@@ -674,7 +691,7 @@ function renderCollectionAccordion(entries, titleField, bodyField, multiple, emp
|
|
|
674
691
|
const bodyVal = String(e.data?.[bodyField] ?? '');
|
|
675
692
|
const bodyHtml = marked.parse(bodyVal);
|
|
676
693
|
return (
|
|
677
|
-
`<div class="accordion-item" data-entry-id="${escapeAttr(String(e.id ?? ''))}">\n` +
|
|
694
|
+
`<div class="accordion-item${entryClassSuffix(ctxOpts)}" data-entry-id="${escapeAttr(String(e.id ?? ''))}">\n` +
|
|
678
695
|
` <h3 class="accordion-header"><button class="accordion-button" type="button">${title}` +
|
|
679
696
|
`<span class="accordion-icon" data-icon="chevron-down"></span></button></h3>\n` +
|
|
680
697
|
` <div class="accordion-body"><div class="accordion-content">${bodyHtml}</div></div>\n` +
|
|
@@ -719,7 +736,7 @@ function renderCollectionTimeline(entries, opts, ctxOpts = {}) {
|
|
|
719
736
|
const bodyHtml = bodyField
|
|
720
737
|
? marked.parse(String(e.data?.[bodyField] ?? ''))
|
|
721
738
|
: '';
|
|
722
|
-
return `<div class="dm-progression-item" data-entry-id="${escapeAttr(String(e.id ?? ''))}"${date}${status}${icon}><div class="dm-progression-item-title">${title}</div><div class="dm-progression-item-body">${bodyHtml}</div></div>`;
|
|
739
|
+
return `<div class="dm-progression-item${entryClassSuffix(ctxOpts)}" data-entry-id="${escapeAttr(String(e.id ?? ''))}"${date}${status}${icon}><div class="dm-progression-item-title">${title}</div><div class="dm-progression-item-body">${bodyHtml}</div></div>`;
|
|
723
740
|
}).join('\n');
|
|
724
741
|
|
|
725
742
|
const ctx = buildCtxAttr(
|
|
@@ -779,7 +796,7 @@ function renderCollectionCarousel(entries, opts, ctxOpts = {}) {
|
|
|
779
796
|
: '';
|
|
780
797
|
inner = img + (title || bodyHtml ? `<div class="carousel-slide-content">${title}${bodyHtml}</div>` : '');
|
|
781
798
|
}
|
|
782
|
-
return `<div class="carousel-slide" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${inner}</div>`;
|
|
799
|
+
return `<div class="carousel-slide${entryClassSuffix(ctxOpts)}" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${inner}</div>`;
|
|
783
800
|
}).join('\n');
|
|
784
801
|
|
|
785
802
|
const ctxFields = blockTemplate
|
|
@@ -872,7 +889,7 @@ function renderCollectionListgroup(entries, opts, ctxOpts = {}) {
|
|
|
872
889
|
: '';
|
|
873
890
|
inner = (title + body) || escapeHtmlText(String(e.id ?? ''));
|
|
874
891
|
}
|
|
875
|
-
return `<div class="${itemClassStr}" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${inner}</div>`;
|
|
892
|
+
return `<div class="${itemClassStr}${entryClassSuffix(ctxOpts)}" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${inner}</div>`;
|
|
876
893
|
}).join('\n');
|
|
877
894
|
|
|
878
895
|
const ctxFields = blockTemplate
|
|
@@ -976,9 +993,10 @@ async function processViewBlocks(markdown, tagSet, user = null, flags = {}) {
|
|
|
976
993
|
const ctxOpts = sourceSchema
|
|
977
994
|
? {
|
|
978
995
|
slug: sourceSlug,
|
|
979
|
-
export: resolveExportConfig(sourceSchema.export, attrs, getRoleHierarchy().at(-1))
|
|
996
|
+
export: resolveExportConfig(sourceSchema.export, attrs, getRoleHierarchy().at(-1)),
|
|
997
|
+
itemClass: attrs['item-class']
|
|
980
998
|
}
|
|
981
|
-
: {slug: sourceSlug || undefined, export: {allowed: false, access: null, formats: []}};
|
|
999
|
+
: {slug: sourceSlug || undefined, export: {allowed: false, access: null, formats: []}, itemClass: attrs['item-class']};
|
|
982
1000
|
|
|
983
1001
|
// Derive visible fields from attr filter or first entry's data keys
|
|
984
1002
|
let fields;
|
|
@@ -1500,7 +1518,9 @@ export async function renderCollectionFragment(attrs, { extraFilter = null, scop
|
|
|
1500
1518
|
const defaultRole = getRoleHierarchy().at(-1);
|
|
1501
1519
|
const ctxOpts = {
|
|
1502
1520
|
slug: schema.slug,
|
|
1503
|
-
export: resolveExportConfig(schema.export, attrs, defaultRole)
|
|
1521
|
+
export: resolveExportConfig(schema.export, attrs, defaultRole),
|
|
1522
|
+
// Designer classes for every entry's wrapper (entryClassSuffix).
|
|
1523
|
+
itemClass: attrs['item-class']
|
|
1504
1524
|
};
|
|
1505
1525
|
|
|
1506
1526
|
const filter = buildShortcodeFilter(attrs);
|
|
@@ -16,6 +16,7 @@ import {buildCustomThemeStyleTag, buildOverrideStyleTag, getThemeTokenMap, listT
|
|
|
16
16
|
import {getSearchSettings} from './search.js';
|
|
17
17
|
import {isToolEnabled} from './tools.js';
|
|
18
18
|
import {fmtDateTime} from './dates.js';
|
|
19
|
+
import {cssClasses} from '../../public/js/css-classes.mjs';
|
|
19
20
|
|
|
20
21
|
const VALID_LAYOUT_WIDTHS = new Set(['narrow', 'normal', 'wide', 'full']);
|
|
21
22
|
const CUSTOM_CSS_PATH = new URL('../../content/custom.css', import.meta.url).pathname;
|
|
@@ -285,7 +286,8 @@ export async function renderPage(page, opts = {}) {
|
|
|
285
286
|
const navbarClass = (navigation.orientation === 'vertical' && navigation.position !== 'floating')
|
|
286
287
|
? `dm-nav-vertical dm-nav-vertical-${navigation.side === 'right' ? 'right' : 'left'}`
|
|
287
288
|
: '';
|
|
288
|
-
|
|
289
|
+
// Site Settings > Appearance > Body CSS classes - the designer's own, on every page.
|
|
290
|
+
const layoutBodyClass = ['dm-layout-' + layoutWidth, preset.class || '', verticalNavClass, ...cssClasses(site.bodyClass)].filter(Boolean).join(' ');
|
|
289
291
|
const pageBodyStyleParts = [];
|
|
290
292
|
const BG_COLOR_SAFE = /^[a-zA-Z0-9#(),.\s%/-]+$/;
|
|
291
293
|
if (preset.bgColor && BG_COLOR_SAFE.test(preset.bgColor)) pageBodyStyleParts.push(`background-color:${preset.bgColor}`);
|
|
@@ -365,7 +367,7 @@ export async function renderPage(page, opts = {}) {
|
|
|
365
367
|
? `window.__CMS_CTXMENUS__ = ${JSON.stringify(ctxMenus).replace(/<\/script>/gi, '<\\/script>')};`
|
|
366
368
|
: '';
|
|
367
369
|
const ctxMenusModule = ctxMenus.length
|
|
368
|
-
? '<script src="/public/js/context-menus.js?v=
|
|
370
|
+
? '<script src="/public/js/context-menus.js?v=20261009-classes" type="module"></script>'
|
|
369
371
|
: '';
|
|
370
372
|
|
|
371
373
|
const vars = {
|
|
@@ -1079,7 +1081,7 @@ export async function renderBlogPage(templatePath, data = {}, seoMeta = {}) {
|
|
|
1079
1081
|
? `window.__CMS_CTXMENUS__ = ${JSON.stringify(ctxMenus).replace(/<\/script>/gi, '<\\/script>')};`
|
|
1080
1082
|
: '';
|
|
1081
1083
|
const ctxMenusModule = ctxMenus.length
|
|
1082
|
-
? '<script src="/public/js/context-menus.js?v=
|
|
1084
|
+
? '<script src="/public/js/context-menus.js?v=20261009-classes" type="module"></script>'
|
|
1083
1085
|
: '';
|
|
1084
1086
|
|
|
1085
1087
|
const vars = {
|
|
@@ -1098,7 +1100,7 @@ export async function renderBlogPage(templatePath, data = {}, seoMeta = {}) {
|
|
|
1098
1100
|
layout: 'default',
|
|
1099
1101
|
layoutBodyClass: ['dm-layout-normal', navigation.orientation === 'vertical'
|
|
1100
1102
|
? `dm-vnav dm-vnav-${navigation.side === 'right' ? 'right' : 'left'}`
|
|
1101
|
-
: ''].filter(Boolean).join(' '),
|
|
1103
|
+
: '', ...cssClasses(site.bodyClass)].filter(Boolean).join(' '),
|
|
1102
1104
|
pageBodyStyle: '',
|
|
1103
1105
|
showNavbar: true,
|
|
1104
1106
|
showFooter: true,
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
</script>
|
|
138
138
|
|
|
139
139
|
<!-- Site initialisation -->
|
|
140
|
-
<script src="/public/js/site.js?v=20261009-
|
|
140
|
+
<script src="/public/js/site.js?v=20261009-more-classes" type="module"></script>
|
|
141
141
|
<script src="/public/js/collection-context.js?v=20260927-ctx" type="module"></script>
|
|
142
142
|
{{ctxMenusModule}}
|
|
143
143
|
|