domma-cms 0.37.0 → 0.38.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/js/lib/shortcode-context-menu.js +4 -4
- package/admin/js/templates/collection-entries.html +1 -0
- package/admin/js/views/collection-entries.js +1 -1
- package/admin/js/views/settings.js +1 -1
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/users.js +2 -2
- package/package.json +1 -1
- package/public/js/collection-context.js +2 -0
- package/public/js/site.js +1 -1
- package/server/routes/api/settings.js +6 -1
- package/server/routes/api/users.js +12 -5
- package/server/services/markdown.js +302 -40
- package/server/templates/page.html +2 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{SHORTCODES as T}from"./shortcode-registry.js";import{EFFECTS as g}from"./effect-defs.js";import{findEnclosingShortcode as j,setAttr as S,setAttrs as D,effectAttrKeys as W}from"./shortcode-tags.js";import{insertAtCursor as _,replaceRange as Y}from"./markdown-toolbar.js";import{api as h}from"../api.js";const k=new Map,F={forms:async()=>(await h.forms.list()).map(e=>[e.slug,e.title||e.slug]),collections:async()=>(await h.collections.list()).map(e=>[e.slug,e.title||e.slug]),views:async()=>(await h.views.list()).map(e=>[e.slug,e.title||e.slug]),blocks:async()=>(await h.blocks.list()).map(e=>{const t=e?.name??e;return[t,t]}),actions:async()=>(await h.actions.list()).map(e=>[e.slug,e.name||e.title||e.slug]),menus:async()=>(await h.menus.list()).map(e=>[e.slug,e.name||e.title||e.slug]),"menu-locations":async()=>Object.keys(await h.menuLocations.get()||{}).map(e=>[e,e]),"collection-fields":async()=>{const e=i?.span?.attrs?.slug;return!e||e===!0?[]:((await h.collections.get(e)).fields||[]).map(n=>[n.name,n.label||n.name])}};function R(e){const t=e==="collection-fields"?`${e}:${i?.span?.attrs?.slug}`:e;return k.has(t)||k.set(t,F[e]().catch(()=>[])),k.get(t)}const B=T.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),U=new Map(T.map(e=>[e.name,e])),K=["Layout","Content","Components","Data","Media","Effects"],V=new Map(g.map(e=>[e.name,e])),G=g.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),X=[...B,...G],A=new Set(["card","hero","banner","col","button"]);let i=null;const M="position:fixed;z-index:10000;min-width:210px;max-height:70vh;overflow-y:auto;background:var(--dm-surface,#1c1c30);border:1px solid var(--dm-border,#2a2a40);border-radius:6px;box-shadow:var(--dm-shadow-lg);padding:.3rem;font-size:.83rem;color:var(--dm-text,#e5e5e5);",u="display:flex;align-items:center;gap:.5rem;width:100%;text-align:left;padding:.35rem .55rem;border:none;background:transparent;color:inherit;border-radius:4px;cursor:pointer;font-size:.83rem;",L="padding:.3rem .55rem;color:var(--dm-text-muted,#8a8a9a);font-size:.68rem;text-transform:uppercase;letter-spacing:.06em;",q="flex:1;min-width:0;padding:.2rem .4rem;background:var(--dm-input-bg,#12121e);border:1px solid var(--dm-border,#333);border-radius:3px;color:var(--dm-text,#eee);font-size:.8rem;",J="height:1px;background:var(--dm-border,#2a2a40);margin:.3rem .2rem;";function m(e){return e.addEventListener("mouseenter",()=>{e.style.background="var(--dm-primary,#4338ca)",e.style.color="var(--dm-primary-text,#fff)"}),e.addEventListener("mouseleave",()=>{e.style.background="transparent",e.style.color="inherit"}),e}export function attachContextMenu(e){e.addEventListener("contextmenu",t=>{t.preventDefault(),p();const n=j(e,X);Q(e,n,t.clientX,t.clientY)})}function p(){i&&($(),i.root.remove(),document.removeEventListener("mousedown",N,!0),document.removeEventListener("keydown",O,!0),window.removeEventListener("scroll",p,!0),i=null)}const N=e=>{i&&!i.root.contains(e.target)&&!(i.submenu&&i.submenu.contains(e.target))&&p()},O=e=>{e.key==="Escape"&&p()};function Q(e,t,n,o){k.clear();const s=document.createElement("div");s.className="dm-sc-menu",s.style.cssText=M,i={root:s,textarea:e,span:t,submenu:null};const a=t&&(U.get(t.name)||V.get(t.name)),l=t&&A.has(t.name)?g.filter(r=>r.name in t.attrs):[];a&&ee(s,a);for(const r of l)ce(s,r);(a||l.length)&&s.appendChild(Z()),s.appendChild(C("Add effect\u2026",()=>re())),s.appendChild(C("Insert\u2026",()=>pe()));const c=e.selectionStart!==e.selectionEnd;(t||c)&&s.appendChild(C(c?"Wrap selection in\u2026":"Wrap in\u2026",()=>me())),document.body.appendChild(s),z(s,n,o),document.addEventListener("mousedown",N,!0),document.addEventListener("keydown",O,!0),window.addEventListener("scroll",p,!0),window.I&&I.scan(s)}function Z(){const e=document.createElement("div");return e.style.cssText=J,e}function x(e){const t=document.createElement("div");return t.style.cssText=L,t.textContent=e,t}function ee(e,t){e.appendChild(x(`${t.label} options`));for(const n of t.attrs||[])e.appendChild(P(n))}function P(e,t=e.name){const n=i.span.attrs[t];switch(e.type){case"checkbox":return oe(e,t,te(n,e.default));case"select":return ie(e,t,n);case"lookup":return le(e,t,n);case"colour":return ae(e,t,n);default:return se(e,t,n)}}function v(e,t){i.span=S(i.textarea,i.span,e,t)}function te(e,t){return e==null?!!t:!(e===!1||e==="false"||e==="0")}function ne(e,t){return t===!!e.default?null:t?"true":"false"}function oe(e,t,n){const o=m(document.createElement("label"));o.style.cssText=u;const s=document.createElement("input");return s.type="checkbox",s.checked=n,s.addEventListener("change",()=>v(t,ne(e,s.checked))),o.appendChild(s),o.appendChild(document.createTextNode(e.label)),o}function se(e,t,n){const o=document.createElement("div");o.style.cssText=u+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.cssText="white-space:nowrap;color:var(--dm-text-muted,#9a9aac);";const a=document.createElement("input");a.type=e.type==="number"?"number":"text",a.value=n===!0||n==null?"":n,e.default!=null&&e.default!==""&&(a.placeholder=String(e.default)),a.style.cssText=q;let l=null;return a.addEventListener("input",()=>{clearTimeout(l),l=setTimeout(()=>v(t,a.value.trim()),180)}),a.addEventListener("mousedown",c=>c.stopPropagation()),o.appendChild(s),o.appendChild(a),o}function ae(e,t,n){const o=document.createElement("div");o.style.cssText=u+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.flex="1";const a=document.createElement("input");return a.type="color",typeof n=="string"&&/^#/.test(n)?a.value=n:typeof e.default=="string"&&/^#/.test(e.default)&&(a.value=e.default),a.addEventListener("input",()=>v(t,a.value)),o.appendChild(s),o.appendChild(a),o}function ie(e,t,n){const o=n==null||n===!0?e.default??"":n;return C(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=w();for(const[a,l]of e.options||[]){const c=m(document.createElement("button"));c.type="button",c.style.cssText=u,c.textContent=l,a===o&&(c.style.fontWeight="700"),c.addEventListener("click",()=>{v(t,a),$()}),s.appendChild(c)}return s})}function le(e,t,n){const o=n==null||n===!0?e.default??"":n;return C(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=w();s.style.minHeight="48px";const a=typeof E<"u"&&E.loader?E.loader(s,{type:"dots"}):null;return R(e.source).then(l=>{if(a&&a.destroy(),!i||i.submenu!==s)return;if(s.style.minHeight="",!l.length){const d=document.createElement("div");d.style.cssText=L,d.textContent="Nothing found",s.appendChild(d)}const c=l.some(([d])=>d===o),r=[["","\u2014 none \u2014"],...l];o!==""&&!c&&r.push([o,`${o} (missing)`]);for(const[d,b]of r){const f=m(document.createElement("button"));f.type="button",f.style.cssText=u,f.textContent=b,d===o&&(f.style.fontWeight="700"),f.addEventListener("click",()=>{v(t,d===""?null:d),$()}),s.appendChild(f)}}),s})}function ce(e,t){e.appendChild(x(`Effect: ${t.label}`));for(const o of t.attrs||[])e.appendChild(P(o,`${t.name}-${o.name}`));const n=m(document.createElement("button"));n.type="button",n.style.cssText=u,n.innerHTML='<span data-icon="trash" data-icon-size="13" style="display:inline-flex;align-items:center;"></span> Remove effect',n.addEventListener("click",()=>{const o={};for(const s of W(i.span.attrs,t.name))o[s]=null;D(i.textarea,i.span,o),p()}),e.appendChild(n)}function re(){const e=w(),t=i.span,n=t&&A.has(t.name)?t.name:null;for(const o of[...new Set(g.map(s=>s.category))]){const s=g.filter(a=>a.category===o);if(s.length){e.appendChild(x(o));for(const a of s){const l=n&&(a.hosts||[]).includes(n)&&!(a.name in t.attrs),c=m(document.createElement("button"));if(c.type="button",c.style.cssText=u+"justify-content:space-between;",c.appendChild(document.createTextNode(a.label)),l){const r=document.createElement("span");r.textContent=`inject \u2192 [${n}]`,r.style.cssText="opacity:.6;font-size:.7rem;",c.appendChild(r)}c.addEventListener("click",()=>{if(l)S(i.textarea,i.span,a.name,!0);else{const r=a.selfClosing?`[${a.name} /]`:`[${a.name}]
|
|
1
|
+
import{SHORTCODES as A}from"./shortcode-registry.js";import{EFFECTS as _}from"./effect-defs.js";import{findEnclosingShortcode as K,setAttr as N,setAttrs as V,effectAttrKeys as G}from"./shortcode-tags.js";import{insertAtCursor as R,replaceRange as X}from"./markdown-toolbar.js";import{api as g}from"../api.js";const M=new Map,q={forms:async()=>(await g.forms.list()).map(e=>[e.slug,e.title||e.slug]),collections:async()=>(await g.collections.list()).map(e=>[e.slug,e.title||e.slug]),views:async()=>(await g.views.list()).map(e=>[e.slug,e.title||e.slug]),blocks:async()=>(await g.blocks.list()).map(e=>{const t=e?.name??e;return[t,t]}),actions:async()=>(await g.actions.list()).map(e=>[e.slug,e.name||e.title||e.slug]),menus:async()=>(await g.menus.list()).map(e=>[e.slug,e.name||e.title||e.slug]),"menu-locations":async()=>Object.keys(await g.menuLocations.get()||{}).map(e=>[e,e]),"collection-fields":async()=>{const e=a?.span?.attrs?.slug;return!e||e===!0?[]:((await g.collections.get(e)).fields||[]).map(n=>[n.name,n.label||n.name])}};function O(e){const t=e==="collection-fields"?`${e}:${a?.span?.attrs?.slug}`:e;return M.has(t)||M.set(t,q[e]().catch(()=>[])),M.get(t)}const J=A.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),Q=new Map(A.map(e=>[e.name,e])),Z=["Layout","Content","Components","Data","Media","Effects"],ee=new Map(_.map(e=>[e.name,e])),te=_.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),ne=[...J,...te],z=new Set(["card","hero","banner","col","button"]);let a=null;const H="position:fixed;z-index:10000;min-width:210px;max-height:70vh;overflow-y:auto;background:var(--dm-surface,#1c1c30);border:1px solid var(--dm-border,#2a2a40);border-radius:6px;box-shadow:var(--dm-shadow-lg);padding:.3rem;font-size:.83rem;color:var(--dm-text,#e5e5e5);",p="display:flex;align-items:center;gap:.5rem;width:100%;text-align:left;padding:.35rem .55rem;border:none;background:transparent;color:inherit;border-radius:4px;cursor:pointer;font-size:.83rem;",L="padding:.3rem .55rem;color:var(--dm-text-muted,#8a8a9a);font-size:.68rem;text-transform:uppercase;letter-spacing:.06em;",j="flex:1;min-width:0;padding:.2rem .4rem;background:var(--dm-input-bg,#12121e);border:1px solid var(--dm-border,#333);border-radius:3px;color:var(--dm-text,#eee);font-size:.8rem;",oe="height:1px;background:var(--dm-border,#2a2a40);margin:.3rem .2rem;";function m(e){return e.addEventListener("mouseenter",()=>{e.style.background="var(--dm-primary,#4338ca)",e.style.color="var(--dm-primary-text,#fff)"}),e.addEventListener("mouseleave",()=>{e.style.background="transparent",e.style.color="inherit"}),e}export function attachContextMenu(e){e.addEventListener("contextmenu",t=>{t.preventDefault(),b();const n=K(e,ne);se(e,n,t.clientX,t.clientY)})}function b(){a&&(T(),a.root.remove(),document.removeEventListener("mousedown",W,!0),document.removeEventListener("keydown",Y,!0),window.removeEventListener("scroll",D,!0),a=null)}const W=e=>{a&&!a.root.contains(e.target)&&!(a.submenu&&a.submenu.contains(e.target))&&b()},D=e=>{a&&!a.root.contains(e.target)&&!(a.submenu&&a.submenu.contains(e.target))&&b()},Y=e=>{e.key==="Escape"&&b()};function se(e,t,n,o){M.clear();const s=document.createElement("div");s.className="dm-sc-menu",s.style.cssText=H,a={root:s,textarea:e,span:t,submenu:null};const i=t&&(Q.get(t.name)||ee.get(t.name)),l=t&&z.has(t.name)?_.filter(r=>r.name in t.attrs):[];i&&ae(s,i);for(const r of l)he(s,r);(i||l.length)&&s.appendChild(ie()),s.appendChild(v("Add effect\u2026",()=>ye())),s.appendChild(v("Insert\u2026",()=>xe()));const c=e.selectionStart!==e.selectionEnd;(t||c)&&s.appendChild(v(c?"Wrap selection in\u2026":"Wrap in\u2026",()=>Ee())),document.body.appendChild(s),U(s,n,o),document.addEventListener("mousedown",W,!0),document.addEventListener("keydown",Y,!0),window.addEventListener("scroll",D,!0),window.I&&I.scan(s)}function ie(){const e=document.createElement("div");return e.style.cssText=oe,e}function k(e){const t=document.createElement("div");return t.style.cssText=L,t.textContent=e,t}function ae(e,t){e.appendChild(k(`${t.label} options`));for(const n of t.attrs||[])e.appendChild(F(n))}function F(e,t=e.name){const n=a.span.attrs[t];switch(e.type){case"checkbox":return re(e,t,le(n,e.default));case"select":return pe(e,t,n);case"lookup":return fe(e,t,n);case"colour":return ue(e,t,n);case"icon":return me(e,t,n);default:return de(e,t,n)}}function C(e,t){a.span=N(a.textarea,a.span,e,t)}function le(e,t){return e==null?!!t:!(e===!1||e==="false"||e==="0")}function ce(e,t){return t===!!e.default?null:t?"true":"false"}function re(e,t,n){const o=m(document.createElement("label"));o.style.cssText=p;const s=document.createElement("input");return s.type="checkbox",s.checked=n,s.addEventListener("change",()=>C(t,ce(e,s.checked))),o.appendChild(s),o.appendChild(document.createTextNode(e.label)),o}function de(e,t,n){const o=document.createElement("div");o.style.cssText=p+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.cssText="white-space:nowrap;color:var(--dm-text-muted,#9a9aac);";const i=document.createElement("input");i.type=e.type==="number"?"number":"text",i.value=n===!0||n==null?"":n,e.default!=null&&e.default!==""&&(i.placeholder=String(e.default)),i.style.cssText=j;let l=null;return i.addEventListener("input",()=>{clearTimeout(l),l=setTimeout(()=>C(t,i.value.trim()),180)}),i.addEventListener("mousedown",c=>c.stopPropagation()),o.appendChild(s),o.appendChild(i),o}function ue(e,t,n){const o=document.createElement("div");o.style.cssText=p+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.flex="1";const i=document.createElement("input");return i.type="color",typeof n=="string"&&/^#/.test(n)?i.value=n:typeof e.default=="string"&&/^#/.test(e.default)&&(i.value=e.default),i.addEventListener("input",()=>C(t,i.value)),o.appendChild(s),o.appendChild(i),o}function pe(e,t,n){const o=n==null||n===!0?e.default??"":n;return v(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=w();for(const[i,l]of e.options||[]){const c=m(document.createElement("button"));c.type="button",c.style.cssText=p,c.textContent=l,i===o&&(c.style.fontWeight="700"),c.addEventListener("click",()=>{C(t,i),T()}),s.appendChild(c)}return s})}function me(e,t,n){const o=n==null||n===!0?"":String(n),s=o?`${e.label}: ${o}`:e.label;return v(s,()=>{const i=w();i.style.width="232px";const l=document.createElement("input");l.type="text",l.placeholder="Search icons\u2026",l.style.cssText=j+"margin:6px;width:calc(100% - 12px);",l.addEventListener("mousedown",y=>y.stopPropagation()),i.appendChild(l);const c=m(document.createElement("button"));c.type="button",c.style.cssText=p,c.textContent="\u2014 none \u2014",o||(c.style.fontWeight="700"),c.addEventListener("click",()=>{C(t,null),T()}),i.appendChild(c);const r=document.createElement("div");r.style.cssText="display:flex;flex-wrap:wrap;gap:2px;padding:4px 6px;max-height:220px;overflow-y:auto;",i.appendChild(r);const d=96,h=()=>{const y=typeof I<"u"&&typeof I.list=="function"?I.list():[],P=l.value.trim().toLowerCase(),$=P?y.filter(u=>u.toLowerCase().includes(P)):y;r.replaceChildren();for(const u of $.slice(0,d)){const x=m(document.createElement("button"));x.type="button",x.title=u,x.style.cssText="width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;background:none;border:1px solid transparent;border-radius:4px;cursor:pointer;color:inherit;",u===o&&(x.style.borderColor="var(--dm-primary,#5b8cff)");const S=document.createElement("span");S.setAttribute("data-icon",u),S.setAttribute("data-icon-size","15"),S.style.cssText="display:inline-flex;align-items:center;",x.appendChild(S),x.addEventListener("click",()=>{C(t,u),T()}),r.appendChild(x)}if($.length>d){const u=document.createElement("div");u.style.cssText=L,u.textContent=`${$.length-d} more \u2014 refine search`,r.appendChild(u)}if(!$.length){const u=document.createElement("div");u.style.cssText=L,u.textContent="No icons match",r.appendChild(u)}window.I&&I.scan(r)};let f=null;return l.addEventListener("input",()=>{clearTimeout(f),f=setTimeout(h,120)}),h(),setTimeout(()=>l.focus(),0),i})}function fe(e,t,n){const o=n==null||n===!0?e.default??"":n;return v(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=w();s.style.minHeight="48px";const i=typeof E<"u"&&E.loader?E.loader(s,{type:"dots"}):null;return O(e.source).then(l=>{if(i&&i.destroy(),!a||a.submenu!==s)return;if(s.style.minHeight="",!l.length){const d=document.createElement("div");d.style.cssText=L,d.textContent="Nothing found",s.appendChild(d)}const c=l.some(([d])=>d===o),r=[["","\u2014 none \u2014"],...l];o!==""&&!c&&r.push([o,`${o} (missing)`]);for(const[d,h]of r){const f=m(document.createElement("button"));f.type="button",f.style.cssText=p,f.textContent=h,d===o&&(f.style.fontWeight="700"),f.addEventListener("click",()=>{C(t,d===""?null:d),T()}),s.appendChild(f)}}),s})}function he(e,t){e.appendChild(k(`Effect: ${t.label}`));for(const o of t.attrs||[])e.appendChild(F(o,`${t.name}-${o.name}`));const n=m(document.createElement("button"));n.type="button",n.style.cssText=p,n.innerHTML='<span data-icon="trash" data-icon-size="13" style="display:inline-flex;align-items:center;"></span> Remove effect',n.addEventListener("click",()=>{const o={};for(const s of G(a.span.attrs,t.name))o[s]=null;V(a.textarea,a.span,o),b()}),e.appendChild(n)}function ye(){const e=w(),t=a.span,n=t&&z.has(t.name)?t.name:null;for(const o of[...new Set(_.map(s=>s.category))]){const s=_.filter(i=>i.category===o);if(s.length){e.appendChild(k(o));for(const i of s){const l=n&&(i.hosts||[]).includes(n)&&!(i.name in t.attrs),c=m(document.createElement("button"));if(c.type="button",c.style.cssText=p+"justify-content:space-between;",c.appendChild(document.createTextNode(i.label)),l){const r=document.createElement("span");r.textContent=`inject \u2192 [${n}]`,r.style.cssText="opacity:.6;font-size:.7rem;",c.appendChild(r)}c.addEventListener("click",()=>{if(l)N(a.textarea,a.span,i.name,!0);else{const r=i.selfClosing?`[${i.name} /]`:`[${i.name}]
|
|
2
2
|
Content
|
|
3
|
-
[/${
|
|
3
|
+
[/${i.name}]`;R(a.textarea,r)}b()}),e.appendChild(c)}}}return e}function v(e,t){const n=m(document.createElement("button"));n.type="button",n.style.cssText=p+"justify-content:space-between;",n.appendChild(document.createTextNode(e));const o=document.createElement("span");return o.setAttribute("data-icon","chevron-right"),o.setAttribute("data-icon-size","12"),o.style.cssText="opacity:.6;display:inline-flex;align-items:center;flex-shrink:0;",n.appendChild(o),n.addEventListener("mouseenter",()=>B(n,t)),n.addEventListener("click",s=>{s.stopPropagation(),B(n,t)}),n}function w(){const e=document.createElement("div");return e.className="dm-sc-submenu",e.style.cssText=H,e}function B(e,t){T();const n=t();a.submenu=n,document.body.appendChild(n);const o=e.getBoundingClientRect();U(n,o.right+2,o.top,o.left-2),window.I&&I.scan(n)}function T(){a&&a.submenu&&(a.submenu.remove(),a.submenu=null)}function be(e){return(e.attrs||[]).find(t=>t.type==="lookup"&&t.source!=="collection-fields")||null}function ge(e,t,n){const o=e.template||`[${e.name} /]`,s=new RegExp(`\\b${t}="[^"]*"`);return s.test(o)?o.replace(s,`${t}="${n}"`):o.replace(`[${e.name}`,`[${e.name} ${t}="${n}"`)}function xe(){const e=w();for(const t of Z){const n=A.filter(o=>o.category===t&&!o.child);if(n.length){e.appendChild(k(t));for(const o of n){const s=be(o),i=m(document.createElement("button"));if(i.type="button",i.style.cssText=p+(s?"justify-content:space-between;":""),i.appendChild(document.createTextNode(o.label)),s){const l=document.createElement("span");l.setAttribute("data-icon","chevron-right"),l.setAttribute("data-icon-size","12"),l.style.cssText="opacity:.6;display:inline-flex;align-items:center;flex-shrink:0;",i.appendChild(l)}i.addEventListener("click",async l=>{if(!s){R(a.textarea,o.template||`[${o.name} /]`),b();return}l.stopPropagation(),e.textContent="",e.style.minHeight="48px";const c=typeof E<"u"&&E.loader?E.loader(e,{type:"dots"}):null,r=await O(s.source);if(c&&c.destroy(),!a||a.submenu!==e)return;if(e.style.minHeight="",e.appendChild(k(`Insert ${o.label}`)),!r.length){const h=document.createElement("div");h.style.cssText=L,h.textContent="Nothing found \u2014 inserting the blank template instead.",e.appendChild(h)}for(const[h,f]of r){const y=m(document.createElement("button"));y.type="button",y.style.cssText=p,y.textContent=f,y.addEventListener("click",()=>{R(a.textarea,ge(o,s.name,h)),b()}),e.appendChild(y)}const d=m(document.createElement("button"));d.type="button",d.style.cssText=p+"opacity:.75;",d.textContent=r.length?"Blank template\u2026":"Insert blank template",d.addEventListener("click",()=>{R(a.textarea,o.template||`[${o.name} /]`),b()}),e.appendChild(d)}),e.appendChild(i)}}}return e}function Ee(){const e=w(),t=A.filter(n=>n.wrappable||n.container);for(const n of t){const o=m(document.createElement("button"));o.type="button",o.style.cssText=p,o.textContent=n.label,o.addEventListener("click",()=>{Ce(n),b()}),e.appendChild(o)}return e}function Ce(e){const t=a.textarea,n=t.selectionStart,o=t.selectionEnd,s=t.value.slice(n,o)||"Content";let i;e.name==="grid"?i=`[grid cols="2"]
|
|
4
4
|
[col]
|
|
5
5
|
${s}
|
|
6
6
|
[/col]
|
|
7
|
-
[/grid]`:
|
|
7
|
+
[/grid]`:i=`[${e.name}]
|
|
8
8
|
${s}
|
|
9
|
-
[/${e.name}]`,
|
|
9
|
+
[/${e.name}]`,X(t,n,o,i)}function U(e,t,n,o){e.style.left="0px",e.style.top="0px";const s=e.getBoundingClientRect();let i=t;t+s.width>window.innerWidth-8&&(i=o!=null?o-s.width:t-s.width);let l=n;n+s.height>window.innerHeight-8&&(l=Math.max(8,window.innerHeight-s.height-8)),e.style.left=`${Math.max(8,i)}px`,e.style.top=`${Math.max(8,l)}px`}
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
<div class="card-body" style="display:flex;gap:.75rem;align-items:center;flex-wrap:wrap;">
|
|
30
30
|
<input id="entry-search" type="text" class="form-input" placeholder="Search entries…" style="flex:1;min-width:200px;">
|
|
31
31
|
<span id="entry-count" class="text-muted" style="white-space:nowrap;font-size:.85rem;"></span>
|
|
32
|
+
<div id="entry-filter-chips" style="display:none;flex-basis:100%;gap:.4rem;flex-wrap:wrap;align-items:center;"></div>
|
|
32
33
|
</div>
|
|
33
34
|
</div>
|
|
34
35
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as h}from"../api.js";import{keepTableIconsAlive as M}from"../lib/table-icons.js";function L(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}let u=null,g=null,d=[],N=1,w=[],x={};const O=50;export const collectionEntriesView={templateUrl:"/admin/js/templates/collection-entries.html",async onMount(t){d=[],N=1,g=null,w=[],x={};const i=window.location.hash.match(/\/collections\/([^/?#]+)\/entries/);if(u=i?i[1]:null,!u){E.toast("No collection selected.",{type:"error"});return}const s=E.loader(t.get(0),{type:"dots"});try{if(g=await h.collections.get(u),!g){s.destroy(),E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const o=t.find("#entries-title").get(0);o&&(o.textContent=g.title+" \u2014 Entries");const e=t.find("#edit-schema-btn").get(0);e&&e.setAttribute("href",`#/collections/edit/${u}`);const r=t.find("#form-btn").get(0);r&&(new Set(["roles","user-profiles"]).has(u)?r.style.display="none":r.setAttribute("href",`#/forms/edit/${u}`))}catch{s.destroy(),E.toast("Failed to load collection schema.",{type:"error"});return}try{w=await h.actions.forCollection(u)}catch{w=[]}try{await v(t)}finally{s.destroy()}await k(t);const l=t.find("#entry-search").get(0);l&&l.addEventListener("input",()=>{const o=l.value.toLowerCase().trim(),e=o?d.filter(r=>Object.values(r.data||{}).some(a=>String(a).toLowerCase().includes(o))):d;C(e,t)}),t.find("#add-entry-btn").off("click").on("click",()=>{A(null,t)}),t.find("#export-btn").off("click").on("click",()=>{const o=E.slideover({title:"Export Entries",size:"sm",position:"right"}),e=document.createElement("div");e.style.cssText="padding:1.25rem;display:flex;flex-direction:column;gap:1rem;",["json","csv"].forEach(r=>{const a=document.createElement("button");a.className="btn btn-ghost",a.style.cssText="justify-content:flex-start;gap:.5rem;",a.textContent=r==="json"?"Export as JSON":"Export as CSV",a.addEventListener("click",()=>{fetch(`/api/collections/${u}/export?format=${r}`,{headers:{Authorization:`Bearer ${S.get("auth_token")||""}`}}).then(c=>c.blob()).then(c=>{const n=document.createElement("a");n.href=URL.createObjectURL(c),n.download=`${u}-entries.${r}`,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(n.href),o.close()}).catch(()=>E.toast("Export failed.",{type:"error"}))}),e.appendChild(a)}),o.setContent(e),o.open()}),t.find("#import-btn").off("click").on("click",()=>{I(t)}),t.find("#clear-all-btn").off("click").on("click",async()=>{if(await E.confirm("Delete ALL entries? This cannot be undone."))try{await h.collections.clearEntries(u),d=[],E.toast("All entries cleared.",{type:"success"}),C([],t)}catch{E.toast("Failed to clear entries.",{type:"error"})}}),t.off("click",".js-run-action").on("click",".js-run-action",async function(o){o.stopPropagation();const e=this,{actionSlug:r,entryId:a,confirmMessage:c}=e.dataset;if(!(c&&!await E.confirm(c))){e.disabled=!0;try{const n=await h.actions.execute(r,a);if(n.success)E.toast(`Action completed (${n.stepsCompleted} step${n.stepsCompleted!==1?"s":""}).`,{type:"success"}),await v(t);else{const p=n.results?.find(f=>!f.success);E.toast(p?.error||"Action failed.",{type:"error"})}}catch(n){E.toast(n.message||"Action failed.",{type:"error"})}finally{e.disabled=!1}}}),t.off("click",".js-edit-entry").on("click",".js-edit-entry",function(o){o.stopPropagation();const e=d.find(r=>r.id===this.dataset.id);e&&A(e,t)}),t.off("click",".js-delete-entry").on("click",".js-delete-entry",async function(o){o.stopPropagation();const e=this.dataset.id;if(await E.confirm("Delete this entry?"))try{await h.collections.deleteEntry(u,e),d=d.filter(a=>a.id!==e),E.toast("Entry deleted.",{type:"success"}),C(d,t)}catch{E.toast("Failed to delete entry.",{type:"error"})}}),t.off("click","#entries-table tbody tr").on("click","#entries-table tbody tr",function(o){if(o.target.closest(".js-run-action, .js-edit-entry, .js-delete-entry"))return;const e=this.querySelector(".js-edit-entry")?.dataset.id,r=d.find(a=>a.id===e);r&&B(r)}),Domma.icons.scan(),M(t.get(0))}};async function k(t){const m=w.filter(s=>s.access?.rowLevel);if(m.length===0)return;const i=d.map(s=>s.id);i.length!==0&&(await Promise.all(m.map(async s=>{try{const{allowed:l}=await h.actions.checkAccess(s.slug,i);x[s.slug]=new Set(l)}catch{x[s.slug]=null}})),C(d,t))}async function v(t){try{const m=await h.collections.listEntries(u,{limit:500});d=m.entries||m||[]}catch{d=[],E.toast("Could not load entries.",{type:"error"})}C(d,t),await k(t)}function C(t,m){const i=m.find("#entry-count").get(0);i&&(i.textContent=t.length===d.length?`${d.length} entr${d.length!==1?"ies":"y"}`:`Showing ${t.length} of ${d.length}`);const l=[...(g?.fields||[]).slice(0,5).map(e=>({key:`data.${e.name}`,title:e.label||e.name,render:(r,a)=>{const c=a.data?.[e.name]??"",n=String(c),p=document.createElement("span");return p.title=n,p.textContent=n.length>60?n.slice(0,60)+"\u2026":n,p.outerHTML}})),{key:"meta",title:"Created",render:e=>D(e?.createdAt).format("DD MMM YYYY HH:mm")},{key:"id",title:"",render:e=>{const r=document.createElement("div");r.style.cssText="display:flex;gap:.3rem;justify-content:flex-end;flex-wrap:wrap;",w.forEach(n=>{if(x[n.slug]!==void 0&&x[n.slug]!==null&&!x[n.slug].has(e))return;const p=document.createElement("button");p.className="btn btn-sm btn-ghost js-run-action",p.dataset.actionSlug=n.slug,p.dataset.entryId=e,p.dataset.confirmMessage=n.trigger?.confirmMessage||"",p.title=n.title||n.slug;const f=document.createElement("span");f.setAttribute("data-icon",L(n.trigger?.icon||"zap")),f.setAttribute("data-icon-size","14"),p.appendChild(f),p.appendChild(document.createTextNode(" "+(n.trigger?.label||n.title))),r.appendChild(p)});const a=document.createElement("button");a.className="btn btn-sm btn-ghost js-edit-entry",a.dataset.id=e,a.setAttribute("data-tooltip","Edit"),a.innerHTML='<span data-icon="edit" data-icon-size="14"></span>';const c=document.createElement("button");return c.className="btn btn-sm btn-ghost js-delete-entry",c.dataset.id=e,c.setAttribute("data-tooltip","Delete"),c.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',r.appendChild(a),r.appendChild(c),r.outerHTML}}];T.create("#entries-table",{data:t,columns:l,emptyMessage:'No entries yet. Click "Add Entry" to get started.'});const o=document.querySelector("#entries-table");o&&(o.querySelectorAll("tbody tr").forEach(e=>{e.style.cursor="pointer"}),Domma.icons.scan(o),o.querySelectorAll("[data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}))}function z(t,m){const i={};return t.forEach(s=>{const l={};s.fullWidth?l.span=m:s.span>1&&(l.span=s.span),s.placeholder&&(l.placeholder=s.placeholder),s.helper&&(l.hint=s.helper);const o=s.type==="checkbox"?"boolean":s.type==="date"?"string":s.type;i[s.name]={type:o,label:s.label,required:s.required,options:s.options,...Object.keys(l).length?{formConfig:l}:{}}}),i}function A(t,m){const i=!!t,s=E.slideover({title:i?"Edit Entry":"Add Entry",size:"md",position:"right"}),l=document.createElement("div");l.style.cssText="padding:.25rem 0 .5rem;";const o=g?.fields||[],e=g?.columns||2,r=g?.layout||"stacked",a=document.createElement("div");F.create(z(o,e),t?.data||{},{layout:r,columns:e,showSubmitButton:!1}).renderTo(a),(o||[]).forEach(f=>{if(f.type!=="date"||!f.name)return;const y=a.querySelector(`[name="${f.name}"]`);y&&y.type!=="date"&&(y.type="date")}),l.appendChild(a);const c=document.createElement("div");c.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.5rem;";const n=document.createElement("button");n.className="btn btn-ghost",n.textContent="Cancel";const p=document.createElement("button");p.className="btn btn-primary",p.textContent=i?"Save Changes":"Add Entry",c.appendChild(n),c.appendChild(p),l.appendChild(c),s.element.appendChild(l),s.open(),n.addEventListener("click",()=>s.close()),p.addEventListener("click",async()=>{const f={};o.forEach(y=>{const b=a.querySelector(`[name="${y.name}"]`);b&&(f[y.name]=y.type==="checkbox"?b.checked:b.value)}),p.disabled=!0;try{if(i){const y=await h.collections.updateEntry(u,t.id,f),b=d.findIndex(j=>j.id===t.id);b!==-1&&(d[b]=y),E.toast("Entry updated.",{type:"success"})}else{const y=await h.collections.createEntry(u,f);d.unshift(y),E.toast("Entry added.",{type:"success"})}s.close(),C(d,m||{find:()=>({get:()=>null})})}catch(y){E.toast(y.message||"Failed to save entry.",{type:"error"}),p.disabled=!1}})}function B(t){const m=document.createElement("div"),i=g?.fields||[],s=document.createElement("div");s.style.cssText="display:flex;flex-direction:column;gap:.75rem;margin-bottom:1.25rem;",i.forEach(e=>{const r=t.data?.[e.name];if(r==null||r==="")return;const a=document.createElement("div");a.style.cssText="border-bottom:1px solid var(--dm-border,#333);padding-bottom:.6rem;";const c=document.createElement("strong");c.textContent=e.label||e.name,c.style.cssText="display:block;font-size:.8rem;color:var(--dm-text-muted,#888);margin-bottom:.2rem;";const n=document.createElement("p");n.textContent=String(r),n.style.cssText="margin:0;word-break:break-word;",a.appendChild(c),a.appendChild(n),s.appendChild(a)});const l=document.createElement("div");if(l.style.cssText="font-size:.8rem;color:var(--dm-text-muted,#888);display:flex;flex-direction:column;gap:.3rem;border-top:1px solid var(--dm-border,#333);padding-top:.75rem;",t.meta?.createdAt){const e=document.createElement("span");e.textContent=`Created: ${D(t.meta.createdAt).format("DD MMM YYYY HH:mm")}`,l.appendChild(e)}if(t.meta?.source){const e=document.createElement("span");e.textContent=`Source: ${t.meta.source}`,l.appendChild(e)}m.appendChild(s),m.appendChild(l);const o=E.slideover({title:"Entry Details",size:"md",position:"right"});o.element.appendChild(m),o.open()}function I(t){const m=E.slideover({title:"Import Entries",size:"md",position:"right"}),i=document.createElement("div");i.style.cssText="padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem;";const s=document.createElement("p");s.textContent='Paste a JSON array of entries. Each item should have a "data" object with field values.',s.style.cssText="font-size:.875rem;color:var(--dm-text-muted,#888);margin:0;",i.appendChild(s);const l=document.createElement("textarea");l.className="form-input",l.rows=10,l.placeholder='[{"data": {"name": "Example"}}, ...]',i.appendChild(l);const o=document.createElement("p");o.style.cssText="font-size:.875rem;margin:0;display:none;",i.appendChild(o);const e=document.createElement("div");e.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;";const r=document.createElement("button");r.className="btn btn-ghost",r.textContent="Cancel";const a=document.createElement("button");a.className="btn btn-primary",a.textContent="Import",e.appendChild(r),e.appendChild(a),i.appendChild(e),m.element.appendChild(i),m.open(),r.addEventListener("click",()=>m.close()),a.addEventListener("click",async()=>{let c;try{if(c=JSON.parse(l.value),!Array.isArray(c))throw new Error("Must be a JSON array")}catch(n){o.style.display="",o.style.color="var(--dm-danger,#f87171)",o.textContent=`Invalid JSON: ${n.message}`;return}a.disabled=!0;try{const n=await h.collections.import(u,c);o.style.display="",o.style.color="var(--dm-success,#4ade80)",o.textContent=`Imported ${n.imported} entries. Skipped: ${n.skipped}.`,await v(t||{find:()=>({get:()=>null,off:()=>({on:()=>{}})})}),setTimeout(()=>m.close(),1500)}catch(n){o.style.display="",o.style.color="var(--dm-danger,#f87171)",o.textContent=n.message||"Import failed."}finally{a.disabled=!1}})}
|
|
1
|
+
import{api as h}from"../api.js";import{keepTableIconsAlive as _}from"../lib/table-icons.js";function O(e){return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}let y=null,g=null,u=[],H=1,L=[],k={},b=[];const te=50,P={eq:"=",ne:"\u2260",contains:"contains",gt:">",lt:"<"};function U(e,r){const a=e.data?.[r.field],o=String(a??"");switch(r.op){case"eq":return o===String(r.value);case"ne":return o!==String(r.value);case"contains":return o.toLowerCase().includes(String(r.value).toLowerCase());case"gt":case"lt":{const c=parseFloat(o),t=parseFloat(r.value);if(!Number.isNaN(c)&&!Number.isNaN(t))return r.op==="gt"?c>t:c<t;const n=Date.parse(o),i=Date.parse(r.value);return!Number.isNaN(n)&&!Number.isNaN(i)?r.op==="gt"?n>i:n<i:r.op==="gt"?o>String(r.value):o<String(r.value)}default:return!0}}function x(e){const r=(e.find("#entry-search").get(0)?.value||"").toLowerCase().trim();let a=u;for(const o of b)a=a.filter(c=>U(c,o));r&&(a=a.filter(o=>Object.values(o.data||{}).some(c=>String(c).toLowerCase().includes(r)))),W(e),B(a,e)}function W(e){const r=e.find("#entry-filter-chips").get(0);if(!r)return;if(r.replaceChildren(),!b.length){r.style.display="none";return}r.style.display="flex",b.forEach((o,c)=>{const t=document.createElement("span");t.className="badge badge-secondary",t.style.cssText="display:inline-flex;align-items:center;gap:.35rem;font-size:.78rem;";const n=String(o.value);t.textContent=`${o.label} ${P[o.op]||o.op} \u201C${n.length>24?n.slice(0,24)+"\u2026":n}\u201D`;const i=document.createElement("button");i.type="button",i.textContent="\xD7",i.title="Remove filter",i.style.cssText="background:none;border:none;cursor:pointer;color:inherit;font-size:.9rem;line-height:1;padding:0;",i.addEventListener("click",()=>{b.splice(c,1),x(e)}),t.appendChild(i),r.appendChild(t)});const a=document.createElement("button");a.type="button",a.className="btn btn-ghost btn-sm",a.textContent="Clear all",a.addEventListener("click",()=>{b=[],x(e)}),r.appendChild(a)}let v=null;const J="position:fixed;z-index:10000;min-width:200px;max-height:60vh;overflow-y:auto;background:var(--dm-surface,#1c1c30);border:1px solid var(--dm-border,#2a2a40);border-radius:6px;box-shadow:var(--dm-shadow-lg);padding:.3rem;font-size:.83rem;color:var(--dm-text,#e5e5e5);",G="display:flex;align-items:center;gap:.5rem;width:100%;text-align:left;background:none;border:none;color:inherit;font:inherit;padding:.35rem .6rem;border-radius:4px;cursor:pointer;";function C(){v&&(v.remove(),v=null,document.removeEventListener("mousedown",A,!0),document.removeEventListener("keydown",j,!0),window.removeEventListener("scroll",z,!0))}const A=e=>{v&&!v.contains(e.target)&&C()},j=e=>{e.key==="Escape"&&C()},z=e=>{v&&!v.contains(e.target)&&C()};function N(e,r){const a=document.createElement("button");return a.type="button",a.style.cssText=G,a.textContent=e,a.addEventListener("mouseenter",()=>{a.style.background="var(--dm-surface-2,rgba(255,255,255,.06))"}),a.addEventListener("mouseleave",()=>{a.style.background="none"}),a.addEventListener("click",r),a}function V(e){const r=document.createElement("div");return r.style.cssText="padding:.3rem .6rem;font-size:.72rem;text-transform:uppercase;letter-spacing:.04em;color:var(--dm-text-muted,#9a9aac);",r.textContent=e,r}function K(e,r,a,o,c){C();const t=document.createElement("div");t.style.cssText=J,v=t;const n=a.label||a.name,i=String(o).length>28?String(o).slice(0,28)+"\u2026":String(o),l=(m,f)=>{b.push({field:a.name,label:n,op:m,value:f}),x(c),C()};t.appendChild(V(n)),o!==""&&(t.appendChild(N(`Filter: = \u201C${i}\u201D`,()=>l("eq",o))),t.appendChild(N(`Filter: \u2260 \u201C${i}\u201D`,()=>l("ne",o))),["number","date","time"].includes(a.type)&&(t.appendChild(N(`Filter: > \u201C${i}\u201D`,()=>l("gt",o))),t.appendChild(N(`Filter: < \u201C${i}\u201D`,()=>l("lt",o)))));const d=document.createElement("div");d.style.cssText="padding:.35rem .6rem;";const s=document.createElement("input");if(s.type="text",s.placeholder=`${n} contains\u2026`,s.className="form-input",s.style.cssText="width:100%;font-size:.8rem;padding:.3rem .5rem;",s.addEventListener("mousedown",m=>m.stopPropagation()),s.addEventListener("keydown",m=>{m.key==="Enter"&&s.value.trim()&&l("contains",s.value.trim())}),d.appendChild(s),t.appendChild(d),b.length){const m=document.createElement("div");m.style.cssText="border-top:1px solid var(--dm-border,#2a2a40);margin:.25rem 0;",t.appendChild(m),t.appendChild(N(`Clear all filters (${b.length})`,()=>{b=[],x(c),C()}))}document.body.appendChild(t);const p=t.getBoundingClientRect();t.style.left=Math.min(e,window.innerWidth-p.width-8)+"px",t.style.top=Math.min(r,window.innerHeight-p.height-8)+"px",document.addEventListener("mousedown",A,!0),document.addEventListener("keydown",j,!0),window.addEventListener("scroll",z,!0),setTimeout(()=>s.focus(),0)}export const collectionEntriesView={templateUrl:"/admin/js/templates/collection-entries.html",async onMount(e){u=[],H=1,g=null,L=[],k={},b=[],C();const a=window.location.hash.match(/\/collections\/([^/?#]+)\/entries/);if(y=a?a[1]:null,!y){E.toast("No collection selected.",{type:"error"});return}const o=E.loader(e.get(0),{type:"dots"});try{if(g=await h.collections.get(y),!g){o.destroy(),E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const t=e.find("#entries-title").get(0);t&&(t.textContent=g.title+" \u2014 Entries");const n=e.find("#edit-schema-btn").get(0);n&&n.setAttribute("href",`#/collections/edit/${y}`);const i=e.find("#form-btn").get(0);i&&(new Set(["roles","user-profiles"]).has(y)?i.style.display="none":i.setAttribute("href",`#/forms/edit/${y}`))}catch{o.destroy(),E.toast("Failed to load collection schema.",{type:"error"});return}try{L=await h.actions.forCollection(y)}catch{L=[]}try{await M(e)}finally{o.destroy()}await I(e);const c=e.find("#entry-search").get(0);c&&c.addEventListener("input",()=>x(e)),e.get(0).addEventListener("contextmenu",t=>{const n=t.target.closest("#entries-table tbody td");if(!n)return;const l=(g?.fields||[]).slice(0,5)[n.cellIndex];if(!l)return;t.preventDefault();const d=n.querySelector("span[title]")?.title??n.textContent.trim();K(t.clientX,t.clientY,l,d,e)}),e.find("#add-entry-btn").off("click").on("click",()=>{q(null,e)}),e.find("#export-btn").off("click").on("click",()=>{const t=E.slideover({title:"Export Entries",size:"sm",position:"right"}),n=document.createElement("div");n.style.cssText="padding:1.25rem;display:flex;flex-direction:column;gap:1rem;",["json","csv"].forEach(i=>{const l=document.createElement("button");l.className="btn btn-ghost",l.style.cssText="justify-content:flex-start;gap:.5rem;",l.textContent=i==="json"?"Export as JSON":"Export as CSV",l.addEventListener("click",()=>{fetch(`/api/collections/${y}/export?format=${i}`,{headers:{Authorization:`Bearer ${S.get("auth_token")||""}`}}).then(d=>d.blob()).then(d=>{const s=document.createElement("a");s.href=URL.createObjectURL(d),s.download=`${y}-entries.${i}`,document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(s.href),t.close()}).catch(()=>E.toast("Export failed.",{type:"error"}))}),n.appendChild(l)}),t.setContent(n),t.open()}),e.find("#import-btn").off("click").on("click",()=>{Q(e)}),e.find("#clear-all-btn").off("click").on("click",async()=>{if(await E.confirm("Delete ALL entries? This cannot be undone."))try{await h.collections.clearEntries(y),u=[],E.toast("All entries cleared.",{type:"success"}),x(e)}catch{E.toast("Failed to clear entries.",{type:"error"})}}),e.off("click",".js-run-action").on("click",".js-run-action",async function(t){t.stopPropagation();const n=this,{actionSlug:i,entryId:l,confirmMessage:d}=n.dataset;if(!(d&&!await E.confirm(d))){n.disabled=!0;try{const s=await h.actions.execute(i,l);if(s.success)E.toast(`Action completed (${s.stepsCompleted} step${s.stepsCompleted!==1?"s":""}).`,{type:"success"}),await M(e);else{const p=s.results?.find(m=>!m.success);E.toast(p?.error||"Action failed.",{type:"error"})}}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{n.disabled=!1}}}),e.off("click",".js-edit-entry").on("click",".js-edit-entry",function(t){t.stopPropagation();const n=u.find(i=>i.id===this.dataset.id);n&&q(n,e)}),e.off("click",".js-delete-entry").on("click",".js-delete-entry",async function(t){t.stopPropagation();const n=this.dataset.id;if(await E.confirm("Delete this entry?"))try{await h.collections.deleteEntry(y,n),u=u.filter(l=>l.id!==n),E.toast("Entry deleted.",{type:"success"}),x(e)}catch{E.toast("Failed to delete entry.",{type:"error"})}}),e.off("click","#entries-table tbody tr").on("click","#entries-table tbody tr",function(t){if(t.target.closest(".js-run-action, .js-edit-entry, .js-delete-entry"))return;const n=this.querySelector(".js-edit-entry")?.dataset.id,i=u.find(l=>l.id===n);i&&Z(i)}),Domma.icons.scan(),_(e.get(0))}};async function I(e){const r=L.filter(o=>o.access?.rowLevel);if(r.length===0)return;const a=u.map(o=>o.id);a.length!==0&&(await Promise.all(r.map(async o=>{try{const{allowed:c}=await h.actions.checkAccess(o.slug,a);k[o.slug]=new Set(c)}catch{k[o.slug]=null}})),x(e))}async function M(e){try{const r=await h.collections.listEntries(y,{limit:500});u=r.entries||r||[]}catch{u=[],E.toast("Could not load entries.",{type:"error"})}x(e),await I(e)}function B(e,r){const a=r.find("#entry-count").get(0);a&&(a.textContent=e.length===u.length?`${u.length} entr${u.length!==1?"ies":"y"}`:`Showing ${e.length} of ${u.length}`);const c=[...(g?.fields||[]).slice(0,5).map(n=>({key:`data.${n.name}`,title:n.label||n.name,render:(i,l)=>{const d=l.data?.[n.name]??"",s=String(d),p=document.createElement("span");return p.title=s,p.textContent=s.length>60?s.slice(0,60)+"\u2026":s,p.outerHTML}})),{key:"meta",title:"Created",render:n=>D(n?.createdAt).format("DD MMM YYYY HH:mm")},{key:"id",title:"",render:n=>{const i=document.createElement("div");i.style.cssText="display:flex;gap:.3rem;justify-content:flex-end;flex-wrap:wrap;",L.forEach(s=>{if(k[s.slug]!==void 0&&k[s.slug]!==null&&!k[s.slug].has(n))return;const p=document.createElement("button");p.className="btn btn-sm btn-ghost js-run-action",p.dataset.actionSlug=s.slug,p.dataset.entryId=n,p.dataset.confirmMessage=s.trigger?.confirmMessage||"",p.title=s.title||s.slug;const m=document.createElement("span");m.setAttribute("data-icon",O(s.trigger?.icon||"zap")),m.setAttribute("data-icon-size","14"),p.appendChild(m),p.appendChild(document.createTextNode(" "+(s.trigger?.label||s.title))),i.appendChild(p)});const l=document.createElement("button");l.className="btn btn-sm btn-ghost js-edit-entry",l.dataset.id=n,l.setAttribute("data-tooltip","Edit"),l.innerHTML='<span data-icon="edit" data-icon-size="14"></span>';const d=document.createElement("button");return d.className="btn btn-sm btn-ghost js-delete-entry",d.dataset.id=n,d.setAttribute("data-tooltip","Delete"),d.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',i.appendChild(l),i.appendChild(d),i.outerHTML}}];T.create("#entries-table",{data:e,columns:c,emptyMessage:'No entries yet. Click "Add Entry" to get started.'});const t=document.querySelector("#entries-table");t&&(t.querySelectorAll("tbody tr").forEach(n=>{n.style.cursor="pointer"}),Domma.icons.scan(t),t.querySelectorAll("[data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})}))}function X(e,r){const a={};return e.forEach(o=>{const c={};o.fullWidth?c.span=r:o.span>1&&(c.span=o.span),o.placeholder&&(c.placeholder=o.placeholder),o.helper&&(c.hint=o.helper);const t=o.type==="checkbox"?"boolean":o.type==="date"?"string":o.type;a[o.name]={type:t,label:o.label,required:o.required,options:o.options,...Object.keys(c).length?{formConfig:c}:{}}}),a}function q(e,r){const a=!!e,o=E.slideover({title:a?"Edit Entry":"Add Entry",size:"md",position:"right"}),c=document.createElement("div");c.style.cssText="padding:.25rem 0 .5rem;";const t=g?.fields||[],n=g?.columns||2,i=g?.layout||"stacked",l=document.createElement("div");F.create(X(t,n),e?.data||{},{layout:i,columns:n,showSubmitButton:!1}).renderTo(l),(t||[]).forEach(m=>{if(m.type!=="date"||!m.name)return;const f=l.querySelector(`[name="${m.name}"]`);f&&f.type!=="date"&&(f.type="date")}),c.appendChild(l);const d=document.createElement("div");d.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.5rem;";const s=document.createElement("button");s.className="btn btn-ghost",s.textContent="Cancel";const p=document.createElement("button");p.className="btn btn-primary",p.textContent=a?"Save Changes":"Add Entry",d.appendChild(s),d.appendChild(p),c.appendChild(d),o.element.appendChild(c),o.open(),s.addEventListener("click",()=>o.close()),p.addEventListener("click",async()=>{const m={};t.forEach(f=>{const w=l.querySelector(`[name="${f.name}"]`);w&&(m[f.name]=f.type==="checkbox"?w.checked:w.value)}),p.disabled=!0;try{if(a){const f=await h.collections.updateEntry(y,e.id,m),w=u.findIndex(Y=>Y.id===e.id);w!==-1&&(u[w]=f),E.toast("Entry updated.",{type:"success"})}else{const f=await h.collections.createEntry(y,m);u.unshift(f),E.toast("Entry added.",{type:"success"})}o.close(),r?x(r):B(u,{find:()=>({get:()=>null})})}catch(f){E.toast(f.message||"Failed to save entry.",{type:"error"}),p.disabled=!1}})}function Z(e){const r=document.createElement("div"),a=g?.fields||[],o=document.createElement("div");o.style.cssText="display:flex;flex-direction:column;gap:.75rem;margin-bottom:1.25rem;",a.forEach(n=>{const i=e.data?.[n.name];if(i==null||i==="")return;const l=document.createElement("div");l.style.cssText="border-bottom:1px solid var(--dm-border,#333);padding-bottom:.6rem;";const d=document.createElement("strong");d.textContent=n.label||n.name,d.style.cssText="display:block;font-size:.8rem;color:var(--dm-text-muted,#888);margin-bottom:.2rem;";const s=document.createElement("p");s.textContent=String(i),s.style.cssText="margin:0;word-break:break-word;",l.appendChild(d),l.appendChild(s),o.appendChild(l)});const c=document.createElement("div");if(c.style.cssText="font-size:.8rem;color:var(--dm-text-muted,#888);display:flex;flex-direction:column;gap:.3rem;border-top:1px solid var(--dm-border,#333);padding-top:.75rem;",e.meta?.createdAt){const n=document.createElement("span");n.textContent=`Created: ${D(e.meta.createdAt).format("DD MMM YYYY HH:mm")}`,c.appendChild(n)}if(e.meta?.source){const n=document.createElement("span");n.textContent=`Source: ${e.meta.source}`,c.appendChild(n)}r.appendChild(o),r.appendChild(c);const t=E.slideover({title:"Entry Details",size:"md",position:"right"});t.element.appendChild(r),t.open()}function Q(e){const r=E.slideover({title:"Import Entries",size:"md",position:"right"}),a=document.createElement("div");a.style.cssText="padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem;";const o=document.createElement("p");o.textContent='Paste a JSON array of entries. Each item should have a "data" object with field values.',o.style.cssText="font-size:.875rem;color:var(--dm-text-muted,#888);margin:0;",a.appendChild(o);const c=document.createElement("textarea");c.className="form-input",c.rows=10,c.placeholder='[{"data": {"name": "Example"}}, ...]',a.appendChild(c);const t=document.createElement("p");t.style.cssText="font-size:.875rem;margin:0;display:none;",a.appendChild(t);const n=document.createElement("div");n.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;";const i=document.createElement("button");i.className="btn btn-ghost",i.textContent="Cancel";const l=document.createElement("button");l.className="btn btn-primary",l.textContent="Import",n.appendChild(i),n.appendChild(l),a.appendChild(n),r.element.appendChild(a),r.open(),i.addEventListener("click",()=>r.close()),l.addEventListener("click",async()=>{let d;try{if(d=JSON.parse(c.value),!Array.isArray(d))throw new Error("Must be a JSON array")}catch(s){t.style.display="",t.style.color="var(--dm-danger,#f87171)",t.textContent=`Invalid JSON: ${s.message}`;return}l.disabled=!0;try{const s=await h.collections.import(y,d);t.style.display="",t.style.color="var(--dm-success,#4ade80)",t.textContent=`Imported ${s.imported} entries. Skipped: ${s.skipped}.`,await M(e||{find:()=>({get:()=>null,off:()=>({on:()=>{}})})}),setTimeout(()=>r.close(),1500)}catch(s){t.style.display="",t.style.color="var(--dm-danger,#f87171)",t.textContent=s.message||"Import failed."}finally{l.disabled=!1}})}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import{api as C,apiRequest as v}from"../api.js";import{populateThemeSelect as w}from"../lib/themes.js";export const settingsView={templateUrl:"/admin/js/templates/settings.html",async onMount(e){E.tabs(e.find("#settings-tabs").get(0));const k=E.loader(e.get(0),{type:"dots"}),l=await C.settings.get().catch(()=>({}));if(k.destroy(),e.find("#field-site-title").val(l.title||""),e.find("#field-tagline").val(l.tagline||""),e.find("#field-font-family").val(l.fontFamily||"Roboto"),e.find("#field-font-size").val(l.fontSize||16),w(e.find("#field-theme").get(0)),w(e.find("#field-admin-theme").get(0),{includeAdmin:!0}),e.find("#field-theme").val(l.baseTheme||l.theme||"charcoal-dark"),l.baseTheme){e.find("#field-theme").prop("disabled",!0);const t=document.createElement("p");t.className="form-hint",t.style.cssText="margin-top:.4rem;font-size:.8rem;color:var(--dm-info);";const s=document.createElement("a");s.href="#/plugins/theme-roller",s.textContent="Theme Roller",t.appendChild(document.createTextNode(`Custom theme \u201C${l.theme}\u201D is active (based on ${l.baseTheme}). Manage via `)),t.appendChild(s),t.appendChild(document.createTextNode(".")),e.find("#field-theme").get(0).closest(".col-6").appendChild(t)}e.find("#field-admin-theme").val(l.adminTheme||"charcoal-dark");const p=l.autoTheme||{},h=!!p.enabled,b=e.find("#field-auto-theme-enabled"),y=e.find("#auto-theme-fields"),u=e.find("#field-theme"),f=u.get(0).innerHTML;e.find("#field-day-theme").html(f),e.find("#field-night-theme").html(f),l.baseTheme?(b.prop("disabled",!0),e.find("#auto-theme-roller-hint").show()):(b.prop("checked",h),h&&(y.show(),u.prop("disabled",!0)),b.on("change",function(){const t=this.checked;y.toggle(t),u.prop("disabled",t)})),e.find("#field-day-theme").val(p.dayTheme||"charcoal-light"),e.find("#field-night-theme").val(p.nightTheme||"charcoal-dark"),e.find("#field-day-start").val(p.dayStart||"07:00"),e.find("#field-night-start").val(p.nightStart||"19:00"),e.find("#field-spacer-size").val(l.layoutOptions?.spacerSize??40),e.find("#field-seo-title").val(l.seo?.defaultTitle||""),e.find("#field-seo-separator").val(l.seo?.titleSeparator||" | "),e.find("#field-seo-desc").val(l.seo?.defaultDescription||""),e.find("#field-footer-copy").val(l.footer?.copyright||""),e.find("#field-social-twitter").val(l.social?.twitter||""),e.find("#field-social-facebook").val(l.social?.facebook||""),e.find("#field-social-instagram").val(l.social?.instagram||""),e.find("#field-social-linkedin").val(l.social?.linkedin||""),e.find("#field-social-github").val(l.social?.github||""),e.find("#field-social-youtube").val(l.social?.youtube||""),e.find("#field-smtp-host").val(l.smtp?.host||""),e.find("#field-smtp-port").val(l.smtp?.port||587),e.find("#field-smtp-user").val(l.smtp?.user||""),e.find("#field-smtp-pass").val(l.smtp?.pass||""),e.find("#field-smtp-secure").prop("checked",l.smtp?.secure||!1),e.find("#field-smtp-from-address").val(l.smtp?.fromAddress||""),e.find("#field-smtp-from-name").val(l.smtp?.fromName||"");const i=l.backToTop||{};e.find("#field-btt-enabled").prop("checked",i.enabled!==!1),e.find("#field-btt-threshold").val(i.scrollThreshold??300),e.find("#field-btt-position").val(i.position||"bottom-right"),e.find("#field-btt-offset").val(i.offset??32),e.find("#field-btt-bottom-offset").val(i.bottomOffset??i.offset??32),e.find("#field-btt-label").val(i.label||""),e.find("#field-btt-smooth").prop("checked",i.smooth!==!1);const d=l.cookieConsent||{};e.find("#field-cc-enabled").prop("checked",d.enabled!==!1),e.find("#field-cc-message").val(d.message||""),e.find("#field-cc-accept-all").val(d.acceptAllText||"Accept All"),e.find("#field-cc-reject-all").val(d.rejectAllText||"Reject All"),e.find("#field-cc-customize").val(d.customizeText||"Customize"),e.find("#field-cc-save-prefs").val(d.savePreferencesText||"Save Preferences"),e.find("#field-cc-privacy-text").val(d.privacyPolicyText||"Privacy Policy"),e.find("#field-cc-privacy-url").val(d.privacyPolicyUrl||""),e.find("#field-cc-cookie-text").val(d.cookiePolicyText||"Cookie Policy"),e.find("#field-cc-cookie-url").val(d.cookiePolicyUrl||""),e.find("#field-cc-position").val(d.position||"bottom"),e.find("#field-cc-layout").val(d.layout||"bar"),e.find("#field-cc-theme").val(d.theme||"dark"),e.find("#field-cc-show-functional").prop("checked",d.showFunctional!==!1),e.find("#field-cc-show-analytics").prop("checked",d.showAnalytics!==!1),e.find("#field-cc-show-marketing").prop("checked",d.showMarketing!==!1),e.find("#field-cc-version").val(d.consentVersion||"1.0");const n=l.breadcrumbs||{};e.find("#field-breadcrumbs-enabled").prop("checked",n.enabled===!0),e.find("#field-breadcrumbs-home-label").val(n.homeLabel||"Home"),e.find("#field-breadcrumbs-position").val(n.position||"TL"),e.find("#field-bc-offset-x").val(n.offsetX??8),e.find("#field-bc-offset-y").val(n.offsetY??8);function c(t){e.find(".bc-pos-btn").each(function(){const s=this.dataset.pos===t;this.style.background=s?"var(--dm-primary, #5b8cff)":"",this.style.color=s?"var(--dm-primary-text)":""})}c(n.position||"TL"),e.find(".bc-pos-btn").on("click",function(){const t=this.dataset.pos;e.find("#field-breadcrumbs-position").val(t),c(t)});let a=null;try{const{css:t}=await v("/settings/custom-css");e.find("#field-custom-css").val(t||""),E.editor&&(a=E.editor(e.find("#field-custom-css").get(0),{mode:"code",language:"css",lineNumbers:!0,showToolbar:!1,minHeight:420,placeholder:"/* Add your custom CSS here */",characterCount:!0}))}catch{}a&&a._editorEl&&a._editorEl.addEventListener("keydown",t=>{if(!t.ctrlKey&&!t.metaKey)return;if(t.key==="s"){t.preventDefault(),e.find("#save-css-btn").get(0)?.click();return}const s=a._editorEl,m=s.selectionStart!==s.selectionEnd;if((t.key==="c"||t.key==="x")&&!m){t.preventDefault();const o=s.value,g=s.selectionStart,T=o.lastIndexOf(`
|
|
2
2
|
`,g-1)+1,x=o.indexOf(`
|
|
3
|
-
`,g),S=x===-1?o.slice(T):o.slice(T,x+1);if(navigator.clipboard.writeText(S),t.key==="x"){const z=o.slice(0,T),P=x===-1?"":o.slice(x+1);s.value=z+P,s.selectionStart=s.selectionEnd=T,s.dispatchEvent(new Event("input",{bubbles:!0})),E.toast("Line cut",{type:"info",duration:1500})}else E.toast("Line copied",{type:"info",duration:1500})}}),e.find("#save-settings-btn").on("click",async()=>{const t=e.find("#field-admin-theme").val(),s=!l.baseTheme&&e.find("#field-auto-theme-enabled").prop("checked"),m={enabled:s,dayTheme:e.find("#field-day-theme").val()||"charcoal-light",nightTheme:e.find("#field-night-theme").val()||"charcoal-dark",dayStart:e.find("#field-day-start").val()||"07:00",nightStart:e.find("#field-night-start").val()||"19:00"},o={title:e.find("#field-site-title").val().trim(),tagline:e.find("#field-tagline").val().trim(),fontFamily:e.find("#field-font-family").val()||"Roboto",fontSize:parseInt(e.find("#field-font-size").val(),10)||16,theme:l.baseTheme?l.theme:s?m.dayTheme:e.find("#field-theme").val(),...l.baseTheme?{baseTheme:l.baseTheme}:{},autoTheme:m,adminTheme:t,layoutOptions:{spacerSize:parseInt(e.find("#field-spacer-size").val(),10)||40},seo:{defaultTitle:e.find("#field-seo-title").val().trim(),titleSeparator:e.find("#field-seo-separator").val()||" | ",defaultDescription:e.find("#field-seo-desc").val().trim()},footer:{copyright:e.find("#field-footer-copy").val().trim(),links:l.footer?.links||[]},social:{twitter:e.find("#field-social-twitter").val().trim(),facebook:e.find("#field-social-facebook").val().trim(),instagram:e.find("#field-social-instagram").val().trim(),linkedin:e.find("#field-social-linkedin").val().trim(),github:e.find("#field-social-github").val().trim(),youtube:e.find("#field-social-youtube").val().trim()},smtp:{host:e.find("#field-smtp-host").val().trim(),port:parseInt(e.find("#field-smtp-port").val(),10)||587,user:e.find("#field-smtp-user").val().trim(),pass:e.find("#field-smtp-pass").val(),secure:e.find("#field-smtp-secure").prop("checked"),fromAddress:e.find("#field-smtp-from-address").val().trim(),fromName:e.find("#field-smtp-from-name").val().trim()},backToTop:{enabled:e.find("#field-btt-enabled").prop("checked"),scrollThreshold:parseInt(e.find("#field-btt-threshold").val(),10)||300,position:e.find("#field-btt-position").val()||"bottom-right",offset:parseInt(e.find("#field-btt-offset").val(),10)||32,bottomOffset:parseInt(e.find("#field-btt-bottom-offset").val(),10)||32,label:e.find("#field-btt-label").val().trim(),smooth:e.find("#field-btt-smooth").prop("checked")},cookieConsent:{enabled:e.find("#field-cc-enabled").prop("checked"),message:e.find("#field-cc-message").val().trim(),acceptAllText:e.find("#field-cc-accept-all").val().trim(),rejectAllText:e.find("#field-cc-reject-all").val().trim(),customizeText:e.find("#field-cc-customize").val().trim(),savePreferencesText:e.find("#field-cc-save-prefs").val().trim(),privacyPolicyText:e.find("#field-cc-privacy-text").val().trim(),privacyPolicyUrl:e.find("#field-cc-privacy-url").val().trim(),cookiePolicyText:e.find("#field-cc-cookie-text").val().trim(),cookiePolicyUrl:e.find("#field-cc-cookie-url").val().trim(),position:e.find("#field-cc-position").val(),layout:e.find("#field-cc-layout").val(),theme:e.find("#field-cc-theme").val(),showFunctional:e.find("#field-cc-show-functional").prop("checked"),showAnalytics:e.find("#field-cc-show-analytics").prop("checked"),showMarketing:e.find("#field-cc-show-marketing").prop("checked"),consentVersion:e.find("#field-cc-version").val().trim()||"1.0"}},g=e.find("#save-settings-btn");g.prop("disabled",!0);try{await C.settings.save(o),Domma.theme.set(t),E.toast("Settings saved.",{type:"success"})}catch{E.toast("Failed to save settings.",{type:"error"})}finally{g.prop("disabled",!1)}}),e.find("#send-test-email-btn").on("click",async()=>{const t=e.find("#field-test-email-to").val().trim(),s=e.find("#test-email-result").get(0),m=e.find("#send-test-email-btn");m.prop("disabled",!0),s&&(s.textContent="Sending\u2026",s.style.color="");try{const o=await v("/settings/test-email",{method:"POST",body:JSON.stringify({to:t||void 0})});s&&(s.textContent=o.message||"Test email sent.",s.style.color="var(--dm-success,#4ade80)")}catch(o){s&&(s.textContent=o.message||"Failed to send test email.",s.style.color="var(--dm-danger,#f87171)")}finally{m.prop("disabled",!1)}}),e.find("#save-breadcrumbs-btn").on("click",async()=>{const t=e.find("#save-breadcrumbs-btn");t.prop("disabled",!0);try{const s={enabled:e.find("#field-breadcrumbs-enabled").prop("checked"),homeLabel:e.find("#field-breadcrumbs-home-label").val().trim()||"Home",position:e.find("#field-breadcrumbs-position").val()||"TL",offsetX:parseInt(e.find("#field-bc-offset-x").val(),10)||8,offsetY:parseInt(e.find("#field-bc-offset-y").val(),10)||8};await C.settings.save({
|
|
3
|
+
`,g),S=x===-1?o.slice(T):o.slice(T,x+1);if(navigator.clipboard.writeText(S),t.key==="x"){const z=o.slice(0,T),P=x===-1?"":o.slice(x+1);s.value=z+P,s.selectionStart=s.selectionEnd=T,s.dispatchEvent(new Event("input",{bubbles:!0})),E.toast("Line cut",{type:"info",duration:1500})}else E.toast("Line copied",{type:"info",duration:1500})}}),e.find("#save-settings-btn").on("click",async()=>{const t=e.find("#field-admin-theme").val(),s=!l.baseTheme&&e.find("#field-auto-theme-enabled").prop("checked"),m={enabled:s,dayTheme:e.find("#field-day-theme").val()||"charcoal-light",nightTheme:e.find("#field-night-theme").val()||"charcoal-dark",dayStart:e.find("#field-day-start").val()||"07:00",nightStart:e.find("#field-night-start").val()||"19:00"},o={title:e.find("#field-site-title").val().trim(),tagline:e.find("#field-tagline").val().trim(),fontFamily:e.find("#field-font-family").val()||"Roboto",fontSize:parseInt(e.find("#field-font-size").val(),10)||16,theme:l.baseTheme?l.theme:s?m.dayTheme:e.find("#field-theme").val(),...l.baseTheme?{baseTheme:l.baseTheme}:{},autoTheme:m,adminTheme:t,layoutOptions:{spacerSize:parseInt(e.find("#field-spacer-size").val(),10)||40},seo:{defaultTitle:e.find("#field-seo-title").val().trim(),titleSeparator:e.find("#field-seo-separator").val()||" | ",defaultDescription:e.find("#field-seo-desc").val().trim()},footer:{copyright:e.find("#field-footer-copy").val().trim(),links:l.footer?.links||[]},social:{twitter:e.find("#field-social-twitter").val().trim(),facebook:e.find("#field-social-facebook").val().trim(),instagram:e.find("#field-social-instagram").val().trim(),linkedin:e.find("#field-social-linkedin").val().trim(),github:e.find("#field-social-github").val().trim(),youtube:e.find("#field-social-youtube").val().trim()},smtp:{host:e.find("#field-smtp-host").val().trim(),port:parseInt(e.find("#field-smtp-port").val(),10)||587,user:e.find("#field-smtp-user").val().trim(),pass:e.find("#field-smtp-pass").val(),secure:e.find("#field-smtp-secure").prop("checked"),fromAddress:e.find("#field-smtp-from-address").val().trim(),fromName:e.find("#field-smtp-from-name").val().trim()},backToTop:{enabled:e.find("#field-btt-enabled").prop("checked"),scrollThreshold:parseInt(e.find("#field-btt-threshold").val(),10)||300,position:e.find("#field-btt-position").val()||"bottom-right",offset:parseInt(e.find("#field-btt-offset").val(),10)||32,bottomOffset:parseInt(e.find("#field-btt-bottom-offset").val(),10)||32,label:e.find("#field-btt-label").val().trim(),smooth:e.find("#field-btt-smooth").prop("checked")},cookieConsent:{enabled:e.find("#field-cc-enabled").prop("checked"),message:e.find("#field-cc-message").val().trim(),acceptAllText:e.find("#field-cc-accept-all").val().trim(),rejectAllText:e.find("#field-cc-reject-all").val().trim(),customizeText:e.find("#field-cc-customize").val().trim(),savePreferencesText:e.find("#field-cc-save-prefs").val().trim(),privacyPolicyText:e.find("#field-cc-privacy-text").val().trim(),privacyPolicyUrl:e.find("#field-cc-privacy-url").val().trim(),cookiePolicyText:e.find("#field-cc-cookie-text").val().trim(),cookiePolicyUrl:e.find("#field-cc-cookie-url").val().trim(),position:e.find("#field-cc-position").val(),layout:e.find("#field-cc-layout").val(),theme:e.find("#field-cc-theme").val(),showFunctional:e.find("#field-cc-show-functional").prop("checked"),showAnalytics:e.find("#field-cc-show-analytics").prop("checked"),showMarketing:e.find("#field-cc-show-marketing").prop("checked"),consentVersion:e.find("#field-cc-version").val().trim()||"1.0"}},g=e.find("#save-settings-btn");g.prop("disabled",!0);try{await C.settings.save(o),Domma.theme.set(t),E.toast("Settings saved.",{type:"success"})}catch{E.toast("Failed to save settings.",{type:"error"})}finally{g.prop("disabled",!1)}}),e.find("#send-test-email-btn").on("click",async()=>{const t=e.find("#field-test-email-to").val().trim(),s=e.find("#test-email-result").get(0),m=e.find("#send-test-email-btn");m.prop("disabled",!0),s&&(s.textContent="Sending\u2026",s.style.color="");try{const o=await v("/settings/test-email",{method:"POST",body:JSON.stringify({to:t||void 0})});s&&(s.textContent=o.message||"Test email sent.",s.style.color="var(--dm-success,#4ade80)")}catch(o){s&&(s.textContent=o.message||"Failed to send test email.",s.style.color="var(--dm-danger,#f87171)")}finally{m.prop("disabled",!1)}}),e.find("#save-breadcrumbs-btn").on("click",async()=>{const t=e.find("#save-breadcrumbs-btn");t.prop("disabled",!0);try{const s={enabled:e.find("#field-breadcrumbs-enabled").prop("checked"),homeLabel:e.find("#field-breadcrumbs-home-label").val().trim()||"Home",position:e.find("#field-breadcrumbs-position").val()||"TL",offsetX:parseInt(e.find("#field-bc-offset-x").val(),10)||8,offsetY:parseInt(e.find("#field-bc-offset-y").val(),10)||8};await C.settings.save({breadcrumbs:s}),l.breadcrumbs=s,E.toast("Breadcrumbs settings saved.",{type:"success"})}catch{E.toast("Failed to save breadcrumbs settings.",{type:"error"})}finally{t.prop("disabled",!1)}}),e.find("#save-css-btn").on("click",async()=>{const t=a?a.getValue():e.find("#field-custom-css").val(),s=e.find("#save-css-btn");s.prop("disabled",!0);try{await v("/settings/custom-css",{method:"PUT",body:JSON.stringify({css:t})}),E.toast("Custom CSS saved.",{type:"success"})}catch(m){E.toast(m.message||"Failed to save CSS.",{type:"error"})}finally{s.prop("disabled",!1)}});async function r(){try{const t=await v("/cache/status"),s=t.enabled?`enabled (${t.driver})`:"disabled",m=t.maxItems?` \xB7 ${t.size} / ${t.maxItems} entries`:"";e.find("#cache-status").text(s+m)}catch{e.find("#cache-status").text("unknown")}try{const{entries:t}=await v("/cache/keys");A(e.find("#cache-keys-table"),t),e.find("#cache-key-count").text(`(${t.length})`)}catch(t){e.find("#cache-keys-table").get(0).textContent="Failed to load keys: "+t.message}}await r(),e.find("#btn-clear-cache").on("click",async()=>{if(!await E.confirm("Clear the entire response cache?"))return;const t=e.find("#btn-clear-cache");t.prop("disabled",!0);try{await v("/cache/clear",{method:"POST"}),E.toast("Cache cleared.",{type:"success"}),await r()}catch(s){E.toast(s.message||"Failed to clear cache.",{type:"error"})}finally{t.prop("disabled",!1)}}),e.find("#btn-refresh-cache-keys").on("click",r)}};function A(e,k){const l=e.get(0);if(!l)return;for(;l.firstChild;)l.removeChild(l.firstChild);if(!k.length){const f=document.createElement("p");f.className="text-muted",f.style.fontSize=".875rem",f.textContent="Cache is empty.",l.appendChild(f);return}const p=[...k].reverse(),h=document.createElement("table");h.className="table table-compact";const b=document.createElement("thead"),y=document.createElement("tr");for(const f of["Key","Tags","Expires"]){const i=document.createElement("th");i.textContent=f,y.appendChild(i)}b.appendChild(y),h.appendChild(b);const u=document.createElement("tbody");for(const f of p){const i=document.createElement("tr"),d=document.createElement("td");d.style.fontFamily="monospace",d.style.fontSize="12px",d.textContent=f.key,i.appendChild(d);const n=document.createElement("td");n.style.fontSize="12px";for(const a of f.tags){const r=document.createElement("span");r.className="badge",r.style.marginRight="4px",r.style.fontSize="11px",r.textContent=a,n.appendChild(r)}i.appendChild(n);const c=document.createElement("td");if(c.style.fontSize="12px",c.style.color="var(--dm-text-muted, #6b7280)",f.expiresAt===null)c.textContent="no expiry";else{const a=f.expiresAt-Date.now();a<=0?c.textContent="expired":a<6e4?c.textContent=`${Math.round(a/1e3)}s`:a<36e5?c.textContent=`${Math.round(a/6e4)}m`:c.textContent=`${Math.round(a/36e5)}h`}i.appendChild(c),u.appendChild(i)}h.appendChild(u),l.appendChild(h)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as a}from"../api.js";function
|
|
1
|
+
import{api as a,getUser as N}from"../api.js";function I(c){return{string:"string",email:"email",tel:"string",number:"number",textarea:"textarea",select:"select",radio:"select",checkbox:"boolean","checkbox-group":"select",date:"string",time:"string",url:"string",hidden:"string"}[c]||"string"}export const userEditorView={templateUrl:"/admin/js/templates/user-editor.html",async onMount(c){const b=window.location.hash.match(/#\/users\/edit\/([^/]+)/),u=b?b[1]:null,r=!!u,C=E.loader(c.get(0),{type:"dots"});let o,m,y,v;try{[o,m,y,v]=await Promise.all([r?a.users.get(u).catch(()=>null):Promise.resolve(null),a.collections.listEntries("roles",{limit:100}).catch(()=>null),a.collections.get("user-profiles").catch(()=>null),a.projects.list().catch(()=>[])])}finally{C.destroy()}if(r&&!o){E.toast("User not found.",{type:"error"}),R.navigate("/users");return}c.find("#editor-title").text(r?"Edit User":"New User"),c.find("#cancel-btn").on("click",()=>R.navigate("/users"));const O=N(),h=e=>(m?.entries||[]).find(i=>i.data.name===e)?.data?.level??99,B=h(O?.role),j=(m?.entries||[]).filter(e=>(e.data.level??99)>B||r&&e.data.name===o?.role).map(e=>({value:e.data.name,label:e.data.label})).sort((e,i)=>h(e.value)-h(i.value)),S=[{value:"admin",label:"Admin"},{value:"user",label:"User"}],A=j.length>0?j:S,P=(v||[]).map(e=>({value:e.slug,label:e.name||e.slug})),q=[{value:"",label:"(none)"},...P],L={name:{type:"string",required:!0,minLength:2,label:"Full Name",formConfig:{placeholder:"Jane Smith"}},email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"jane@example.com"}},role:{type:"select",required:!0,label:"Primary role",options:A,formConfig:{tooltip:"Shown as the user's badge. Used for hierarchy comparisons."}},additionalRoles:{type:"multiselect",label:"Additional roles",options:A,formConfig:{placeholder:"",tooltip:"Optional \u2014 extra roles whose permissions also apply. Useful for someone who is both, say, a recruiter AND a content editor."}},ownedByProject:{type:"select",label:"Owned by project",options:q,formConfig:{tooltip:"Administrative tag \u2014 controls where this user appears in the sidebar Projects section. Mapped to meta.project."}},projects:{type:"multiselect",label:"Project access",options:P,formConfig:{placeholder:"",tooltip:"Access scope \u2014 limits which artefacts the user can see and edit. Leave empty for no restriction."}},isActive:{type:"boolean",label:"Active account"},password:{type:"password",required:!r,minLength:r?0:8,label:r?"New Password":"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:r?"Leave blank to keep current password":"Minimum 8 characters"}}},T=o?{name:o.name||"",email:o.email||"",role:o.role||"user",additionalRoles:Array.isArray(o.additionalRoles)?o.additionalRoles:[],ownedByProject:o.meta?.project||"",projects:Array.isArray(o.projects)?o.projects:[],isActive:o.isActive!==!1}:{role:"user",additionalRoles:[],ownedByProject:"",projects:[],isActive:!0};F.render("#user-form-container",L,T,{layout:"stacked",submitText:r?"Update User":"Create User",onSubmit:async e=>{if(r&&e.password&&e.password.length<8)return E.toast("Password must be at least 8 characters.",{type:"warning"}),!1;const t=(Array.isArray(e.additionalRoles)?e.additionalRoles:[]).filter(s=>s&&s!==e.role).filter((s,d,n)=>n.indexOf(s)===d),l=(Array.isArray(e.projects)?e.projects:[]).filter(Boolean).filter((s,d,n)=>n.indexOf(s)===d),p=e.ownedByProject||null,g={name:e.name,email:e.email,role:e.role,additionalRoles:t,projects:l,meta:{...o?.meta||{},project:p},isActive:!!e.isActive};e.password&&(g.password=e.password);try{let s;r?s=await a.users.update(u,g):s=await a.users.create(g);const d=y?.fields||[];if(d.length>0){const n={};for(const w of d)w.name in e&&(n[w.name]=e[w.name]);const x=r?u:s?.id;x&&Object.keys(n).length>0&&await a.users.update(x,{profile:n})}E.toast("User saved successfully.",{type:"success"}),R.navigate("/users")}catch(s){return E.toast(`Save failed: ${s.message||"Unknown error"}`,{type:"error"}),!1}}});const U=y?.fields||[];if(U.length>0){c.find("#profile-card").show();const e={},i={};for(const t of U){const l={type:I(t.type),label:t.label||t.name};t.required&&(l.required=!0),t.placeholder&&(l.formConfig={placeholder:t.placeholder}),t.options&&Array.isArray(t.options)&&(l.options=t.options.map(p=>typeof p=="string"?{value:p,label:p}:p)),e[t.name]=l,i[t.name]=o?.profile?.[t.name]??""}F.render("#profile-form-container",e,i,{layout:"stacked",submitText:r?"Update Profile":"Save Profile",onSubmit:async t=>{const f=u;if(!f)return E.toast("Save the user first, then update the profile.",{type:"info"}),!1;try{await a.users.update(f,{profile:t}),E.toast("Profile saved.",{type:"success"})}catch(l){return E.toast(`Profile save failed: ${l.message||"Unknown error"}`,{type:"error"}),!1}}})}Domma.icons.scan()}};
|
package/admin/js/views/users.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as d,getUser as A}from"../api.js";import{filterByProject as k,getProjectFromHash as M}from"../lib/project-context.js";import{keepTableIconsAlive as j}from"../lib/table-icons.js";export const usersView={templateUrl:"/admin/js/templates/users.html",async onMount(o){const m=A(),f=E.loader(o.get(0),{type:"dots"});let[n,y]=await Promise.all([d.users.list().catch(()=>[]),d.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]}))]);f.destroy();const p=M();p&&(n=k(n,p));const c=y.entries||[],u=r=>{T.create("#users-table",{data:r,columns:[{key:"name",title:"Name"},{key:"email",title:"Email"},{key:"role",title:"Role",render:(e,t)=>{const a=c.find(s=>s.data?.name===e),i=a?.data?.badgeClass||"badge-secondary",b=a?.data?.label||e,g=`<span class="badge ${i}">${b}</span>`,l=Array.isArray(t.additionalRoles)?t.additionalRoles.filter(s=>s&&s!==e).map(s=>`<span class="badge badge-outline" title="Additional role" style="margin-left:.25rem;">${c.find(h=>h.data?.name===s)?.data?.label||s}</span>`).join(""):"";return g+l}},{key:"isActive",title:"Status",render:e=>e?'<span class="badge badge-success">Active</span>':'<span class="badge badge-secondary">Inactive</span>'},{key:"lastLogin",title:"Last login",render:e=>e?D(e).format("DD MMM YYYY HH:mm"):"Never"},{key:"actions",title:"Actions",render:(e,t)=>{const a=t.id===m?.id,i=Object.fromEntries(c.map(l=>[l.data?.name,l.data?.level??99]));return(i[m?.role]??99)<(i[t.role]??99)
|
|
1
|
+
import{api as d,getUser as A}from"../api.js";import{filterByProject as k,getProjectFromHash as M}from"../lib/project-context.js";import{keepTableIconsAlive as j}from"../lib/table-icons.js";export const usersView={templateUrl:"/admin/js/templates/users.html",async onMount(o){const m=A(),f=E.loader(o.get(0),{type:"dots"});let[n,y]=await Promise.all([d.users.list().catch(()=>[]),d.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]}))]);f.destroy();const p=M();p&&(n=k(n,p));const c=y.entries||[],u=r=>{T.create("#users-table",{data:r,columns:[{key:"name",title:"Name"},{key:"email",title:"Email"},{key:"role",title:"Role",render:(e,t)=>{const a=c.find(s=>s.data?.name===e),i=a?.data?.badgeClass||"badge-secondary",b=a?.data?.label||e,g=`<span class="badge ${i}">${b}</span>`,l=Array.isArray(t.additionalRoles)?t.additionalRoles.filter(s=>s&&s!==e).map(s=>`<span class="badge badge-outline" title="Additional role" style="margin-left:.25rem;">${c.find(h=>h.data?.name===s)?.data?.label||s}</span>`).join(""):"";return g+l}},{key:"isActive",title:"Status",render:e=>e?'<span class="badge badge-success">Active</span>':'<span class="badge badge-secondary">Inactive</span>'},{key:"lastLogin",title:"Last login",render:e=>e?D(e).format("DD MMM YYYY HH:mm"):"Never"},{key:"actions",title:"Actions",render:(e,t)=>{const a=t.id===m?.id,i=Object.fromEntries(c.map(l=>[l.data?.name,l.data?.level??99]));return!((i[m?.role]??99)<(i[t.role]??99))&&!a?"":`
|
|
2
2
|
<a href="#/users/edit/${t.id}" class="btn btn-sm btn-ghost" data-tooltip="Edit"><span data-icon="edit" data-icon-size="14"></span></a>
|
|
3
3
|
${a?"":`<button class="btn btn-sm btn-ghost btn-delete-user" data-id="${t.id}" data-name="${v(t.name)}" data-tooltip="Delete"><span data-icon="trash" data-icon-size="14"></span></button>`}
|
|
4
|
-
|
|
4
|
+
`}}],emptyMessage:"No users found."}),Domma.icons.scan(),document.querySelectorAll("#users-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})})};u(n),j(o.get(0)),o.off("click",".btn-delete-user").on("click",".btn-delete-user",async function(){const r=$(this).data("id"),e=$(this).data("name");if(await E.confirm(`Delete user <strong>${e}</strong>? This cannot be undone.`))try{await d.delete(`/users/${r}`),E.toast("User deleted.",{type:"success"}),n=n.filter(a=>a.id!==r),u(n)}catch(a){E.toast(`Failed to delete: ${a.message}`,{type:"error"})}})}};function v(o){return String(o).replace(/"/g,""")}
|
package/package.json
CHANGED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(){"use strict";const _=document.createElement("style");_.textContent=["[data-ctx], [data-collection-table] { cursor: context-menu; }","[data-ctx] a, [data-ctx] button, [data-collection-table] a, [data-collection-table] button,","[data-ctx] input, [data-collection-table] input { cursor: pointer; }",'[data-ctx] input[type="text"], [data-collection-table] input[type="text"] { cursor: text; }',".dm-ctx-hidden { display: none !important; }",".dm-ctx-menu { position: fixed; z-index: 10000; min-width: 200px; max-width: 300px; max-height: 60vh; overflow-y: auto;background: var(--dm-surface, #1c1c30); border: 1px solid var(--dm-border, #2a2a40); border-radius: 6px;box-shadow: var(--dm-shadow-lg, 0 8px 24px rgba(0,0,0,.4)); padding: .3rem; font-size: .83rem;color: var(--dm-text, #e5e5e5); }",".dm-ctx-menu button.dm-ctx-row { display: flex; align-items: center; gap: .5rem; width: 100%; text-align: left;background: none; border: none; color: inherit; font: inherit; padding: .35rem .6rem; border-radius: 4px; cursor: pointer; }",".dm-ctx-menu button.dm-ctx-row:hover { background: var(--dm-surface-2, rgba(255,255,255,.06)); }",".dm-ctx-heading { padding: .3rem .6rem; font-size: .72rem; text-transform: uppercase; letter-spacing: .04em;color: var(--dm-text-muted, #9a9aac); }",".dm-ctx-input { width: 100%; font-size: .8rem; padding: .3rem .5rem; border-radius: 4px;border: 1px solid var(--dm-border, #2a2a40); background: var(--dm-surface-2, rgba(255,255,255,.04)); color: inherit; cursor: text; }",".dm-ctx-chips { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; margin: 0 0 .6rem; }",".dm-ctx-chip { display: inline-flex; align-items: center; gap: .35rem; font-size: .76rem; padding: .2rem .6rem;border-radius: 999px; background: var(--dm-surface-2, rgba(255,255,255,.08)); border: 1px solid var(--dm-border, #2a2a40); }",".dm-ctx-chip button { background: none; border: none; cursor: pointer; color: inherit; font-size: .9rem; line-height: 1; padding: 0; }",".dm-ctx-empty-note { padding: .75rem; font-size: .85rem; color: var(--dm-text-muted, #9a9aac); }"].join(`
|
|
2
|
+
`),document.head.appendChild(_);function v(t){if(t._dmTable)return{fields:t._dmTable.columns.filter(n=>n.key!=="_cta").map(n=>({name:n.key,label:n.title||n.key})),entries:t._dmTable.rows.map((n,e)=>({id:String(e),data:n}))};if(t._dmCtxModel)return t._dmCtxModel;try{t._dmCtxModel=JSON.parse(atob(t.dataset.ctx))}catch{t._dmCtxModel=null}return t._dmCtxModel}function M(t,n){if(n.field==="*"){const d=String(n.value).toLowerCase();return Object.values(t.data||{}).some(o=>String(o??"").toLowerCase().includes(d))}const e=String(t.data?.[n.field]??"");switch(n.op){case"eq":return e===String(n.value);case"ne":return e!==String(n.value);case"contains":return e.toLowerCase().includes(String(n.value).toLowerCase());case"gt":case"lt":{const d=parseFloat(e),o=parseFloat(n.value);if(!Number.isNaN(d)&&!Number.isNaN(o))return n.op==="gt"?d>o:d<o;const l=Date.parse(e),a=Date.parse(n.value);return!Number.isNaN(l)&&!Number.isNaN(a)?n.op==="gt"?l>a:l<a:n.op==="gt"?e>String(n.value):e<String(n.value)}default:return!0}}function g(t){const n=t._dmCtxFilters||[],e=v(t);if(!e)return;const d=e.entries.filter(l=>n.every(a=>M(l,a))),o=new Set(d.map(l=>l.id));t._dmTable?t._dmTable.render(d.map(l=>l.data)):t._dmCtx&&t._dmCtx.instance&&t._dmCtx.instance.setItems?t._dmCtx.instance.setItems(t._dmCtx.items.filter(l=>o.has(String(l._entryId)))):t._dmCtxSlides?$(t,o):t.querySelectorAll("[data-entry-id]").forEach(l=>{l.classList.toggle("dm-ctx-hidden",!o.has(l.dataset.entryId))}),T(t,n),k(t,d.length,n.length)}function $(t,n){if(t._dmCarousel&&typeof t._dmCarousel.destroy=="function")try{t._dmCarousel.destroy()}catch{}const e=document.createElement("div");e.className="carousel-track",t._dmCtxSlides.forEach(d=>{n.has(d.dataset.entryId)&&e.appendChild(d.cloneNode(!0))}),t.replaceChildren(e),e.children.length&&typeof Domma<"u"&&Domma.elements?.carousel&&(t._dmCarousel=Domma.elements.carousel(t,t._dmCarouselOpts||{}))}const D={eq:"=",ne:"\u2260",contains:"contains",gt:">",lt:"<"};function T(t,n){let e=t._dmCtxBar;if(!n.length){e&&(e.remove(),t._dmCtxBar=null);return}e||(e=document.createElement("div"),e.className="dm-ctx-chips",t.parentNode.insertBefore(e,t),t._dmCtxBar=e),e.replaceChildren(),n.forEach((o,l)=>{const a=document.createElement("span");a.className="dm-ctx-chip";const s=String(o.value),m=s.length>24?s.slice(0,24)+"\u2026":s;a.appendChild(document.createTextNode(o.field==="*"?`search \u201C${m}\u201D`:`${o.label||o.field} ${D[o.op]||o.op} \u201C${m}\u201D`));const r=document.createElement("button");r.type="button",r.textContent="\xD7",r.title="Remove filter",r.addEventListener("click",()=>{t._dmCtxFilters.splice(l,1),g(t)}),a.appendChild(r),e.appendChild(a)});const d=document.createElement("button");d.type="button",d.className="dm-ctx-chip",d.style.cursor="pointer",d.textContent="Clear all",d.addEventListener("click",()=>{t._dmCtxFilters=[],g(t)}),e.appendChild(d)}function k(t,n,e){if(t._dmCtxEmptyNote&&(t._dmCtxEmptyNote.remove(),t._dmCtxEmptyNote=null),n||!e)return;const d=document.createElement("div");d.className="dm-ctx-empty-note",d.textContent="No entries match the current filters.",t.appendChild(d),t._dmCtxEmptyNote=d}let u=null;function x(){u&&(u.remove(),u=null,document.removeEventListener("mousedown",E,!0),document.removeEventListener("keydown",N,!0),window.removeEventListener("scroll",S,!0))}const E=t=>{u&&!u.contains(t.target)&&x()},N=t=>{t.key==="Escape"&&x()},S=t=>{u&&!u.contains(t.target)&&x()};function f(t,n){const e=document.createElement("button");return e.type="button",e.className="dm-ctx-row",e.textContent=t,e.addEventListener("click",n),e}function L(t){const n=document.createElement("div");return n.className="dm-ctx-heading",n.textContent=t,n}const h=(t,n=24)=>{const e=String(t);return e.length>n?e.slice(0,n)+"\u2026":e};function z(t,n,e,d,o){x();const l=v(e);if(!l)return;const a=document.createElement("div");a.className="dm-ctx-menu",u=a;const s=(i,c,y,p)=>{(e._dmCtxFilters=e._dmCtxFilters||[]).push({field:i,label:c,op:y,value:p}),g(e),x()};if(o&&d){const i=d.data?.[o.name]??"";a.appendChild(L(o.label||o.name)),a.appendChild(f(`Filter: = \u201C${h(i)}\u201D`,()=>s(o.name,o.label,"eq",i))),a.appendChild(f(`Filter: \u2260 \u201C${h(i)}\u201D`,()=>s(o.name,o.label,"ne",i)));const c=String(i);(c!==""&&!Number.isNaN(parseFloat(c))||!Number.isNaN(Date.parse(c)))&&(a.appendChild(f(`Filter: > \u201C${h(i)}\u201D`,()=>s(o.name,o.label,"gt",i))),a.appendChild(f(`Filter: < \u201C${h(i)}\u201D`,()=>s(o.name,o.label,"lt",i))))}else if(d){a.appendChild(L("Filter by"));for(const i of l.fields){const c=d.data?.[i.name];c==null||String(c)===""||a.appendChild(f(`${i.label||i.name}: \u201C${h(c,20)}\u201D`,()=>s(i.name,i.label,"eq",c)))}}const m=document.createElement("div");m.style.padding=".35rem .6rem";const r=document.createElement("input");r.type="text",r.className="dm-ctx-input",r.placeholder="Search\u2026",r.addEventListener("mousedown",i=>i.stopPropagation()),r.addEventListener("keydown",i=>{i.key==="Enter"&&r.value.trim()&&s("*","search","contains",r.value.trim())}),m.appendChild(r),a.appendChild(m);const b=e._dmCtxFilters||[];if(b.length){const i=document.createElement("div");i.style.cssText="border-top:1px solid var(--dm-border,#2a2a40);margin:.25rem 0;",a.appendChild(i),a.appendChild(f(`Clear all filters (${b.length})`,()=>{e._dmCtxFilters=[],g(e),x()}))}document.body.appendChild(a);const C=a.getBoundingClientRect();a.style.left=Math.min(t,window.innerWidth-C.width-8)+"px",a.style.top=Math.min(n,window.innerHeight-C.height-8)+"px",document.addEventListener("mousedown",E,!0),document.addEventListener("keydown",N,!0),window.addEventListener("scroll",S,!0),setTimeout(()=>r.focus(),0)}function I(t,n,e){if(t._dmTable){const s=n.closest("tbody tr");if(!s)return{entry:null,field:null};const m=n.closest("td"),r=s.querySelectorAll("td"),b=Array.from(r).map(c=>c.textContent.trim()),C=e.entries.find(c=>e.fields.every((y,p)=>String(c.data?.[y.name]??"").trim()===(b[p]??"").trim()))||null,i=m&&e.fields[m.cellIndex]||null;return{entry:C,field:i}}const d=n.closest("[data-entry-id]");if(!d)return{entry:null,field:null};const o=e.entries.find(s=>s.id===d.dataset.entryId)||null;if(!o)return{entry:null,field:null};const l=(n.textContent||"").trim().slice(0,80);let a=null;if(l){const s=e.fields.filter(m=>{const r=String(o.data?.[m.name]??"");return r&&(r.includes(l)||l.includes(r))});s.length===1&&(a=s[0])}return{entry:o,field:a}}document.addEventListener("contextmenu",t=>{const n=t.target.closest("[data-ctx], [data-collection-table]");if(!n)return;const e=v(n);if(!e||!e.fields?.length)return;t.preventDefault();const{entry:d,field:o}=I(n,t.target,e);z(t.clientX,t.clientY,n,d,o)})})();
|
package/public/js/site.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{makeBadgeEl as _,applyColour as A,applyPill as I}from"/public/js/menu-decor.mjs";import{enhancePickerFields as D}from"/public/js/forms.js?v=20260722-pickers";$(()=>{const C=window.__CMS_NAV__||{},y=window.__CMS_SITE__||{};if(y.autoTheme?.enabled){let o=function(e){const u=(e||"07:00").split(":");return+u[0]*60+(+u[1]||0)},a=function(){const e=new Date,u=e.getHours()*60+e.getMinutes();return u>=o(t.dayStart)&&u<o(t.nightStart)?t.dayTheme:t.nightTheme};const t=y.autoTheme;Domma.theme.set(a()),setInterval(()=>Domma.theme.set(a()),6e4)}const b=$("#site-navbar");if(b.length&&C.brand){let a=function(i,n=0){return Array.isArray(i)?i.filter(c=>!(c&&c.type==="separator"&&n===0)).map(c=>c&&c.type==="separator"?{divider:!0}:Array.isArray(c?.items)?{...c,items:a(c.items,n+1)}:c):i},e=function(i){return Array.isArray(i)?i.filter(n=>!(n&&n.type==="separator")):[]};const t={...C.brand},o=t.size&&t.size!=="md"?` navbar-brand-${t.size}`:"";if(t.logo||t.icon||t.tagline){let i="";t.logo?i+=`<img src="${t.logo}" class="navbar-brand-logo" alt="${t.text||""}">`:t.icon&&(i+=`<span data-icon="${t.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),t.text&&(i+=`<span class="navbar-brand-text${o}">${t.text}</span>`),t.tagline&&(i+=`<small class="navbar-brand-tagline">${t.tagline}</small>`),t.html=i}else o&&t.text&&(t.html=`<span class="navbar-brand-text${o}">${t.text}</span>`);Domma.elements.navbar("#site-navbar",{brand:t,items:a(C.items||[]),variant:C.variant||"dark",position:C.position||"sticky",collapsible:!0}),Domma.icons.scan("#site-navbar");const u=b.get(0);if(C.appearOnHover){const i=window.matchMedia("(min-width: 993px)");u.querySelectorAll(".navbar-dropdown").forEach(n=>{n.addEventListener("mouseenter",()=>{i.matches&&n.classList.add("open")}),n.addEventListener("mouseleave",()=>{i.matches&&n.classList.remove("open")})})}const d=u.querySelector(".navbar-toggle"),h=u.querySelector(".navbar-collapse");if(d&&h){let i=null;const n=()=>{h.classList.contains("show")&&d.click()},c=()=>{i&&(i.remove(),i=null),document.body.classList.remove("dm-drawer-open")},s=()=>{i||(i=document.createElement("div"),i.className="dm-navbar-backdrop",i.addEventListener("click",n),document.body.appendChild(i),document.body.classList.add("dm-drawer-open"))};d.addEventListener("click",()=>{requestAnimationFrame(()=>{h.classList.contains("show")?s():c()})}),h.addEventListener("click",p=>{p.target.closest("a")&&requestAnimationFrame(c)}),document.addEventListener("keydown",p=>{p.key==="Escape"&&h.classList.contains("show")&&n()})}const m=e(C.items||[]);u.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(i=>{const n=m[parseInt(i.dataset.index,10)];if(!n)return;const c=i.dataset.subindex!=null,s=c&&Array.isArray(n.items)?n.items[parseInt(i.dataset.subindex,10)]:n;if(!s)return;s.colour&&A(i,s.colour),s.pill&&!c&&I(i,s.pill);const p=_(s.badge);p&&i.appendChild(p)})}const v=$("#site-footer");if(v.length){let u=function(r){return String(r??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},d=function(r){return u(r).replace(/'/g,"'")},h=function(r){if(r&&r.type==="separator")return'<li class="footer-sep"><hr></li>';const l=d(r.url||"#"),g=u(r.text||""),k=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(h).join("")}</ul>`:"";return`<li><a href="${l}">${g}</a>${k}</li>`},m=function(r,l){return(r||[]).forEach(g=>{g&&g.type==="separator"||(l.push(g),Array.isArray(g.items)&&g.items.length&&m(g.items,l))}),l};const t=y.social||{},o={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let a='<div class="footer-inner container">';const e=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(e.copyright&&(a+=`<p>${u(e.copyright)}</p>`),e.primary.length&&(a+=`<nav class="footer-nav footer-nav--primary"><ul>${e.primary.map(h).join("")}</ul></nav>`),e.legal.length&&(a+=`<nav class="footer-nav footer-nav--legal"><ul>${e.legal.map(h).join("")}</ul></nav>`),y.footer){const r=Object.keys(o).filter(l=>t[l]);r.length&&(a+='<div class="footer-social">',r.forEach(l=>{const{label:g,svg:k}=o[l];a+=`<a href="${t[l]}" target="_blank" rel="noopener noreferrer" aria-label="${g}" class="footer-social-link">${k}</a>`}),a+="</div>")}a+="</div>",v.html(a);const i=[...m(e.primary,[]),...m(e.legal,[])];v.get(0).querySelectorAll(".footer-nav a").forEach((r,l)=>{const g=i[l];if(!g)return;g.colour&&A(r,g.colour);const k=_(g.badge);k&&r.appendChild(k)});const c=S.get("reduced_motion"),s=c!==null?!!c:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),p=v.get(0).querySelector(".footer-inner");if(p){const r=document.createElement("input");r.type="checkbox",r.className="form-switch-input",r.id="dm-motion-switch",r.checked=s,r.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const l=document.createElement("span");l.className="form-switch-label",l.textContent="Reduce motion";const g=document.createElement("label");g.className="form-switch footer-motion-switch",g.title="Reduce motion",g.appendChild(r),g.appendChild(l),p.appendChild(g)}}$("#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 t={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide"};if(this.dataset.animationDuration){const o=parseInt(this.dataset.animationDuration,10);Number.isFinite(o)&&(t.animationDuration=o)}this.dataset.animationEasing&&(t.animationEasing=this.dataset.animationEasing),Domma.elements.carousel(this,t)}),f.find(".dm-countdown").each(function(){const t={autoStart:!0};this.dataset.to&&(t.targetDate=new Date(this.dataset.to)),this.dataset.duration&&(t.duration=parseInt(this.dataset.duration,10)),this.dataset.format&&(t.format=this.dataset.format),Domma.elements.timer(this,t)}),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(){Domma.elements.progression(this,{layout:this.dataset.layout||"vertical",theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}document.querySelectorAll(".page-body .row[data-reveal]").forEach(t=>{const o=t.dataset.revealMode||"stagger",a=t.dataset.revealAnimation||"slide-up",e=parseInt(t.dataset.revealDuration,10)||400,u=parseInt(t.dataset.revealStagger,10)||60,d=parseInt(t.dataset.revealDelay,10)||0,h=t.dataset.revealDirection==="rtl",m=Array.from(t.children),i={"slide-up":"translateY(30px)","slide-down":"translateY(-30px)","slide-left":"translateX(30px)","slide-right":"translateX(-30px)",zoom:"scale(0.85)",flip:"perspective(600px) rotateX(15deg)"};m.forEach((n,c)=>{n.style.opacity="0",n.style.transform=i[a]||"",n.style.transition=`opacity ${e}ms ease, transform ${e}ms ease`;const s=h?m.length-1-c:c;n.style.transitionDelay=o==="stagger"?`${d+s*u}ms`:`${d}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const n=new IntersectionObserver(c=>{c.forEach(s=>{s.isIntersecting&&(s.target.offsetWidth,s.target.style.opacity="1",s.target.style.transform="none",n.unobserve(s.target))})},{threshold:.1});m.forEach(c=>n.observe(c))}))}),f.find(".card[data-collapsible]").each(function(){const t=this.querySelector(".card-header");t&&t.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),f.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const t=this.dataset.soTarget,o=document.getElementById(t);if(!o)return;const a=E.slideover({title:o.dataset.soTitle||"",size:o.dataset.soSize||"md",position:o.dataset.soPosition||"right"});o.style.display="",a.setContent(o),a.open()})})}if(typeof $.setup=="function"){const t=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(o=>{try{const a=atob(o.dataset.config),e=JSON.parse(a);Object.assign(t,e)}catch{}}),Object.keys(t).length>0){const o={};for(const[a,e]of Object.entries(t)){const u=e?.events?.click,{confirm:d,toast:h,alert:m,prompt:i,...n}=u||{};d||h||m||i?($(a).on("click",async function(s){if(s.preventDefault(),d&&!await E.confirm(d))return;let p=null;if(!(i&&(p=await E.prompt(i,{inputPlaceholder:n.promptPlaceholder||"",inputValue:n.promptDefault||""}),p===null))){if(n.target){const r=$(n.target);n.toggleClass&&r.toggleClass(n.toggleClass),n.addClass&&r.addClass(n.addClass),n.removeClass&&r.removeClass(n.removeClass),p!==null&&(n.setText&&r.text(p),n.setVal&&r.val(p),n.setAttr&&r.attr(n.setAttr,p))}n.href&&(window.location.href=n.href),h&&E.toast(h,{type:n.toastType||"success"}),m&&E.alert(m)}}),Object.keys(n).length&&(o[a]={...e,events:{...e.events,click:n}})):o[a]=e}$.setup(o)}}f.length&&x(f.get(0))});function x(C){C.querySelectorAll(".dm-cta-trigger").forEach(y=>{y.__ctaWired||(y.__ctaWired=!0,y.addEventListener("click",async()=>{const b=y.dataset.action,v=y.dataset.entry,w=y.dataset.confirm;let f=S.get("auth_token");if(!f){E.toast("Please log in to perform this action.",{type:"warning"});return}if(w&&!await E.confirm(w))return;const t=Array.from(y.childNodes).map(a=>a.cloneNode(!0));y.disabled=!0,y.textContent="Running\u2026";const o=a=>fetch(`/api/actions/${b}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({entryId:v})});try{let a=await o(f);if(a.status===401){const u=S.get("auth_refresh_token");if(u){const d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:u})});if(d.ok){const{token:h}=await d.json();S.set("auth_token",h),f=h,a=await o(f)}}}const e=await a.json().catch(()=>({}));if(!a.ok)throw new Error(e.error||e.message||`Error ${a.status}`);E.toast(e.message||"Action completed.",{type:"success"})}catch(a){E.toast(a.message||"Action failed.",{type:"error"})}finally{y.disabled=!1,y.textContent="",t.forEach(a=>y.appendChild(a)),Domma.icons.scan(y)}}))})}(function(){const y=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!y.length)return;const b=S.get("auth_token");function v(f,t){const o=document.createElement("div");o.className="dm-collection-display dm-collection-empty";const a=document.createElement("p");if(t){a.appendChild(document.createTextNode("Please "));const e=document.createElement("a");e.href="/login",e.textContent="sign in",a.appendChild(e),a.appendChild(document.createTextNode(" to view your entries."))}else a.textContent=f;return o.appendChild(a),o}function w(f,t){const o=document.createElement("template");o.innerHTML=t||"",f.replaceWith(o.content)}y.forEach(async f=>{const t=f.dataset.collectionAttrs;if(!t)return;if(!b){f.replaceChildren(v("",!0));return}const o=a=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({attrs:t})});try{let a=b,e=await o(a);if(e.status===401){const d=S.get("auth_refresh_token");if(d){const h=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:d})});if(h.ok){const{token:m}=await h.json();S.set("auth_token",m),a=m,e=await o(a)}}}if(!e.ok){const{error:d}=await e.json().catch(()=>({}));f.replaceChildren(v(d||"Unable to load entries.",!1));return}const{html:u}=await e.json();w(f,u)}catch{f.replaceChildren(v("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),x(document.body)},100)})(),(function(){const y=document.querySelectorAll("[data-collection-table]");!y.length||typeof T>"u"||!T.create||y.forEach(b=>{let v;try{v=JSON.parse(atob(b.dataset.payload))}catch{return}const{columns:w,rows:f,search:t,sortable:o,exportable:a,pageSize:e,empty:u,ctaConfig:d}=v;if(!w?.length)return;if(d){const i=w.findIndex(n=>n.key==="_cta");i!==-1&&(w[i]={key:"_cta",title:"",render:(n,c)=>{const s=document.createElement("button");if(s.className=`btn btn-${d.style||"primary"} dm-cta-trigger`,s.dataset.action=d.action||"",s.dataset.entry=c._entryId||"",d.confirm&&(s.dataset.confirm=d.confirm),d.icon){const p=document.createElement("span");p.dataset.icon=d.icon,s.appendChild(p),s.appendChild(document.createTextNode(" "))}return s.appendChild(document.createTextNode(d.label||"Run")),s}})}const h="col-table-"+Math.random().toString(36).slice(2,7),m=document.createElement("div");m.id=h,b.replaceChildren(m),T.create("#"+h,{data:f,columns:w,search:t,sortable:o,exportable:a,pageSize:e,emptyMessage:u}),d&&x(m)})})(),(function(){const y=document.querySelectorAll("[data-form-inline]");if(!y.length||typeof F>"u")return;function b(t,o){const a={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const u=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),d=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":u?"chooser":e.type,h={...e.formConfig||{}};h.span==="full"&&o&&(h.span=o);const m={type:d,label:e.label,required:e.required,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...h}};u&&(e.variant&&(m.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(m.multiple=!0),e.density&&(m.density=e.density),e.columns&&(m.columns=e.columns),e.accent&&(m.accent=e.accent),e.accentStyle&&(m.accentStyle=e.accentStyle),e.glow&&(m.glow=!0),e.glowColour&&(m.glowColour=e.glowColour),e.shadow&&(m.shadow=e.shadow),e.shadowColour&&(m.shadowColour=e.shadowColour)),a[e.name]=m}),a}function v(t){const o={};return(t||[]).forEach(a=>{if(!(!a.name||a.type==="page-break"||a.type==="spacer")&&(a.type==="select"||a.type==="multiselect")&&a.required){const e=(a.options||[])[0];e&&(o[a.name]=typeof e=="object"?e.value:e)}}),o}function w(t,o){(o||[]).forEach(a=>{if(a.type!=="date"||!a.name)return;const e=t.querySelector(`[name="${a.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function f(t,o,a){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=o,e.style.cssText=a?"color:var(--dm-danger,#f87171);margin-top:.75rem;":"color:var(--dm-success,#4ade80);margin-top:.75rem;"}y.forEach(t=>{let o;try{o=JSON.parse(atob(t.dataset.formInline))}catch{return}const a=o.fields||[],e=o.settings||{},u=e.columns||1,d=e.layout||"stacked",h=a.some(c=>c.type==="page-break"),m=async c=>{try{const s=t.querySelector('[name="website"]'),p=t.querySelector('[name="_t"]'),r=Object.assign({},c);s!==null&&(r._hp=s.value),p!==null&&(r._t=p.value);const l=await H.post(`/api/forms/submit/${o.slug}`,r);if(l&&l.error){f(t,l.error,!0);const g=new Error(l.error);throw g.formSubmitFailed=!0,g}if(l?.redirect){window.location.href=l.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);f(t,l?.message||e.successMessage||"Thank you for your submission.",!1)}catch(s){throw s.formSubmitFailed||f(t,s.message||"Submission failed. Please try again.",!0),s}};function i(c){const s=c.querySelector("form");if(!s)return;const p=document.createElement("div");p.className="fb-form-honeypot",p.setAttribute("aria-hidden","true");const r=document.createElement("input");r.name="website",r.type="text",r.tabIndex=-1,r.autocomplete="url",r.placeholder="https://",p.appendChild(r);const l=document.createElement("input");l.name="_t",l.type="hidden",l.value=Date.now(),p.appendChild(l),s.appendChild(p)}function n(){if(!window.FormLogicEngine||!a.some(s=>s.logic))return;const c=new window.FormLogicEngine.FormLogicRuntime(o,t);if(c.init(),t._formLogicRuntime=c,t.parentNode&&typeof MutationObserver<"u"){const s=new MutationObserver(function(p){for(const r of p)for(const l of r.removedNodes)if(l===t||l.nodeType===1&&l.contains&&l.contains(t)){c.destroy(),s.disconnect();return}});s.observe(t.parentNode,{childList:!0,subtree:!1})}}if(h&&F.wizard){const c=[];let s=[],p=o.title||"Step 1",r="";a.forEach(g=>{g.type==="page-break"?(c.push({title:p,description:r,fields:b(s,u)}),s=[],p=g.label||`Step ${c.length+1}`,r=g.description||""):g.type!=="spacer"&&s.push(g)}),c.push({title:p,description:r,fields:b(s,u)});const l=F.wizard(t,{schema:{steps:c},onSubmit:m});Promise.resolve(l).then(function(){w(t,a),D(t,a),e.honeypot!==!1&&i(t),n()})}else if(F.render){const c=F.render(t,b(a,u),v(a),{submitText:e.submitText||"Submit",layout:d,columns:u,onSubmit:m});Promise.resolve(c).then(function(){if(d==="grid"&&e.submitSpan==="full"){const s=t.querySelector(".form-buttons");s&&s.classList.add("col-span-full")}w(t,a),D(t,a),e.honeypot!==!1&&i(t),n()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
|
1
|
+
import{makeBadgeEl as _,applyColour as A,applyPill as I}from"/public/js/menu-decor.mjs";import{enhancePickerFields as D}from"/public/js/forms.js?v=20260722-pickers";$(()=>{const C=window.__CMS_NAV__||{},y=window.__CMS_SITE__||{};if(y.autoTheme?.enabled){let o=function(e){const l=(e||"07:00").split(":");return+l[0]*60+(+l[1]||0)},a=function(){const e=new Date,l=e.getHours()*60+e.getMinutes();return l>=o(t.dayStart)&&l<o(t.nightStart)?t.dayTheme:t.nightTheme};const t=y.autoTheme;Domma.theme.set(a()),setInterval(()=>Domma.theme.set(a()),6e4)}const v=$("#site-navbar");if(v.length&&C.brand){let a=function(i,n=0){return Array.isArray(i)?i.filter(c=>!(c&&c.type==="separator"&&n===0)).map(c=>c&&c.type==="separator"?{divider:!0}:Array.isArray(c?.items)?{...c,items:a(c.items,n+1)}:c):i},e=function(i){return Array.isArray(i)?i.filter(n=>!(n&&n.type==="separator")):[]};const t={...C.brand},o=t.size&&t.size!=="md"?` navbar-brand-${t.size}`:"";if(t.logo||t.icon||t.tagline){let i="";t.logo?i+=`<img src="${t.logo}" class="navbar-brand-logo" alt="${t.text||""}">`:t.icon&&(i+=`<span data-icon="${t.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),t.text&&(i+=`<span class="navbar-brand-text${o}">${t.text}</span>`),t.tagline&&(i+=`<small class="navbar-brand-tagline">${t.tagline}</small>`),t.html=i}else o&&t.text&&(t.html=`<span class="navbar-brand-text${o}">${t.text}</span>`);Domma.elements.navbar("#site-navbar",{brand:t,items:a(C.items||[]),variant:C.variant||"dark",position:C.position||"sticky",collapsible:!0}),Domma.icons.scan("#site-navbar");const l=v.get(0);if(C.appearOnHover){const i=window.matchMedia("(min-width: 993px)");l.querySelectorAll(".navbar-dropdown").forEach(n=>{n.addEventListener("mouseenter",()=>{i.matches&&n.classList.add("open")}),n.addEventListener("mouseleave",()=>{i.matches&&n.classList.remove("open")})})}const d=l.querySelector(".navbar-toggle"),h=l.querySelector(".navbar-collapse");if(d&&h){let i=null;const n=()=>{h.classList.contains("show")&&d.click()},c=()=>{i&&(i.remove(),i=null),document.body.classList.remove("dm-drawer-open")},s=()=>{i||(i=document.createElement("div"),i.className="dm-navbar-backdrop",i.addEventListener("click",n),document.body.appendChild(i),document.body.classList.add("dm-drawer-open"))};d.addEventListener("click",()=>{requestAnimationFrame(()=>{h.classList.contains("show")?s():c()})}),h.addEventListener("click",p=>{p.target.closest("a")&&requestAnimationFrame(c)}),document.addEventListener("keydown",p=>{p.key==="Escape"&&h.classList.contains("show")&&n()})}const m=e(C.items||[]);l.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(i=>{const n=m[parseInt(i.dataset.index,10)];if(!n)return;const c=i.dataset.subindex!=null,s=c&&Array.isArray(n.items)?n.items[parseInt(i.dataset.subindex,10)]:n;if(!s)return;s.colour&&A(i,s.colour),s.pill&&!c&&I(i,s.pill);const p=_(s.badge);p&&i.appendChild(p)})}const b=$("#site-footer");if(b.length){let l=function(r){return String(r??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},d=function(r){return l(r).replace(/'/g,"'")},h=function(r){if(r&&r.type==="separator")return'<li class="footer-sep"><hr></li>';const u=d(r.url||"#"),g=l(r.text||""),k=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(h).join("")}</ul>`:"";return`<li><a href="${u}">${g}</a>${k}</li>`},m=function(r,u){return(r||[]).forEach(g=>{g&&g.type==="separator"||(u.push(g),Array.isArray(g.items)&&g.items.length&&m(g.items,u))}),u};const t=y.social||{},o={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let a='<div class="footer-inner container">';const e=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(e.copyright&&(a+=`<p>${l(e.copyright)}</p>`),e.primary.length&&(a+=`<nav class="footer-nav footer-nav--primary"><ul>${e.primary.map(h).join("")}</ul></nav>`),e.legal.length&&(a+=`<nav class="footer-nav footer-nav--legal"><ul>${e.legal.map(h).join("")}</ul></nav>`),y.footer){const r=Object.keys(o).filter(u=>t[u]);r.length&&(a+='<div class="footer-social">',r.forEach(u=>{const{label:g,svg:k}=o[u];a+=`<a href="${t[u]}" target="_blank" rel="noopener noreferrer" aria-label="${g}" class="footer-social-link">${k}</a>`}),a+="</div>")}a+="</div>",b.html(a);const i=[...m(e.primary,[]),...m(e.legal,[])];b.get(0).querySelectorAll(".footer-nav a").forEach((r,u)=>{const g=i[u];if(!g)return;g.colour&&A(r,g.colour);const k=_(g.badge);k&&r.appendChild(k)});const c=S.get("reduced_motion"),s=c!==null?!!c:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),p=b.get(0).querySelector(".footer-inner");if(p){const r=document.createElement("input");r.type="checkbox",r.className="form-switch-input",r.id="dm-motion-switch",r.checked=s,r.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const u=document.createElement("span");u.className="form-switch-label",u.textContent="Reduce motion";const g=document.createElement("label");g.className="form-switch footer-motion-switch",g.title="Reduce motion",g.appendChild(r),g.appendChild(u),p.appendChild(g)}}$("#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 t={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide"};if(this.dataset.animationDuration){const o=parseInt(this.dataset.animationDuration,10);Number.isFinite(o)&&(t.animationDuration=o)}this.dataset.animationEasing&&(t.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(o=>o.cloneNode(!0)),this._dmCarouselOpts=t),this._dmCarousel=Domma.elements.carousel(this,t)}),f.find(".dm-countdown").each(function(){const t={autoStart:!0};this.dataset.to&&(t.targetDate=new Date(this.dataset.to)),this.dataset.duration&&(t.duration=parseInt(this.dataset.duration,10)),this.dataset.format&&(t.format=this.dataset.format),Domma.elements.timer(this,t)}),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 t=Array.from(this.querySelectorAll(".dm-progression-item")).map(e=>{const l={title:e.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:e.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return e.dataset.date&&(l.date=e.dataset.date),e.dataset.status&&(l.status=e.dataset.status),e.dataset.entryId&&(l._entryId=e.dataset.entryId),l}),o=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",a=Domma.elements.progression(this,{layout:o,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...t.length?{items:t}:{}});t.length&&(this._dmCtx={instance:a,items:t})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}document.querySelectorAll(".page-body .row[data-reveal]").forEach(t=>{const o=t.dataset.revealMode||"stagger",a=t.dataset.revealAnimation||"slide-up",e=parseInt(t.dataset.revealDuration,10)||400,l=parseInt(t.dataset.revealStagger,10)||60,d=parseInt(t.dataset.revealDelay,10)||0,h=t.dataset.revealDirection==="rtl",m=Array.from(t.children),i={"slide-up":"translateY(30px)","slide-down":"translateY(-30px)","slide-left":"translateX(30px)","slide-right":"translateX(-30px)",zoom:"scale(0.85)",flip:"perspective(600px) rotateX(15deg)"};m.forEach((n,c)=>{n.style.opacity="0",n.style.transform=i[a]||"",n.style.transition=`opacity ${e}ms ease, transform ${e}ms ease`;const s=h?m.length-1-c:c;n.style.transitionDelay=o==="stagger"?`${d+s*l}ms`:`${d}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const n=new IntersectionObserver(c=>{c.forEach(s=>{s.isIntersecting&&(s.target.offsetWidth,s.target.style.opacity="1",s.target.style.transform="none",n.unobserve(s.target))})},{threshold:.1});m.forEach(c=>n.observe(c))}))}),f.find(".card[data-collapsible]").each(function(){const t=this.querySelector(".card-header");t&&t.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),f.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const t=this.dataset.soTarget,o=document.getElementById(t);if(!o)return;const a=E.slideover({title:o.dataset.soTitle||"",size:o.dataset.soSize||"md",position:o.dataset.soPosition||"right"});o.style.display="",a.setContent(o),a.open()})})}if(typeof $.setup=="function"){const t=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(o=>{try{const a=atob(o.dataset.config),e=JSON.parse(a);Object.assign(t,e)}catch{}}),Object.keys(t).length>0){const o={};for(const[a,e]of Object.entries(t)){const l=e?.events?.click,{confirm:d,toast:h,alert:m,prompt:i,...n}=l||{};d||h||m||i?($(a).on("click",async function(s){if(s.preventDefault(),d&&!await E.confirm(d))return;let p=null;if(!(i&&(p=await E.prompt(i,{inputPlaceholder:n.promptPlaceholder||"",inputValue:n.promptDefault||""}),p===null))){if(n.target){const r=$(n.target);n.toggleClass&&r.toggleClass(n.toggleClass),n.addClass&&r.addClass(n.addClass),n.removeClass&&r.removeClass(n.removeClass),p!==null&&(n.setText&&r.text(p),n.setVal&&r.val(p),n.setAttr&&r.attr(n.setAttr,p))}n.href&&(window.location.href=n.href),h&&E.toast(h,{type:n.toastType||"success"}),m&&E.alert(m)}}),Object.keys(n).length&&(o[a]={...e,events:{...e.events,click:n}})):o[a]=e}$.setup(o)}}f.length&&x(f.get(0))});function x(C){C.querySelectorAll(".dm-cta-trigger").forEach(y=>{y.__ctaWired||(y.__ctaWired=!0,y.addEventListener("click",async()=>{const v=y.dataset.action,b=y.dataset.entry,w=y.dataset.confirm;let f=S.get("auth_token");if(!f){E.toast("Please log in to perform this action.",{type:"warning"});return}if(w&&!await E.confirm(w))return;const t=Array.from(y.childNodes).map(a=>a.cloneNode(!0));y.disabled=!0,y.textContent="Running\u2026";const o=a=>fetch(`/api/actions/${v}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({entryId:b})});try{let a=await o(f);if(a.status===401){const l=S.get("auth_refresh_token");if(l){const d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:l})});if(d.ok){const{token:h}=await d.json();S.set("auth_token",h),f=h,a=await o(f)}}}const e=await a.json().catch(()=>({}));if(!a.ok)throw new Error(e.error||e.message||`Error ${a.status}`);E.toast(e.message||"Action completed.",{type:"success"})}catch(a){E.toast(a.message||"Action failed.",{type:"error"})}finally{y.disabled=!1,y.textContent="",t.forEach(a=>y.appendChild(a)),Domma.icons.scan(y)}}))})}(function(){const y=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!y.length)return;const v=S.get("auth_token");function b(f,t){const o=document.createElement("div");o.className="dm-collection-display dm-collection-empty";const a=document.createElement("p");if(t){a.appendChild(document.createTextNode("Please "));const e=document.createElement("a");e.href="/login",e.textContent="sign in",a.appendChild(e),a.appendChild(document.createTextNode(" to view your entries."))}else a.textContent=f;return o.appendChild(a),o}function w(f,t){const o=document.createElement("template");o.innerHTML=t||"",f.replaceWith(o.content)}y.forEach(async f=>{const t=f.dataset.collectionAttrs;if(!t)return;if(!v){f.replaceChildren(b("",!0));return}const o=a=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({attrs:t})});try{let a=v,e=await o(a);if(e.status===401){const d=S.get("auth_refresh_token");if(d){const h=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:d})});if(h.ok){const{token:m}=await h.json();S.set("auth_token",m),a=m,e=await o(a)}}}if(!e.ok){const{error:d}=await e.json().catch(()=>({}));f.replaceChildren(b(d||"Unable to load entries.",!1));return}const{html:l}=await e.json();w(f,l)}catch{f.replaceChildren(b("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),x(document.body)},100)})(),(function(){const y=document.querySelectorAll("[data-collection-table]");!y.length||typeof T>"u"||!T.create||y.forEach(v=>{let b;try{b=JSON.parse(atob(v.dataset.payload))}catch{return}const{columns:w,rows:f,search:t,sortable:o,exportable:a,pageSize:e,empty:l,ctaConfig:d}=b;if(!w?.length)return;if(d){const i=w.findIndex(n=>n.key==="_cta");i!==-1&&(w[i]={key:"_cta",title:"",render:(n,c)=>{const s=document.createElement("button");if(s.className=`btn btn-${d.style||"primary"} dm-cta-trigger`,s.dataset.action=d.action||"",s.dataset.entry=c._entryId||"",d.confirm&&(s.dataset.confirm=d.confirm),d.icon){const p=document.createElement("span");p.dataset.icon=d.icon,s.appendChild(p),s.appendChild(document.createTextNode(" "))}return s.appendChild(document.createTextNode(d.label||"Run")),s}})}const h="col-table-"+Math.random().toString(36).slice(2,7),m=document.createElement("div");m.id=h,v.replaceChildren(m),T.create("#"+h,{data:f,columns:w,search:t,sortable:o,exportable:a,pageSize:e,emptyMessage:l}),d&&x(m),v._dmTable={columns:w,rows:f,render(i){T.create("#"+h,{data:i,columns:w,search:t,sortable:o,exportable:a,pageSize:e,emptyMessage:l}),d&&x(m)}}})})(),(function(){const y=document.querySelectorAll("[data-form-inline]");if(!y.length||typeof F>"u")return;function v(t,o){const a={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const l=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),d=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":l?"chooser":e.type,h={...e.formConfig||{}};h.span==="full"&&o&&(h.span=o);const m={type:d,label:e.label,required:e.required,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...h}};l&&(e.variant&&(m.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(m.multiple=!0),e.density&&(m.density=e.density),e.columns&&(m.columns=e.columns),e.accent&&(m.accent=e.accent),e.accentStyle&&(m.accentStyle=e.accentStyle),e.glow&&(m.glow=!0),e.glowColour&&(m.glowColour=e.glowColour),e.shadow&&(m.shadow=e.shadow),e.shadowColour&&(m.shadowColour=e.shadowColour)),a[e.name]=m}),a}function b(t){const o={};return(t||[]).forEach(a=>{if(!(!a.name||a.type==="page-break"||a.type==="spacer")&&(a.type==="select"||a.type==="multiselect")&&a.required){const e=(a.options||[])[0];e&&(o[a.name]=typeof e=="object"?e.value:e)}}),o}function w(t,o){(o||[]).forEach(a=>{if(a.type!=="date"||!a.name)return;const e=t.querySelector(`[name="${a.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function f(t,o,a){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=o,e.style.cssText=a?"color:var(--dm-danger,#f87171);margin-top:.75rem;":"color:var(--dm-success,#4ade80);margin-top:.75rem;"}y.forEach(t=>{let o;try{o=JSON.parse(atob(t.dataset.formInline))}catch{return}const a=o.fields||[],e=o.settings||{},l=e.columns||1,d=e.layout||"stacked",h=a.some(c=>c.type==="page-break"),m=async c=>{try{const s=t.querySelector('[name="website"]'),p=t.querySelector('[name="_t"]'),r=Object.assign({},c);s!==null&&(r._hp=s.value),p!==null&&(r._t=p.value);const u=await H.post(`/api/forms/submit/${o.slug}`,r);if(u&&u.error){f(t,u.error,!0);const g=new Error(u.error);throw g.formSubmitFailed=!0,g}if(u?.redirect){window.location.href=u.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);f(t,u?.message||e.successMessage||"Thank you for your submission.",!1)}catch(s){throw s.formSubmitFailed||f(t,s.message||"Submission failed. Please try again.",!0),s}};function i(c){const s=c.querySelector("form");if(!s)return;const p=document.createElement("div");p.className="fb-form-honeypot",p.setAttribute("aria-hidden","true");const r=document.createElement("input");r.name="website",r.type="text",r.tabIndex=-1,r.autocomplete="url",r.placeholder="https://",p.appendChild(r);const u=document.createElement("input");u.name="_t",u.type="hidden",u.value=Date.now(),p.appendChild(u),s.appendChild(p)}function n(){if(!window.FormLogicEngine||!a.some(s=>s.logic))return;const c=new window.FormLogicEngine.FormLogicRuntime(o,t);if(c.init(),t._formLogicRuntime=c,t.parentNode&&typeof MutationObserver<"u"){const s=new MutationObserver(function(p){for(const r of p)for(const u of r.removedNodes)if(u===t||u.nodeType===1&&u.contains&&u.contains(t)){c.destroy(),s.disconnect();return}});s.observe(t.parentNode,{childList:!0,subtree:!1})}}if(h&&F.wizard){const c=[];let s=[],p=o.title||"Step 1",r="";a.forEach(g=>{g.type==="page-break"?(c.push({title:p,description:r,fields:v(s,l)}),s=[],p=g.label||`Step ${c.length+1}`,r=g.description||""):g.type!=="spacer"&&s.push(g)}),c.push({title:p,description:r,fields:v(s,l)});const u=F.wizard(t,{schema:{steps:c},onSubmit:m});Promise.resolve(u).then(function(){w(t,a),D(t,a),e.honeypot!==!1&&i(t),n()})}else if(F.render){const c=F.render(t,v(a,l),b(a),{submitText:e.submitText||"Submit",layout:d,columns:l,onSubmit:m});Promise.resolve(c).then(function(){if(d==="grid"&&e.submitSpan==="full"){const s=t.querySelector(".form-buttons");s&&s.classList.add("col-span-full")}w(t,a),D(t,a),e.honeypot!==!1&&i(t),n()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
|
@@ -40,7 +40,12 @@ export async function settingsRoutes(fastify) {
|
|
|
40
40
|
'theme', 'adminTheme', 'fontFamily', 'fontSize',
|
|
41
41
|
'smtp', 'footer', 'analytics', 'social', 'locale',
|
|
42
42
|
'layoutOptions', 'seo', 'backToTop', 'cookieConsent', 'breadcrumbs', 'autoTheme',
|
|
43
|
-
'adminBrand'
|
|
43
|
+
'adminBrand',
|
|
44
|
+
// Keys that legitimately live in site.json (written elsewhere — e.g.
|
|
45
|
+
// `brand` by the menus migration) and are echoed back by GET /settings.
|
|
46
|
+
// They must be accepted here or any client that round-trips the GET
|
|
47
|
+
// payload into a save gets a 400 for keys it never touched.
|
|
48
|
+
'brand', 'url', 'baseTheme'
|
|
44
49
|
]);
|
|
45
50
|
const unknownKeys = Object.keys(data).filter(k => !ALLOWED_KEYS.has(k));
|
|
46
51
|
if (unknownKeys.length > 0) {
|
|
@@ -89,19 +89,26 @@ export async function usersRoutes(fastify) {
|
|
|
89
89
|
const target = await getUserById(id);
|
|
90
90
|
if (!target) return reply.status(404).send({ error: 'User not found' });
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
// Self-edit is always allowed — the hierarchy guard only applies to
|
|
93
|
+
// OTHER users, otherwise nobody can edit a peer-level account (or
|
|
94
|
+
// even their own, since canManageUser is a strict level comparison).
|
|
95
|
+
// Role-escalation guards below still apply to self-edits.
|
|
96
|
+
const isSelf = actor.id === id;
|
|
97
|
+
if (!isSelf && !canManageUser(actor, target)) {
|
|
93
98
|
return reply.code(403).send({ error: 'You cannot edit a user with that role' });
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
const updates = request.body || {};
|
|
97
|
-
// Prevent role escalation beyond what the actor can manage
|
|
98
|
-
|
|
102
|
+
// Prevent role escalation beyond what the actor can manage.
|
|
103
|
+
// Keeping the target's existing role is never an escalation.
|
|
104
|
+
if (updates.role && updates.role !== target.role && !canManageUser(actor, updates.role)) {
|
|
99
105
|
return reply.code(403).send({ error: 'You cannot assign that role' });
|
|
100
106
|
}
|
|
101
|
-
// Same check for any additionalRoles being
|
|
107
|
+
// Same check for any additionalRoles being ADDED (already-held ones may be kept)
|
|
102
108
|
if (Array.isArray(updates.additionalRoles)) {
|
|
109
|
+
const held = Array.isArray(target.additionalRoles) ? target.additionalRoles : [];
|
|
103
110
|
for (const r of updates.additionalRoles) {
|
|
104
|
-
if (!canManageUser(actor, r)) {
|
|
111
|
+
if (!held.includes(r) && !canManageUser(actor, r)) {
|
|
105
112
|
return reply.code(403).send({ error: `You cannot assign the additional role "${r}"` });
|
|
106
113
|
}
|
|
107
114
|
}
|
|
@@ -173,12 +173,77 @@ function formatDate(input, fmt) {
|
|
|
173
173
|
t => String(tokens[t]()));
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Build the `data-ctx` attribute that powers the public-site right-click
|
|
178
|
+
* search/filter menu (public/js/collection-context.js). The payload carries
|
|
179
|
+
* ONLY the fields the display actually renders — never the full entry data —
|
|
180
|
+
* so hidden/allowlisted-away fields can't leak through the attribute.
|
|
181
|
+
*
|
|
182
|
+
* @param {Array<{name: string, label?: string}>} fields - displayed fields
|
|
183
|
+
* @param {object[]} entries - collection entries {id, data}
|
|
184
|
+
* @returns {string} ` data-ctx="<base64 JSON>"` (leading space included)
|
|
185
|
+
*/
|
|
186
|
+
function buildCtxAttr(fields, entries) {
|
|
187
|
+
const names = [...new Set(fields.map(f => f?.name).filter(Boolean))];
|
|
188
|
+
if (!names.length || !entries.length) return '';
|
|
189
|
+
const payload = {
|
|
190
|
+
fields: names.map(n => ({name: n, label: fields.find(f => f.name === n)?.label || n})),
|
|
191
|
+
entries: entries.map(e => ({
|
|
192
|
+
id: String(e.id ?? ''),
|
|
193
|
+
data: Object.fromEntries(names.map(n => [n, e.data?.[n] ?? '']))
|
|
194
|
+
}))
|
|
195
|
+
};
|
|
196
|
+
return ` data-ctx="${Buffer.from(JSON.stringify(payload), 'utf8').toString('base64')}"`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Fields referenced by a block template's {{placeholders}} (system keys excluded). */
|
|
200
|
+
function blockTemplateFields(tpl) {
|
|
201
|
+
const names = new Set();
|
|
202
|
+
for (const m of String(tpl || '').matchAll(/\{\{\{?([\w]+)/g)) {
|
|
203
|
+
if (!['_id', '_createdAt', '_updatedAt'].includes(m[1])) names.add(m[1]);
|
|
204
|
+
}
|
|
205
|
+
return [...names].map(n => ({name: n, label: n}));
|
|
206
|
+
}
|
|
207
|
+
|
|
176
208
|
function buildBlockStyleTag(blockName, css) {
|
|
177
209
|
if (!css || !css.trim()) return '';
|
|
178
210
|
const body = String(css).replace(/<\/style/gi, '<\\/style');
|
|
179
211
|
return `<style>[data-block="${escapeAttr(blockName)}"] {\n${body}\n}</style>\n`;
|
|
180
212
|
}
|
|
181
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Interpolate a block template against a single collection entry.
|
|
216
|
+
*
|
|
217
|
+
* Triple-brace `{{{field}}}` — raw HTML pass-through (no escape). Use for
|
|
218
|
+
* fields that contain pre-rendered HTML. Must be processed BEFORE double-brace
|
|
219
|
+
* so the outer `{`/`}` of a triple match aren't consumed by the double-brace
|
|
220
|
+
* regex. Double-brace `{{field}}` — HTML-escaped (default, safe), with an
|
|
221
|
+
* optional `:format` suffix (e.g. `{{_createdAt:DD MMM YYYY}}`) that applies
|
|
222
|
+
* moment-style date formatting when the value parses as a Date.
|
|
223
|
+
*
|
|
224
|
+
* @param {string} blockTemplate - Raw HTML template with {{placeholders}}
|
|
225
|
+
* @param {object} e - Collection entry { id, data, meta }
|
|
226
|
+
* @returns {string}
|
|
227
|
+
*/
|
|
228
|
+
function interpolateBlockTemplate(blockTemplate, e) {
|
|
229
|
+
let html = blockTemplate.replace(/\{\{\{(\w+)\}\}\}/g, (_, key) => {
|
|
230
|
+
return String(e.data?.[key] ?? '');
|
|
231
|
+
});
|
|
232
|
+
html = html.replace(/\{\{([\w_]+)(?::([^{}]+))?\}\}/g, (_, key, fmt) => {
|
|
233
|
+
let value;
|
|
234
|
+
if (key === '_id') value = e.id ?? '';
|
|
235
|
+
else if (key === '_createdAt') value = e.meta?.createdAt ?? '';
|
|
236
|
+
else if (key === '_updatedAt') value = e.meta?.updatedAt ?? '';
|
|
237
|
+
else value = e.data?.[key] ?? '';
|
|
238
|
+
if (fmt) {
|
|
239
|
+
const formatted = formatDate(value, fmt);
|
|
240
|
+
if (formatted !== null) return escapeHtmlText(formatted);
|
|
241
|
+
}
|
|
242
|
+
return escapeHtmlText(value);
|
|
243
|
+
});
|
|
244
|
+
return html;
|
|
245
|
+
}
|
|
246
|
+
|
|
182
247
|
/**
|
|
183
248
|
* Render collection entries using a reusable HTML block template.
|
|
184
249
|
* Replaces {{fieldName}}, {{_id}}, {{_createdAt}}, {{_updatedAt}} placeholders.
|
|
@@ -194,38 +259,16 @@ function renderCollectionBlocks(entries, blockTemplate, emptyMsg, ctaOpts, cols,
|
|
|
194
259
|
return `<div class="dm-collection-display dm-collection-empty"><p>${escapeHtmlText(emptyMsg)}</p></div>`;
|
|
195
260
|
}
|
|
196
261
|
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
262
|
+
// Every entry gets a neutral wrapper carrying data-entry-id (needed by the
|
|
263
|
+
// public right-click filter menu); the data-block attribute — which scopes
|
|
264
|
+
// the block's CSS — is only added when the block actually carries CSS.
|
|
200
265
|
const hasCss = !!(blockCss && blockCss.trim());
|
|
201
|
-
const
|
|
202
|
-
const wrapClose = hasCss && blockName ? `</div>` : '';
|
|
266
|
+
const blockAttr = hasCss && blockName ? ` data-block="${escapeAttr(blockName)}"` : '';
|
|
203
267
|
|
|
204
268
|
const items = entries.map(e => {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// so the outer `{` and `}` of a triple match aren't consumed by the
|
|
209
|
-
// double-brace regex.
|
|
210
|
-
let html = blockTemplate.replace(/\{\{\{(\w+)\}\}\}/g, (_, key) => {
|
|
211
|
-
return String(e.data?.[key] ?? '');
|
|
212
|
-
});
|
|
213
|
-
// Double-brace `{{field}}` — HTML-escaped (default, safe).
|
|
214
|
-
// Optional `:format` suffix (e.g. `{{_createdAt:DD MMM YYYY}}`) applies
|
|
215
|
-
// moment-style date formatting when the value parses as a Date; any
|
|
216
|
-
// field that doesn't parse falls back to its raw value.
|
|
217
|
-
html = html.replace(/\{\{([\w_]+)(?::([^{}]+))?\}\}/g, (_, key, fmt) => {
|
|
218
|
-
let value;
|
|
219
|
-
if (key === '_id') value = e.id ?? '';
|
|
220
|
-
else if (key === '_createdAt') value = e.meta?.createdAt ?? '';
|
|
221
|
-
else if (key === '_updatedAt') value = e.meta?.updatedAt ?? '';
|
|
222
|
-
else value = e.data?.[key] ?? '';
|
|
223
|
-
if (fmt) {
|
|
224
|
-
const formatted = formatDate(value, fmt);
|
|
225
|
-
if (formatted !== null) return escapeHtmlText(formatted);
|
|
226
|
-
}
|
|
227
|
-
return escapeHtmlText(value);
|
|
228
|
-
});
|
|
269
|
+
const wrapOpen = `<div${blockAttr} data-entry-id="${escapeAttr(String(e.id ?? ''))}">`;
|
|
270
|
+
const wrapClose = `</div>`;
|
|
271
|
+
let html = interpolateBlockTemplate(blockTemplate, e);
|
|
229
272
|
|
|
230
273
|
if (ctaOpts) {
|
|
231
274
|
const ctaStyle = escapeAttr(ctaOpts.style || 'primary');
|
|
@@ -243,8 +286,9 @@ function renderCollectionBlocks(entries, blockTemplate, emptyMsg, ctaOpts, cols,
|
|
|
243
286
|
? `dm-collection-display dm-collection-blocks grid ${responsiveGridCols(validCols).join(' ')} gap-4`
|
|
244
287
|
: 'dm-collection-display dm-collection-blocks';
|
|
245
288
|
|
|
289
|
+
const ctx = buildCtxAttr(blockTemplateFields(blockTemplate), entries);
|
|
246
290
|
const styleTag = buildBlockStyleTag(blockName, blockCss);
|
|
247
|
-
return `${styleTag}<div class="${wrapperClass}">\n${items.join('\n')}\n</div>`;
|
|
291
|
+
return `${styleTag}<div class="${wrapperClass}"${ctx}>\n${items.join('\n')}\n</div>`;
|
|
248
292
|
}
|
|
249
293
|
|
|
250
294
|
function renderBlockFromAttrs(tmpl, attrs, blockName = '') {
|
|
@@ -459,9 +503,9 @@ function renderCollectionCards(entries, visibleFields, titleField, columns, empt
|
|
|
459
503
|
const iconHtml = ctaOpts.icon ? `<span data-icon="${escapeAttr(ctaOpts.icon)}"></span> ` : '';
|
|
460
504
|
footer = `<div class="card-footer"><button class="${btnCls}" ${btnData}>${iconHtml}${escapeHtmlText(ctaOpts.label || 'Run')}</button></div>`;
|
|
461
505
|
}
|
|
462
|
-
return `<div class="card">${title ? `<div class="card-header">${title}</div>` : ''}<div class="card-body">${body || ' '}</div>${footer}</div>`;
|
|
506
|
+
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>`;
|
|
463
507
|
}).join('\n');
|
|
464
|
-
return `<div class="dm-collection-display grid ${responsiveGridCols(cols).join(' ')} gap-4">\n${cards}\n</div>`;
|
|
508
|
+
return `<div class="dm-collection-display grid ${responsiveGridCols(cols).join(' ')} gap-4"${buildCtxAttr(visibleFields, entries)}>\n${cards}\n</div>`;
|
|
465
509
|
}
|
|
466
510
|
|
|
467
511
|
function renderCollectionList(entries, visibleFields, titleField, emptyMsg, ctaOpts) {
|
|
@@ -486,9 +530,9 @@ function renderCollectionList(entries, visibleFields, titleField, emptyMsg, ctaO
|
|
|
486
530
|
const iconHtml = ctaOpts.icon ? `<span data-icon="${escapeAttr(ctaOpts.icon)}"></span> ` : '';
|
|
487
531
|
ctaHtml = `<button class="${btnCls}" ${btnData}>${iconHtml}${escapeHtmlText(ctaOpts.label || 'Run')}</button>`;
|
|
488
532
|
}
|
|
489
|
-
return `<div class="dm-collection-list-item">${title ? `<strong>${title}</strong>` : ''}${rest}${ctaHtml}</div>`;
|
|
533
|
+
return `<div class="dm-collection-list-item" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${title ? `<strong>${title}</strong>` : ''}${rest}${ctaHtml}</div>`;
|
|
490
534
|
}).join('\n');
|
|
491
|
-
return `<div class="dm-collection-display dm-collection-list">\n${items}\n</div>`;
|
|
535
|
+
return `<div class="dm-collection-display dm-collection-list"${buildCtxAttr(visibleFields, entries)}>\n${items}\n</div>`;
|
|
492
536
|
}
|
|
493
537
|
|
|
494
538
|
/**
|
|
@@ -511,7 +555,7 @@ function renderCollectionAccordion(entries, titleField, bodyField, multiple, emp
|
|
|
511
555
|
const bodyVal = String(e.data?.[bodyField] ?? '');
|
|
512
556
|
const bodyHtml = marked.parse(bodyVal);
|
|
513
557
|
return (
|
|
514
|
-
`<div class="accordion-item">\n` +
|
|
558
|
+
`<div class="accordion-item" data-entry-id="${escapeAttr(String(e.id ?? ''))}">\n` +
|
|
515
559
|
` <h3 class="accordion-header"><button class="accordion-button" type="button">${title}` +
|
|
516
560
|
`<span class="accordion-icon" data-icon="chevron-down"></span></button></h3>\n` +
|
|
517
561
|
` <div class="accordion-body"><div class="accordion-content">${bodyHtml}</div></div>\n` +
|
|
@@ -519,7 +563,8 @@ function renderCollectionAccordion(entries, titleField, bodyField, multiple, emp
|
|
|
519
563
|
);
|
|
520
564
|
}).join('\n');
|
|
521
565
|
|
|
522
|
-
|
|
566
|
+
const ctx = buildCtxAttr([{name: titleField}, {name: bodyField}], entries);
|
|
567
|
+
return `<div class="dm-collection-display accordion"${multiAttr}${ctx}>\n${items}\n</div>`;
|
|
523
568
|
}
|
|
524
569
|
|
|
525
570
|
/**
|
|
@@ -553,10 +598,167 @@ function renderCollectionTimeline(entries, opts) {
|
|
|
553
598
|
const bodyHtml = bodyField
|
|
554
599
|
? marked.parse(String(e.data?.[bodyField] ?? ''))
|
|
555
600
|
: '';
|
|
556
|
-
return `<div class="dm-progression-item"${date}${status}${icon}><div class="dm-progression-item-title">${title}</div><div class="dm-progression-item-body">${bodyHtml}</div></div>`;
|
|
601
|
+
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>`;
|
|
602
|
+
}).join('\n');
|
|
603
|
+
|
|
604
|
+
const ctx = buildCtxAttr(
|
|
605
|
+
[{name: titleField}, {name: dateField}, {name: statusField}, {name: bodyField}].filter(f => f.name),
|
|
606
|
+
entries
|
|
607
|
+
);
|
|
608
|
+
return `<div class="dm-collection-display dm-progression" data-layout="${layout}" data-theme="${theme}" data-mode="${mode}"${ctx}>\n${items}\n</div>`;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Render a collection as a Domma carousel — one slide per entry.
|
|
613
|
+
* Output mirrors processCarouselBlocks so site.js's `.carousel` auto-init
|
|
614
|
+
* (Domma.elements.carousel) picks it up unchanged.
|
|
615
|
+
*
|
|
616
|
+
* Per-slide content comes either from a block template (opts.blockTemplate,
|
|
617
|
+
* interpolated per entry like display="block") or from image/title/body
|
|
618
|
+
* fields rendered in the standard carousel-slide structure.
|
|
619
|
+
*
|
|
620
|
+
* @param {object[]} entries
|
|
621
|
+
* @param {object} opts
|
|
622
|
+
* @param {string} [opts.imageField] - field holding the slide image URL
|
|
623
|
+
* @param {string} [opts.titleField] - field for the slide title
|
|
624
|
+
* @param {string} [opts.bodyField] - Markdown body field
|
|
625
|
+
* @param {string} [opts.blockTemplate] - optional block template (wins over fields)
|
|
626
|
+
* @param {string} [opts.blockName]
|
|
627
|
+
* @param {string} [opts.blockCss]
|
|
628
|
+
* @param {object} [opts.dataAttrs] - autoplay/interval/loop/animation passthrough
|
|
629
|
+
* @param {string} opts.emptyMsg
|
|
630
|
+
* @returns {string}
|
|
631
|
+
*/
|
|
632
|
+
function renderCollectionCarousel(entries, opts) {
|
|
633
|
+
const {imageField, titleField, bodyField, blockTemplate, blockName, blockCss, dataAttrs = {}, emptyMsg} = opts;
|
|
634
|
+
if (!entries.length) {
|
|
635
|
+
return `<div class="dm-collection-display dm-collection-empty"><p>${escapeHtmlText(emptyMsg)}</p></div>`;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const passthrough = ['autoplay', 'interval', 'loop', 'animation', 'animation-duration', 'animation-easing']
|
|
639
|
+
.map(k => dataAttrs[k] ? ` data-${k}="${escapeAttr(String(dataAttrs[k]))}"` : '')
|
|
640
|
+
.join('');
|
|
641
|
+
|
|
642
|
+
const hasCss = !!(blockCss && blockCss.trim());
|
|
643
|
+
const slides = entries.map(e => {
|
|
644
|
+
let inner = '';
|
|
645
|
+
if (blockTemplate) {
|
|
646
|
+
const html = interpolateBlockTemplate(blockTemplate, e);
|
|
647
|
+
inner = hasCss && blockName ? `<div data-block="${escapeAttr(blockName)}">${html}</div>` : html;
|
|
648
|
+
} else {
|
|
649
|
+
const img = imageField && e.data?.[imageField]
|
|
650
|
+
? `<img src="${escapeAttr(String(e.data[imageField]))}" alt="${escapeAttr(String(e.data?.[titleField] ?? ''))}">\n`
|
|
651
|
+
: '';
|
|
652
|
+
const title = titleField && e.data?.[titleField] != null
|
|
653
|
+
? `<h2 class="carousel-slide-title">${escapeHtmlText(String(e.data[titleField]))}</h2>`
|
|
654
|
+
: '';
|
|
655
|
+
const bodyHtml = bodyField && e.data?.[bodyField] != null
|
|
656
|
+
? `<div class="carousel-slide-description">${marked.parse(String(e.data[bodyField]))}</div>`
|
|
657
|
+
: '';
|
|
658
|
+
inner = img + (title || bodyHtml ? `<div class="carousel-slide-content">${title}${bodyHtml}</div>` : '');
|
|
659
|
+
}
|
|
660
|
+
return `<div class="carousel-slide" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${inner}</div>`;
|
|
661
|
+
}).join('\n');
|
|
662
|
+
|
|
663
|
+
const ctxFields = blockTemplate
|
|
664
|
+
? blockTemplateFields(blockTemplate)
|
|
665
|
+
: [{name: titleField}, {name: bodyField}].filter(f => f.name);
|
|
666
|
+
const ctx = buildCtxAttr(ctxFields, entries);
|
|
667
|
+
const styleTag = blockTemplate ? buildBlockStyleTag(blockName, blockCss) : '';
|
|
668
|
+
return `${styleTag}<div class="dm-collection-display carousel"${passthrough}${ctx}><div class="carousel-track">${slides}</div></div>`;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Render a collection as a Domma list group — one item per entry.
|
|
673
|
+
* Output mirrors processListGroupBlocks (pure CSS component, no JS init).
|
|
674
|
+
*
|
|
675
|
+
* Per-item content comes either from a block template (interpolated per
|
|
676
|
+
* entry) or from title/body fields.
|
|
677
|
+
*
|
|
678
|
+
* @param {object[]} entries
|
|
679
|
+
* @param {object} opts
|
|
680
|
+
* @param {string} [opts.titleField] - field for the item's leading text
|
|
681
|
+
* @param {string} [opts.bodyField] - inline-Markdown body field
|
|
682
|
+
* @param {string} [opts.variant] - flush | numbered | horizontal
|
|
683
|
+
* @param {string} [opts.size] - sm | lg
|
|
684
|
+
* @param {string} [opts.itemVariant] - primary | secondary | success | danger | warning | info
|
|
685
|
+
* @param {boolean} [opts.action] - add .list-group-item-action per item
|
|
686
|
+
* @param {string} [opts.blockTemplate] - optional block template (wins over fields)
|
|
687
|
+
* @param {string} [opts.blockName]
|
|
688
|
+
* @param {string} [opts.blockCss]
|
|
689
|
+
* @param {string} opts.emptyMsg
|
|
690
|
+
* @returns {string}
|
|
691
|
+
*/
|
|
692
|
+
/** Standard "block template not found" fragment. */
|
|
693
|
+
function blockNotFoundHtml(blockName) {
|
|
694
|
+
return `<div class="dm-collection-display dm-collection-empty"><p>Block template “${escapeHtmlText(blockName || '')}” not found.</p></div>`;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Load a block's template + CSS when a block attr is present.
|
|
699
|
+
* Returns {tpl: '', css: '', missing: false} when no block was requested,
|
|
700
|
+
* and {missing: true} when the named block can't be loaded — callers show
|
|
701
|
+
* blockNotFoundHtml() rather than silently falling back to field rendering.
|
|
702
|
+
*
|
|
703
|
+
* @param {string} [blockName]
|
|
704
|
+
* @returns {Promise<{tpl: string, css: string, missing: boolean}>}
|
|
705
|
+
*/
|
|
706
|
+
async function loadOptionalBlockAssets(blockName) {
|
|
707
|
+
if (!blockName) return {tpl: '', css: '', missing: false};
|
|
708
|
+
try {
|
|
709
|
+
const [tpl, css] = await Promise.all([
|
|
710
|
+
loadBlockTemplate(blockName),
|
|
711
|
+
loadBlockCss(blockName),
|
|
712
|
+
]);
|
|
713
|
+
return {tpl, css, missing: false};
|
|
714
|
+
} catch {
|
|
715
|
+
return {tpl: '', css: '', missing: true};
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function renderCollectionListgroup(entries, opts) {
|
|
720
|
+
const {titleField, bodyField, variant, size, itemVariant, action, blockTemplate, blockName, blockCss, emptyMsg} = opts;
|
|
721
|
+
if (!entries.length) {
|
|
722
|
+
return `<div class="dm-collection-display dm-collection-empty"><p>${escapeHtmlText(emptyMsg)}</p></div>`;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const variantMap = {flush: 'list-group-flush', numbered: 'list-group-numbered', horizontal: 'list-group-horizontal'};
|
|
726
|
+
const sizeMap = {sm: 'list-group-sm', lg: 'list-group-lg'};
|
|
727
|
+
const wrapperClasses = ['dm-collection-display', 'list-group'];
|
|
728
|
+
if (variantMap[variant]) wrapperClasses.push(variantMap[variant]);
|
|
729
|
+
if (sizeMap[size]) wrapperClasses.push(sizeMap[size]);
|
|
730
|
+
|
|
731
|
+
const itemClasses = ['list-group-item'];
|
|
732
|
+
if (action) itemClasses.push('list-group-item-action');
|
|
733
|
+
if (itemVariant && /^(primary|secondary|success|danger|warning|info)$/.test(itemVariant)) {
|
|
734
|
+
itemClasses.push(`list-group-item-${itemVariant}`);
|
|
735
|
+
}
|
|
736
|
+
const itemClassStr = itemClasses.join(' ');
|
|
737
|
+
|
|
738
|
+
const hasCss = !!(blockCss && blockCss.trim());
|
|
739
|
+
const items = entries.map(e => {
|
|
740
|
+
let inner;
|
|
741
|
+
if (blockTemplate) {
|
|
742
|
+
const html = interpolateBlockTemplate(blockTemplate, e);
|
|
743
|
+
inner = hasCss && blockName ? `<div data-block="${escapeAttr(blockName)}">${html}</div>` : html;
|
|
744
|
+
} else {
|
|
745
|
+
const title = titleField && e.data?.[titleField] != null
|
|
746
|
+
? `<strong>${escapeHtmlText(String(e.data[titleField]))}</strong>`
|
|
747
|
+
: '';
|
|
748
|
+
const body = bodyField && e.data?.[bodyField] != null
|
|
749
|
+
? `${title ? ' ' : ''}${marked.parseInline(String(e.data[bodyField]))}`
|
|
750
|
+
: '';
|
|
751
|
+
inner = (title + body) || escapeHtmlText(String(e.id ?? ''));
|
|
752
|
+
}
|
|
753
|
+
return `<div class="${itemClassStr}" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${inner}</div>`;
|
|
557
754
|
}).join('\n');
|
|
558
755
|
|
|
559
|
-
|
|
756
|
+
const ctxFields = blockTemplate
|
|
757
|
+
? blockTemplateFields(blockTemplate)
|
|
758
|
+
: [{name: titleField}, {name: bodyField}].filter(f => f.name);
|
|
759
|
+
const ctx = buildCtxAttr(ctxFields, entries);
|
|
760
|
+
const styleTag = blockTemplate ? buildBlockStyleTag(blockName, blockCss) : '';
|
|
761
|
+
return `${styleTag}<div class="${wrapperClasses.join(' ')}"${ctx}>\n${items}\n</div>`;
|
|
560
762
|
}
|
|
561
763
|
|
|
562
764
|
/**
|
|
@@ -661,6 +863,36 @@ async function processViewBlocks(markdown, tagSet) {
|
|
|
661
863
|
mode: timelineMode,
|
|
662
864
|
emptyMsg,
|
|
663
865
|
});
|
|
866
|
+
} else if (display === 'carousel') {
|
|
867
|
+
const {tpl, css, missing} = await loadOptionalBlockAssets(attrs.block);
|
|
868
|
+
replacement = missing
|
|
869
|
+
? blockNotFoundHtml(attrs.block)
|
|
870
|
+
: renderCollectionCarousel(entries, {
|
|
871
|
+
imageField: attrs['image-field'] || '',
|
|
872
|
+
titleField: attrs['title-field'] || 'title',
|
|
873
|
+
bodyField: attrs['body-field'] || '',
|
|
874
|
+
blockTemplate: tpl,
|
|
875
|
+
blockName: attrs.block || '',
|
|
876
|
+
blockCss: css,
|
|
877
|
+
dataAttrs: attrs,
|
|
878
|
+
emptyMsg,
|
|
879
|
+
});
|
|
880
|
+
} else if (display === 'listgroup') {
|
|
881
|
+
const {tpl, css, missing} = await loadOptionalBlockAssets(attrs.block);
|
|
882
|
+
replacement = missing
|
|
883
|
+
? blockNotFoundHtml(attrs.block)
|
|
884
|
+
: renderCollectionListgroup(entries, {
|
|
885
|
+
titleField: attrs['title-field'] || 'title',
|
|
886
|
+
bodyField: attrs['body-field'] || '',
|
|
887
|
+
variant: attrs.variant || '',
|
|
888
|
+
size: attrs.size || '',
|
|
889
|
+
itemVariant: attrs.itemvariant || '',
|
|
890
|
+
action: attrs.action === 'true',
|
|
891
|
+
blockTemplate: tpl,
|
|
892
|
+
blockName: attrs.block || '',
|
|
893
|
+
blockCss: css,
|
|
894
|
+
emptyMsg,
|
|
895
|
+
});
|
|
664
896
|
} else if (display === 'block') {
|
|
665
897
|
const blockName = attrs.block || viewConfig?.display?.block || '';
|
|
666
898
|
if (blockName) {
|
|
@@ -671,7 +903,7 @@ async function processViewBlocks(markdown, tagSet) {
|
|
|
671
903
|
]);
|
|
672
904
|
replacement = renderCollectionBlocks(entries, tpl, emptyMsg, ctaOpts, '', blockName, css);
|
|
673
905
|
} catch {
|
|
674
|
-
replacement =
|
|
906
|
+
replacement = blockNotFoundHtml(blockName);
|
|
675
907
|
}
|
|
676
908
|
}
|
|
677
909
|
} else {
|
|
@@ -1118,6 +1350,36 @@ export async function renderCollectionFragment(attrs, { extraFilter = null } = {
|
|
|
1118
1350
|
mode: timelineMode,
|
|
1119
1351
|
emptyMsg,
|
|
1120
1352
|
});
|
|
1353
|
+
} else if (display === 'carousel') {
|
|
1354
|
+
const {tpl, css, missing} = await loadOptionalBlockAssets(attrs.block);
|
|
1355
|
+
replacement = missing
|
|
1356
|
+
? blockNotFoundHtml(attrs.block)
|
|
1357
|
+
: renderCollectionCarousel(entries, {
|
|
1358
|
+
imageField: attrs['image-field'] || '',
|
|
1359
|
+
titleField: attrs['title-field'] || 'title',
|
|
1360
|
+
bodyField: attrs['body-field'] || '',
|
|
1361
|
+
blockTemplate: tpl,
|
|
1362
|
+
blockName: attrs.block || '',
|
|
1363
|
+
blockCss: css,
|
|
1364
|
+
dataAttrs: attrs,
|
|
1365
|
+
emptyMsg,
|
|
1366
|
+
});
|
|
1367
|
+
} else if (display === 'listgroup') {
|
|
1368
|
+
const {tpl, css, missing} = await loadOptionalBlockAssets(attrs.block);
|
|
1369
|
+
replacement = missing
|
|
1370
|
+
? blockNotFoundHtml(attrs.block)
|
|
1371
|
+
: renderCollectionListgroup(entries, {
|
|
1372
|
+
titleField: attrs['title-field'] || 'title',
|
|
1373
|
+
bodyField: attrs['body-field'] || '',
|
|
1374
|
+
variant: attrs.variant || '',
|
|
1375
|
+
size: attrs.size || '',
|
|
1376
|
+
itemVariant: attrs.itemvariant || '',
|
|
1377
|
+
action: attrs.action === 'true',
|
|
1378
|
+
blockTemplate: tpl,
|
|
1379
|
+
blockName: attrs.block || '',
|
|
1380
|
+
blockCss: css,
|
|
1381
|
+
emptyMsg,
|
|
1382
|
+
});
|
|
1121
1383
|
} else if (display === 'block') {
|
|
1122
1384
|
const blockName = attrs.block || '';
|
|
1123
1385
|
if (blockName) {
|
|
@@ -1129,7 +1391,7 @@ export async function renderCollectionFragment(attrs, { extraFilter = null } = {
|
|
|
1129
1391
|
const cols = attrs.cols || '';
|
|
1130
1392
|
replacement = renderCollectionBlocks(entries, tpl, emptyMsg, ctaOpts, cols, blockName, css);
|
|
1131
1393
|
} catch {
|
|
1132
|
-
replacement =
|
|
1394
|
+
replacement = blockNotFoundHtml(blockName);
|
|
1133
1395
|
}
|
|
1134
1396
|
}
|
|
1135
1397
|
} else {
|
|
@@ -116,7 +116,8 @@
|
|
|
116
116
|
</script>
|
|
117
117
|
|
|
118
118
|
<!-- Site initialisation -->
|
|
119
|
-
<script src="/public/js/site.js?v=20260722-
|
|
119
|
+
<script src="/public/js/site.js?v=20260722-collection-ctx" type="module"></script>
|
|
120
|
+
<script src="/public/js/collection-context.js?v=20260722-collection-ctx" defer></script>
|
|
120
121
|
|
|
121
122
|
<!-- Interactive Collection Browser (used by [collection] shortcodes with
|
|
122
123
|
searchable / filterable / sortable / paginate attributes) -->
|