domma-cms 0.36.3 → 0.36.4

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.
Files changed (35) hide show
  1. package/admin/css/admin.css +1 -1
  2. package/admin/js/lib/card-builder.js +3 -3
  3. package/admin/js/lib/editor-folding.js +1 -1
  4. package/admin/js/lib/effects-builder.js +2 -2
  5. package/admin/js/lib/markdown-toolbar.js +3 -3
  6. package/admin/js/lib/shortcode-modal.js +1 -1
  7. package/admin/js/lib/timeline-builder.js +2 -2
  8. package/admin/js/views/actions-list.js +1 -1
  9. package/admin/js/views/api-endpoint-editor.js +3 -3
  10. package/admin/js/views/api-endpoints.js +2 -2
  11. package/admin/js/views/api-tokens.js +4 -4
  12. package/admin/js/views/blocks.js +5 -5
  13. package/admin/js/views/collection-editor.js +4 -4
  14. package/admin/js/views/collection-entries.js +1 -1
  15. package/admin/js/views/component-editor.js +1 -1
  16. package/admin/js/views/components.js +2 -2
  17. package/admin/js/views/effects.js +1 -1
  18. package/admin/js/views/form-editor.js +7 -7
  19. package/admin/js/views/layouts.js +1 -1
  20. package/admin/js/views/login.js +1 -1
  21. package/admin/js/views/menu-editor.js +8 -8
  22. package/admin/js/views/menu-locations.js +1 -1
  23. package/admin/js/views/menus.js +3 -3
  24. package/admin/js/views/my-profile.js +1 -1
  25. package/admin/js/views/notifications.js +1 -1
  26. package/admin/js/views/page-editor.js +29 -29
  27. package/admin/js/views/project-detail.js +1 -1
  28. package/admin/js/views/project-editor.js +1 -1
  29. package/admin/js/views/project-settings.js +1 -1
  30. package/admin/js/views/projects.js +2 -2
  31. package/admin/js/views/user-editor.js +1 -1
  32. package/admin/js/views/view-editor.js +1 -1
  33. package/admin/js/views/view-preview.js +1 -1
  34. package/admin/js/views/views-list.js +1 -1
  35. package/package.json +1 -1
@@ -1 +1 @@
1
- import{api as f}from"../api.js";const N={narrow:"Narrow",normal:"Normal",wide:"Wide",full:"Full"},B="display:block;font-size:.7rem;font-weight:600;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;margin-bottom:.25rem;",S="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;box-sizing:border-box;";function x(n,e){const t=document.createElement("div");t.style.cssText="display:flex;flex-direction:column;gap:.25rem;";const a=document.createElement("label");return a.style.cssText=B,a.textContent=n,t.appendChild(a),t.appendChild(e),t}function T(n,e){const t=document.createElement("input");return t.type="text",t.placeholder=n||"",t.value=e||"",t.style.cssText=S,t}function L(n,e){const t=document.createElement("label");t.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!!e;const o=document.createElement("span");return o.style.cssText="font-size:.9em;color:var(--dm-text,#eee);",o.textContent=n,t.appendChild(a),t.appendChild(o),{wrap:t,cb:a}}function F(n){const e=document.createElement("select");return e.style.cssText=S,[["narrow","Narrow (768px)"],["normal","Normal (1100px)"],["wide","Wide (75% of viewport, max 2000px)"],["full","Full width"]].forEach(([t,a])=>{const o=document.createElement("option");o.value=t,o.textContent=a,t===(n||"normal")&&(o.selected=!0),e.appendChild(o)}),e}function D(n){const e=document.createElement("div");e.style.cssText="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;";const t=document.createElement("input");t.type="color",t.value=n||"#ffffff",t.disabled=!n,t.style.cssText="width:38px;height:32px;padding:2px;border:1px solid var(--dm-border,#333);border-radius:4px;background:transparent;cursor:pointer;";const a=document.createElement("span");a.style.cssText="font-size:.85em;color:var(--dm-text-muted,#aaa);min-width:4rem;",a.textContent=n||"None";const o=document.createElement("button");o.type="button",o.textContent="Choose",o.style.cssText="padding:.25rem .6rem;font-size:.8em;background:transparent;border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text-muted,#aaa);cursor:pointer;";const s=document.createElement("button");return s.type="button",s.textContent="Clear",s.style.cssText=o.style.cssText,s.disabled=!n,o.addEventListener("click",()=>{t.disabled=!1,s.disabled=!1,t.click()}),t.addEventListener("input",()=>{a.textContent=t.value,s.disabled=!1}),s.addEventListener("click",()=>{t.value="#ffffff",t.disabled=!0,s.disabled=!0,a.textContent="None"}),e.appendChild(t),e.appendChild(a),e.appendChild(o),e.appendChild(s),{wrap:e,getValue(){return t.disabled?"":t.value}}}function O(n,e,t){const a=document.createElement("div");a.className="card preset-card",a.style.cssText="cursor:pointer;transition:box-shadow .15s;",a.addEventListener("mouseenter",()=>{a.style.boxShadow="0 0 0 2px var(--dm-primary,#4f7fff)"}),a.addEventListener("mouseleave",()=>{a.style.boxShadow=""}),a.addEventListener("click",()=>t(n,e));const o=document.createElement("div");o.className="card-header",o.style.cssText="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;";const s=document.createElement("strong");if(s.textContent=e.label||n,o.appendChild(s),e.builtin){const r=document.createElement("span");r.className="badge",r.textContent="built-in",r.style.cssText="font-size:.65rem;",o.appendChild(r)}const d=document.createElement("span");d.className="badge badge-outline",d.textContent=N[e.width]||"Normal",d.style.cssText="margin-left:auto;font-size:.65rem;",o.appendChild(d),a.appendChild(o);const l=document.createElement("div");if(l.className="card-body",e.description){const r=document.createElement("p");r.className="text-muted",r.style.cssText="font-size:.85em;margin:0 0 .5rem;",r.textContent=e.description,l.appendChild(r)}const c=document.createElement("div");if(c.style.cssText="display:flex;gap:.75rem;flex-wrap:wrap;",[["Navbar",e.navbar],["Footer",e.footer],["Sidebar",e.sidebar]].forEach(([r,u])=>{const p=document.createElement("span");p.style.cssText=`font-size:.75em;color:${u?"var(--dm-success,#4caf50)":"var(--dm-text-muted,#666)"};display:flex;align-items:center;gap:.25rem;`,p.textContent=(u?"\u25CF ":"\u25CB ")+r,c.appendChild(p)}),l.appendChild(c),e.bgColor){const r=document.createElement("span");r.title=e.bgColor,r.style.cssText=`display:inline-block;width:14px;height:14px;border-radius:3px;background:${e.bgColor};border:1px solid var(--dm-border,#333);margin-top:.4rem;vertical-align:middle;`,l.appendChild(r)}return a.appendChild(l),a}function z(n,e,t,a,o){const s=E.slideover({title:t?"New Layout":"Edit Layout",size:"md"}),d=document.createElement("div");d.style.cssText="display:flex;flex-direction:column;gap:1rem;padding:1rem;";const l=T("e.g. Dark Landing",e.label||""),c=T("Optional description",e.description||""),r=F(e.width),u=L("Show Navbar",e.navbar!==!1),p=L("Show Footer",e.footer!==!1),g=L("Show Sidebar",e.sidebar===!0),b=D(e.bgColor||""),C=T("https://...",e.bgImage||""),k=T("e.g. no-hero wide-content",e.class||"");d.appendChild(x("Label",l)),d.appendChild(x("Description",c)),d.appendChild(x("Width",r));const h=document.createElement("div");h.style.cssText="display:flex;gap:1.5rem;flex-wrap:wrap;",h.appendChild(u.wrap),h.appendChild(p.wrap),h.appendChild(g.wrap),d.appendChild(h),d.appendChild(x("Background Colour",b.wrap)),d.appendChild(x("Background Image URL",C)),d.appendChild(x("Custom CSS Class",k));const v=document.createElement("div");v.style.cssText="display:flex;gap:.5rem;flex-wrap:wrap;padding-top:.5rem;border-top:1px solid var(--dm-border,#333);";const y=document.createElement("button");if(y.type="button",y.className="btn btn-primary",y.textContent="Save",v.appendChild(y),!t&&!e.builtin){const i=document.createElement("button");i.type="button",i.className="btn btn-danger",i.style.marginLeft="auto",i.textContent="Delete",i.addEventListener("click",async()=>{if(await E.confirm(`Delete the "${e.label||n}" layout?`))try{await f.layouts.remove(n),E.toast("Layout deleted.",{type:"success"}),s.close(),o(n)}catch(m){E.toast(m?.message||"Failed to delete layout.",{type:"error"})}}),v.appendChild(i)}d.appendChild(v),y.addEventListener("click",async()=>{const i=l.value.trim();if(!i){E.toast("Label is required.",{type:"warning"});return}const w={label:i,description:c.value.trim(),width:r.value,navbar:u.cb.checked,footer:p.cb.checked,sidebar:g.cb.checked,bgColor:b.getValue(),bgImage:C.value.trim(),class:k.value.trim()};try{let m;t?m=await f.layouts.create(w):m=await f.layouts.update(n,w),E.toast("Layout saved.",{type:"success"}),s.close(),a(t?m.key:n,m.preset||{...e,...w})}catch(m){E.toast(m?.message||"Failed to save layout.",{type:"error"})}}),s.setContent(d),s.open()}export const layoutsView={templateUrl:"/admin/js/templates/layouts.html",async onMount(n){E.tabs(n.find("#layouts-tabs").get(0));let e=await f.layouts.get().catch(()=>({}));const t=n.find("#presets-grid");function a(){t.empty();const l=t.get(0);Object.entries(e).forEach(([c,r])=>{const u=O(c,r,(p,g)=>{z(p,g,!1,(b,C)=>{e[b]={...e[b],...C},a()},b=>{delete e[b],a()})});l.appendChild(u)}),I.scan(l)}a(),n.find("#create-layout-btn").on("click",()=>{z(null,{},!0,(l,c)=>{if(!l){E.toast("Layout saved but key missing \u2014 please refresh.",{type:"warning"});return}e[l]=c,a()},()=>{})});const o=await f.layouts.getOptions().catch(()=>({spacerSize:8,spacerClass:""})),s=n.find("#spacer-size-input"),d=n.find("#spacer-class-input");s.val(o.spacerSize??8),d.val(o.spacerClass??""),n.find("#save-options-btn").on("click",async()=>{const l=parseInt(s.val(),10)||8,c=d.val().trim();try{await f.layouts.saveOptions({spacerSize:l,spacerClass:c}),E.toast("Layout options saved.",{type:"success"})}catch{E.toast("Failed to save layout options.",{type:"error"})}})}};
1
+ import{api as f}from"../api.js";const N={narrow:"Narrow",normal:"Normal",wide:"Wide",full:"Full"},B="display:block;font-size:.7rem;font-weight:600;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;margin-bottom:.25rem;",S="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;box-sizing:border-box;";function x(n,t){const e=document.createElement("div");e.style.cssText="display:flex;flex-direction:column;gap:.25rem;";const a=document.createElement("label");return a.style.cssText=B,a.textContent=n,e.appendChild(a),e.appendChild(t),e}function L(n,t){const e=document.createElement("input");return e.type="text",e.placeholder=n||"",e.value=t||"",e.style.cssText=S,e}function k(n,t){const e=document.createElement("label");e.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!!t;const o=document.createElement("span");return o.style.cssText="font-size:.9em;color:var(--dm-text,#eee);",o.textContent=n,e.appendChild(a),e.appendChild(o),{wrap:e,cb:a}}function F(n){const t=document.createElement("select");return t.style.cssText=S,[["narrow","Narrow (768px)"],["normal","Normal (1100px)"],["wide","Wide (75% of viewport, max 2000px)"],["full","Full width"]].forEach(([e,a])=>{const o=document.createElement("option");o.value=e,o.textContent=a,e===(n||"normal")&&(o.selected=!0),t.appendChild(o)}),t}function D(n){const t=document.createElement("div");t.style.cssText="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;";const e=document.createElement("input");e.type="color",e.value=n||"#ffffff",e.disabled=!n,e.style.cssText="width:38px;height:32px;padding:2px;border:1px solid var(--dm-border,#333);border-radius:4px;background:transparent;cursor:pointer;";const a=document.createElement("span");a.style.cssText="font-size:.85em;color:var(--dm-text-muted,#aaa);min-width:4rem;",a.textContent=n||"None";const o=document.createElement("button");o.type="button",o.textContent="Choose",o.style.cssText="padding:.25rem .6rem;font-size:.8em;background:transparent;border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text-muted,#aaa);cursor:pointer;";const r=document.createElement("button");return r.type="button",r.textContent="Clear",r.style.cssText=o.style.cssText,r.disabled=!n,o.addEventListener("click",()=>{e.disabled=!1,r.disabled=!1,e.click()}),e.addEventListener("input",()=>{a.textContent=e.value,r.disabled=!1}),r.addEventListener("click",()=>{e.value="#ffffff",e.disabled=!0,r.disabled=!0,a.textContent="None"}),t.appendChild(e),t.appendChild(a),t.appendChild(o),t.appendChild(r),{wrap:t,getValue(){return e.disabled?"":e.value}}}function O(n,t,e){const a=document.createElement("div");a.className="card preset-card",a.style.cssText="cursor:pointer;transition:box-shadow .15s;",a.addEventListener("mouseenter",()=>{a.style.boxShadow="0 0 0 2px var(--dm-primary,#4f7fff)"}),a.addEventListener("mouseleave",()=>{a.style.boxShadow=""}),a.addEventListener("click",()=>e(n,t));const o=document.createElement("div");o.className="card-header",o.style.cssText="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;";const r=document.createElement("strong");if(r.textContent=t.label||n,o.appendChild(r),t.builtin){const s=document.createElement("span");s.className="badge",s.textContent="built-in",s.style.cssText="font-size:.65rem;",o.appendChild(s)}const d=document.createElement("span");d.className="badge badge-outline",d.textContent=N[t.width]||"Normal",d.style.cssText="margin-left:auto;font-size:.65rem;",o.appendChild(d),a.appendChild(o);const c=document.createElement("div");if(c.className="card-body",t.description){const s=document.createElement("p");s.className="text-muted",s.style.cssText="font-size:.85em;margin:0 0 .5rem;",s.textContent=t.description,c.appendChild(s)}const l=document.createElement("div");if(l.style.cssText="display:flex;gap:.75rem;flex-wrap:wrap;",[["Navbar",t.navbar],["Footer",t.footer],["Sidebar",t.sidebar]].forEach(([s,u])=>{const p=document.createElement("span");p.style.cssText=`font-size:.75em;color:${u?"var(--dm-success,#4caf50)":"var(--dm-text-muted,#666)"};display:flex;align-items:center;gap:.25rem;`,p.textContent=(u?"\u25CF ":"\u25CB ")+s,l.appendChild(p)}),c.appendChild(l),t.bgColor){const s=document.createElement("span");s.title=t.bgColor,s.style.cssText=`display:inline-block;width:14px;height:14px;border-radius:3px;background:${t.bgColor};border:1px solid var(--dm-border,#333);margin-top:.4rem;vertical-align:middle;`,c.appendChild(s)}return a.appendChild(c),a}function z(n,t,e,a,o){const r=E.slideover({title:e?"New Layout":"Edit Layout",size:"md"}),d=document.createElement("div");d.style.cssText="display:flex;flex-direction:column;gap:1rem;padding:1rem;";const c=L("e.g. Dark Landing",t.label||""),l=L("Optional description",t.description||""),s=F(t.width),u=k("Show Navbar",t.navbar!==!1),p=k("Show Footer",t.footer!==!1),g=k("Show Sidebar",t.sidebar===!0),C=D(t.bgColor||""),b=L("https://...",t.bgImage||""),v=L("e.g. no-hero wide-content",t.class||"");d.appendChild(x("Label",c)),d.appendChild(x("Description",l)),d.appendChild(x("Width",s));const y=document.createElement("div");y.style.cssText="display:flex;gap:1.5rem;flex-wrap:wrap;",y.appendChild(u.wrap),y.appendChild(p.wrap),y.appendChild(g.wrap),d.appendChild(y),d.appendChild(x("Background Colour",C.wrap)),d.appendChild(x("Background Image URL",b)),d.appendChild(x("Custom CSS Class",v));const w=document.createElement("div");w.style.cssText="display:flex;gap:.5rem;flex-wrap:wrap;padding-top:.5rem;border-top:1px solid var(--dm-border,#333);";const h=document.createElement("button");if(h.type="button",h.className="btn btn-primary",h.textContent="Save",w.appendChild(h),!e&&!t.builtin){const i=document.createElement("button");i.type="button",i.className="btn btn-danger",i.style.marginLeft="auto",i.textContent="Delete",i.addEventListener("click",async()=>{if(await E.confirm(`Delete the "${t.label||n}" layout?`))try{await f.layouts.remove(n),E.toast("Layout deleted.",{type:"success"}),r.close(),o(n)}catch(m){E.toast(m?.message||"Failed to delete layout.",{type:"error"})}}),w.appendChild(i)}d.appendChild(w),h.addEventListener("click",async()=>{const i=c.value.trim();if(!i){E.toast("Label is required.",{type:"warning"});return}const T={label:i,description:l.value.trim(),width:s.value,navbar:u.cb.checked,footer:p.cb.checked,sidebar:g.cb.checked,bgColor:C.getValue(),bgImage:b.value.trim(),class:v.value.trim()};try{let m;e?m=await f.layouts.create(T):m=await f.layouts.update(n,T),E.toast("Layout saved.",{type:"success"}),r.close(),a(e?m.key:n,m.preset||{...t,...T})}catch(m){E.toast(m?.message||"Failed to save layout.",{type:"error"})}}),r.setContent(d),r.open()}export const layoutsView={templateUrl:"/admin/js/templates/layouts.html",async onMount(n){E.tabs(n.find("#layouts-tabs").get(0));const t=E.loader(n.get(0),{type:"dots"});let e=await f.layouts.get().catch(()=>({}));const a=await f.layouts.getOptions().catch(()=>({spacerSize:8,spacerClass:""}));t.destroy();const o=n.find("#presets-grid");function r(){o.empty();const l=o.get(0);Object.entries(e).forEach(([s,u])=>{const p=O(s,u,(g,C)=>{z(g,C,!1,(b,v)=>{e[b]={...e[b],...v},r()},b=>{delete e[b],r()})});l.appendChild(p)}),I.scan(l)}r(),n.find("#create-layout-btn").on("click",()=>{z(null,{},!0,(l,s)=>{if(!l){E.toast("Layout saved but key missing \u2014 please refresh.",{type:"warning"});return}e[l]=s,r()},()=>{})});const d=n.find("#spacer-size-input"),c=n.find("#spacer-class-input");d.val(a.spacerSize??8),c.val(a.spacerClass??""),n.find("#save-options-btn").on("click",async()=>{const l=parseInt(d.val(),10)||8,s=c.val().trim();try{await f.layouts.saveOptions({spacerSize:l,spacerClass:s}),E.toast("Layout options saved.",{type:"success"})}catch{E.toast("Failed to save layout options.",{type:"error"})}})}};
@@ -1,4 +1,4 @@
1
- import{api as o,isAuthenticated as u,setAuthData as l}from"../api.js";import{THEMES as p}from"../lib/themes.js";const f={name:{type:"string",required:!0,minLength:2,label:"Full Name",formConfig:{placeholder:"Your name",autocomplete:"name"}},email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"admin@example.com",autocomplete:"email"}},password:{type:"password",required:!0,minLength:8,label:"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:"Minimum 8 characters"}}},h={email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"you@example.com",autocomplete:"email"}},password:{type:"password",required:!0,label:"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"current-password"}}};export const loginView={templateUrl:"/admin/js/templates/login.html",async onMount(e){if(u()){R.navigate("/");return}const a=window.location.hash,t=a.match(/[?&]token=([a-f0-9]{64})/);if(a.startsWith("#/reset-password")&&t){r(e,"reset"),S(e,t[1]),Domma.icons.scan();return}if(a.startsWith("#/reset-password")){R.navigate("/login");return}let s=!1;try{s=(await o.auth.setupStatus()).needsSetup}catch{E.toast("Could not reach the server.",{type:"error"})}s?(r(e,"setup"),y(e)):(r(e,"login"),k(e)),Domma.icons.scan()}};const g={email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"you@example.com",autocomplete:"email"}}},b={password:{type:"password",required:!0,minLength:8,label:"New Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:"Minimum 8 characters"}},confirmPassword:{type:"password",required:!0,label:"Confirm Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password"}}},w=["setup","onboarding-site","onboarding-theme","onboarding-done","login","forgot","forgot-success","reset"];function r(e,a){w.forEach(t=>e.find(`#${t}-panel`).hide()),e.find(`#${a}-panel`).show(),Domma.icons.scan()}function d(e,a,t){e.find(`#${a}`).text(t).show()}function y(e){F.render("#setup-form-container",f,{},{layout:"stacked",submitText:"Create admin account",onSubmit:async a=>{try{const t=await o.auth.setup(a);l(t),r(e,"onboarding-site"),v(e)}catch(t){return E.toast(t.message||"Setup failed. Please try again.",{type:"error"}),!1}}})}function v(e){e.find("#ob-site-skip").on("click",a=>{a.preventDefault(),r(e,"onboarding-theme"),c(e),m(e)}),e.find("#ob-site-btn").on("click",async()=>{e.find("#ob-site-error").hide();const a=e.find("#ob-title").val().trim(),t=e.find("#ob-tagline").val().trim(),s=e.find("#ob-site-btn").prop("disabled",!0).text("Saving\u2026");try{const i=await o.settings.get();await o.settings.save({...i,title:a||i.title,tagline:t||i.tagline,seo:{...i.seo||{},defaultTitle:a||i.seo&&i.seo.defaultTitle}});const n=await o.navigation.get();await o.navigation.save({...n,brand:{...n.brand||{},text:a||n.brand.text}}),r(e,"onboarding-theme"),c(e),m(e)}catch(i){d(e,"ob-site-error",i.message||"Could not save site details.")}finally{s.prop("disabled",!1).text("Continue")}})}function c(e){const a=e.find("#theme-grid").empty();p.forEach(t=>{const s=$(`
1
+ import{api as o,isAuthenticated as u,setAuthData as l}from"../api.js";import{THEMES as p}from"../lib/themes.js";const f={name:{type:"string",required:!0,minLength:2,label:"Full Name",formConfig:{placeholder:"Your name",autocomplete:"name"}},email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"admin@example.com",autocomplete:"email"}},password:{type:"password",required:!0,minLength:8,label:"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:"Minimum 8 characters"}}},h={email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"you@example.com",autocomplete:"email"}},password:{type:"password",required:!0,label:"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"current-password"}}};export const loginView={templateUrl:"/admin/js/templates/login.html",async onMount(e){if(u()){R.navigate("/");return}const a=window.location.hash,t=a.match(/[?&]token=([a-f0-9]{64})/);if(a.startsWith("#/reset-password")&&t){r(e,"reset"),S(e,t[1]),Domma.icons.scan();return}if(a.startsWith("#/reset-password")){R.navigate("/login");return}let s=!1;const i=E.loader(e.get(0),{type:"dots"});try{s=(await o.auth.setupStatus()).needsSetup}catch{E.toast("Could not reach the server.",{type:"error"})}finally{i.destroy()}s?(r(e,"setup"),y(e)):(r(e,"login"),k(e)),Domma.icons.scan()}};const g={email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"you@example.com",autocomplete:"email"}}},b={password:{type:"password",required:!0,minLength:8,label:"New Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:"Minimum 8 characters"}},confirmPassword:{type:"password",required:!0,label:"Confirm Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password"}}},w=["setup","onboarding-site","onboarding-theme","onboarding-done","login","forgot","forgot-success","reset"];function r(e,a){w.forEach(t=>e.find(`#${t}-panel`).hide()),e.find(`#${a}-panel`).show(),Domma.icons.scan()}function d(e,a,t){e.find(`#${a}`).text(t).show()}function y(e){F.render("#setup-form-container",f,{},{layout:"stacked",submitText:"Create admin account",onSubmit:async a=>{try{const t=await o.auth.setup(a);l(t),r(e,"onboarding-site"),v(e)}catch(t){return E.toast(t.message||"Setup failed. Please try again.",{type:"error"}),!1}}})}function v(e){e.find("#ob-site-skip").on("click",a=>{a.preventDefault(),r(e,"onboarding-theme"),c(e),m(e)}),e.find("#ob-site-btn").on("click",async()=>{e.find("#ob-site-error").hide();const a=e.find("#ob-title").val().trim(),t=e.find("#ob-tagline").val().trim(),s=e.find("#ob-site-btn").prop("disabled",!0).text("Saving\u2026");try{const i=await o.settings.get();await o.settings.save({...i,title:a||i.title,tagline:t||i.tagline,seo:{...i.seo||{},defaultTitle:a||i.seo&&i.seo.defaultTitle}});const n=await o.navigation.get();await o.navigation.save({...n,brand:{...n.brand||{},text:a||n.brand.text}}),r(e,"onboarding-theme"),c(e),m(e)}catch(i){d(e,"ob-site-error",i.message||"Could not save site details.")}finally{s.prop("disabled",!1).text("Continue")}})}function c(e){const a=e.find("#theme-grid").empty();p.forEach(t=>{const s=$(`
2
2
  <div class="theme-swatch" data-theme="${t.id}" title="${t.id}">
3
3
  <div class="theme-swatch-preview" style="background:${t.bg}">
4
4
  <div class="theme-swatch-accent" style="background:${t.primary}"></div>
@@ -1,5 +1,5 @@
1
- import{api as x}from"../api.js";import{colourToCss as U}from"/public/js/menu-decor.mjs";import{openIconPicker as V}from"../lib/card-builder.js";let G=1;const C=()=>"i_"+G++,K=760;function q(t,l=null,a=[]){for(const n of t||[]){const e=C();if(n&&n.type==="separator"){a.push({id:e,parentId:l,type:"separator"});continue}a.push({id:e,parentId:l,text:n.text||"",url:n.url||"",icon:n.icon||"",visibility:n.visibility||"",permission:n.permission||"",hidden:!!n.hidden,bundled:!!n.bundled,badge:n.badge?{...n.badge}:null,pill:n.pill?{...n.pill}:null,colour:n.colour||""}),Array.isArray(n.items)&&n.items.length&&q(n.items,e,a)}return a}function X(t){const l=new Map;for(const n of t)l.has(n.parentId)||l.set(n.parentId,[]),l.get(n.parentId).push(n);function a(n){return(l.get(n)||[]).map(e=>{if(e.type==="separator")return{type:"separator"};const r=a(e.id);return{text:(e.text||"").trim(),url:(e.url||"").trim(),...e.icon&&{icon:e.icon.trim()},...e.visibility&&{visibility:e.visibility},...e.permission&&{permission:e.permission},...e.hidden&&{hidden:!0},...e.bundled&&{bundled:!0},...e.badge&&(e.badge.text||e.badge.countFrom)?{badge:{...e.badge.text&&{text:e.badge.text},...e.badge.countFrom&&{countFrom:e.badge.countFrom},...e.badge.variant&&{variant:e.badge.variant}}}:{},...e.pill&&e.pill.variant?{pill:{style:e.pill.style||"filled",variant:e.pill.variant}}:{},...e.colour&&{colour:e.colour},...r.length&&{items:r}}})}return a(null)}function O(t){return String(t||"").replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;")}function J(t,l){let a=0,n=l.find(e=>e.id===t);for(;n&&n.parentId;)a++,n=l.find(e=>e.id===n.parentId);return a}function w(t,l,a=()=>""){t.html("");function n(e){for(const r of l.filter(f=>f.parentId===e)){const f=J(r.id,l);if(r.type==="separator"){t.append(`
2
- <div class="menu-tree-row menu-tree-row--separator" data-id="${r.id}" style="margin-left:${f*28}px">
1
+ import{api as k}from"../api.js";import{colourToCss as U}from"/public/js/menu-decor.mjs";import{openIconPicker as V}from"../lib/card-builder.js";let G=1;const N=()=>"i_"+G++,K=760;function q(t,r=null,l=[]){for(const e of t||[]){const n=N();if(e&&e.type==="separator"){l.push({id:n,parentId:r,type:"separator"});continue}l.push({id:n,parentId:r,text:e.text||"",url:e.url||"",icon:e.icon||"",visibility:e.visibility||"",permission:e.permission||"",hidden:!!e.hidden,bundled:!!e.bundled,badge:e.badge?{...e.badge}:null,pill:e.pill?{...e.pill}:null,colour:e.colour||""}),Array.isArray(e.items)&&e.items.length&&q(e.items,n,l)}return l}function X(t){const r=new Map;for(const e of t)r.has(e.parentId)||r.set(e.parentId,[]),r.get(e.parentId).push(e);function l(e){return(r.get(e)||[]).map(n=>{if(n.type==="separator")return{type:"separator"};const i=l(n.id);return{text:(n.text||"").trim(),url:(n.url||"").trim(),...n.icon&&{icon:n.icon.trim()},...n.visibility&&{visibility:n.visibility},...n.permission&&{permission:n.permission},...n.hidden&&{hidden:!0},...n.bundled&&{bundled:!0},...n.badge&&(n.badge.text||n.badge.countFrom)?{badge:{...n.badge.text&&{text:n.badge.text},...n.badge.countFrom&&{countFrom:n.badge.countFrom},...n.badge.variant&&{variant:n.badge.variant}}}:{},...n.pill&&n.pill.variant?{pill:{style:n.pill.style||"filled",variant:n.pill.variant}}:{},...n.colour&&{colour:n.colour},...i.length&&{items:i}}})}return l(null)}function O(t){return String(t||"").replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;")}function J(t,r){let l=0,e=r.find(n=>n.id===t);for(;e&&e.parentId;)l++,e=r.find(n=>n.id===e.parentId);return l}function v(t,r,l=()=>""){t.html("");function e(n){for(const i of r.filter(b=>b.parentId===n)){const b=J(i.id,r);if(i.type==="separator"){t.append(`
2
+ <div class="menu-tree-row menu-tree-row--separator" data-id="${i.id}" style="margin-left:${b*28}px">
3
3
  <span class="menu-tree-grip" data-icon="move"></span>
4
4
  <span class="me-sep-line"><span class="me-sep-label">Separator</span></span>
5
5
  <button class="btn btn-sm btn-ghost me-ins-item" data-tooltip="Add item below"><span data-icon="plus"></span></button>
@@ -10,19 +10,19 @@ import{api as x}from"../api.js";import{colourToCss as U}from"/public/js/menu-dec
10
10
  <button class="btn btn-sm btn-ghost me-down" data-tooltip="Move down"><span data-icon="arrow-down"></span></button>
11
11
  <button class="btn btn-sm btn-danger me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
12
12
  </div>
13
- `);continue}const S=r.badge&&(r.badge.text||r.badge.countFrom)?`<span class="dm-menu-badge">${O(String(r.badge.text||"#"))}</span>`:"",y=U(r.colour),F=y?` style="color:${O(y)}"`:"";t.append(`
14
- <div class="menu-tree-row menu-tree-row--slim${r.hidden?" menu-tree-row--hidden":""}" data-id="${r.id}" style="margin-left:${f*28}px">
13
+ `);continue}const y=i.badge&&(i.badge.text||i.badge.countFrom)?`<span class="dm-menu-badge">${O(String(i.badge.text||"#"))}</span>`:"",w=U(i.colour),x=w?` style="color:${O(w)}"`:"";t.append(`
14
+ <div class="menu-tree-row menu-tree-row--slim${i.hidden?" menu-tree-row--hidden":""}" data-id="${i.id}" style="margin-left:${b*28}px">
15
15
  <span class="menu-tree-grip" data-icon="move"></span>
16
- <span class="me-row-label"${F}>${O(r.text||"(untitled)")}${S}</span>
17
- <span class="me-row-url">${O(r.url||"")}</span>
16
+ <span class="me-row-label"${x}>${O(i.text||"(untitled)")}${y}</span>
17
+ <span class="me-row-url">${O(i.url||"")}</span>
18
18
  <button class="btn btn-sm btn-ghost me-ins-item" data-tooltip="Add item below"><span data-icon="plus"></span></button>
19
19
  <button class="btn btn-sm btn-ghost me-ins-sep" data-tooltip="Add separator below"><span data-icon="minus"></span></button>
20
20
  <button class="btn btn-sm btn-ghost me-outdent" data-tooltip="Outdent"><span data-icon="chevron-left"></span></button>
21
21
  <button class="btn btn-sm btn-ghost me-indent" data-tooltip="Indent"><span data-icon="chevron-right"></span></button>
22
22
  <button class="btn btn-sm btn-ghost me-up" data-tooltip="Move up"><span data-icon="arrow-up"></span></button>
23
23
  <button class="btn btn-sm btn-ghost me-down" data-tooltip="Move down"><span data-icon="arrow-down"></span></button>
24
- <button class="btn btn-sm btn-ghost me-hidden ${r.hidden?"active":""}" data-tooltip="${r.hidden?"Show":"Hide"}"><span data-icon="eye-off"></span></button>
24
+ <button class="btn btn-sm btn-ghost me-hidden ${i.hidden?"active":""}" data-tooltip="${i.hidden?"Show":"Hide"}"><span data-icon="eye-off"></span></button>
25
25
  <button class="btn btn-sm btn-secondary me-edit" data-tooltip="Edit"><span data-icon="edit"></span></button>
26
26
  <button class="btn btn-sm btn-danger me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
27
27
  </div>
28
- `),n(r.id)}}n(null),Domma.icons.scan(t.get(0)),t.get(0).querySelectorAll("[data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})})}function I(t,l){t.find(".menu-tree-row").each(function(){const a=$(this).data("id"),n=l.find(F=>F.id===a);if(!n||n.type==="separator")return;const e=$(this).find(".me-text");e.length&&(n.text=e.val());const r=$(this).find(".me-url");r.length&&(n.url=r.val());const f=$(this).find(".me-icon");f.length&&(n.icon=f.val());const S=$(this).find(".me-vis");S.length&&(n.visibility=S.val());const y=$(this).find(".me-perm");y.length&&(n.permission=y.val())})}function it(t,l){const a={};for(const n of l){const e=t.querySelector(`[name="${n}"]`);e&&(a[n]=e.type==="checkbox"?e.checked:e.value)}return a}function h(t,l={},...a){const n=document.createElement(t);Object.assign(n,l);for(const e of a)e!=null&&n.append(e);return n}function k(t,l,a){const n=h("label",{className:"form-field"},h("span",{className:"form-label",textContent:t}),l);return a&&n.append(h("small",{className:"form-hint",textContent:a,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),n}function Q(t,l){const a=[["","\u2014 Everyone \u2014"],["public","Public \u2014 anyone, incl. logged-out"],...(l||[]).map(e=>[e.name,`${e.label||e.name} and above`]),["private","Private \u2014 super-admin only"]];t&&!a.some(([e])=>e===t)&&a.push([t,`${t} (custom)`]);const n=P(a,t||"");return n.name="visibility",n}function P(t,l){const a=h("select",{className:"form-select"});return t.forEach(([n,e])=>a.add(new Option(e,n))),a.value=l??"",a}function B(t,l){const a=typeof l=="string"&&l.startsWith("#"),n=P([["","\u2014 none \u2014"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],a?"__custom__":l||""),e=h("input",{type:"color",className:"form-input me-colour-hex",value:a?l:"#000000"});e.style.display=a?"":"none",n.addEventListener("change",()=>{e.style.display=n.value==="__custom__"?"":"none"});const r=k(t,n);return r.append(e),r._read=()=>n.value==="__custom__"?e.value:n.value,r}function Y(t,l){const a=h("input",{className:"form-input me-icon",name:"icon",value:l||""}),n=h("span",{className:"me-icon-preview"});l&&n.setAttribute("data-icon",l);const e=h("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),r=f=>{f?n.setAttribute("data-icon",f):n.removeAttribute("data-icon"),Domma.icons.scan(n.parentNode||n)};return e.addEventListener("click",()=>V(f=>{a.value=f,r(f)})),a.addEventListener("input",()=>r(a.value.trim())),k(t,h("div",{className:"me-icon-field"},a,e,n))}function W(...t){return h("div",{className:"me-field-grid"},...t)}async function Z(t,l,a,n,e){const r=g=>h("h4",{className:"me-form-section",textContent:g}),f=k("Label",h("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),S=k("URL",h("input",{className:"form-input",name:"url",value:t.url||""}),"Where it links. Must start with /, #, https://, or mailto: . Leave blank for a parent that only groups sub-items."),y=Y("Icon",t.icon||""),F=k("Visibility",Q(t.visibility||"",n),"Who can see this item, by role seniority. Picking a role shows it to that role and anyone more senior. Blank = everyone."),L=P([["","\u2014 no permission gate \u2014"],...(l||[]).map(g=>[g.key,g.label||g.key])],t.permission||"");L.name="permission";const z=k("Permission",L,"Hide unless the viewer holds this specific permission. Independent of Visibility \u2014 both must pass."),v=k("Badge text",h("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),i=P([["","\u2014 none \u2014"],...(a||[]).map(g=>[g.slug,g.title||g.slug])],t.badge?.countFrom||"");i.name="badgeCount";const m=k("Badge count from",i,"Show a live count of this collection\u2019s entries as the badge."),A=B("Badge colour",t.badge?.variant||""),_=P([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");_.name="renderAs";const T=k("Render as",_,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),R=k("Pill style",P([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));R.querySelector("select").name="pillStyle";const N=B("Pill colour",t.pill?.variant||""),o=h("div",{className:"me-pill-wrap"},W(R,N));o.style.display=t.pill?"":"none",_.addEventListener("change",()=>{o.style.display=_.value==="pill"?"":"none"});const s=B("Text colour",t.colour||""),d=h("button",{className:"btn btn-primary",textContent:"Apply"}),p=h("div",{className:"me-item-form"},r("Link"),f,S,y,r("Access"),W(F,z),r("Badge"),W(v,A),m,r("Appearance"),T,o,s,d),c=E.slideover({title:"Edit menu item",size:"lg",position:"right"});c.element.appendChild(p),c.open();const u=c.element.closest(".dm-slideover")||c.element.querySelector(".dm-slideover")||c.element;u.style.setProperty("width",K+"px","important"),u.style.setProperty("max-width","95vw","important"),Domma.icons.scan(p);const b=g=>p.querySelector(`[name="${g}"]`)?.value||"";d.addEventListener("click",()=>{t.text=b("text"),t.url=b("url"),t.icon=b("icon"),t.visibility=b("visibility"),t.permission=b("permission");const g=b("badgeText"),j=b("badgeCount"),M=A._read();if(t.badge=g||j?{...g&&{text:g},...j&&{countFrom:j},...M&&{variant:M}}:null,b("renderAs")==="pill"){const D=N._read();t.pill={style:b("pillStyle")||"filled",...D&&{variant:D}}}else t.pill=null;t.colour=s._read(),c.close(),e()})}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const l=location.hash.match(/^#\/menus\/edit\/(.+)$/),a=l?decodeURIComponent(l[1]):null,n=!a;let e;if(n)e={slug:"",name:"",description:"",items:[]};else try{e=await x.menus.get(a)}catch(o){E.toast(`Failed to load menu: ${o.message||o}`,{type:"error"}),location.hash="#/menus";return}const r=await x.projects.list().catch(()=>[]),f=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),S=await x.collections.list().catch(()=>[]),y=await x.get("/collections/roles/entries?limit=100").catch(()=>null),L=(Array.isArray(y)?y:y?.entries||[]).map(o=>({name:o.data?.name,label:o.data?.label,level:o.data?.level??99})).filter(o=>o.name).sort((o,s)=>s.level-o.level),z='<option value="">\u2014 no permission gate \u2014</option>',v=o=>z+(f.resources||[]).map(s=>{const d=(o||"")===s.key?" selected":"";return`<option value="${s.key}"${d}>${s.label||s.key}</option>`}).join("");t.find("#me-title").text(n?"New menu":`Edit "${e.slug}"`);let i=q(e.items);const m=t.find("#me-items-list");w(m,i,v),t.find("#me-add-item").on("click",()=>{I(m,i),i.push({id:C(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),w(m,i,v)}),t.find("#me-add-separator").on("click",()=>{I(m,i),i.push({id:C(),parentId:null,type:"separator"}),w(m,i,v)});function A(o){return function(){I(m,i);const s=$(this).closest(".menu-tree-row").data("id"),d=i.findIndex(u=>u.id===s);if(d===-1)return;const p=i[d],c=o==="separator"?{id:C(),parentId:p.parentId,type:"separator"}:{id:C(),parentId:p.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};i.splice(d+1,0,c),w(m,i,v)}}t.off("click",".me-ins-item").on("click",".me-ins-item",A("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",A("separator")),t.off("click",".me-remove").on("click",".me-remove",function(){const o=$(this).closest(".menu-tree-row").data("id");i=i.filter(s=>s.id!==o&&s.parentId!==o),w(m,i,v)}),t.off("click",".me-indent").on("click",".me-indent",function(){I(m,i);const o=$(this).closest(".menu-tree-row").data("id"),s=i.find(c=>c.id===o);if(!s)return;const d=i.filter(c=>c.parentId===s.parentId),p=d.findIndex(c=>c.id===o);p<=0||d[p-1].type!=="separator"&&(s.parentId=d[p-1].id,w(m,i,v))}),t.off("click",".me-outdent").on("click",".me-outdent",function(){I(m,i);const o=$(this).closest(".menu-tree-row").data("id"),s=i.find(p=>p.id===o);if(!s||s.parentId==null)return;const d=i.find(p=>p.id===s.parentId);s.parentId=d?d.parentId:null,w(m,i,v)}),t.off("click",".me-up").on("click",".me-up",function(){I(m,i);const o=$(this).closest(".menu-tree-row").data("id"),s=i.filter(u=>u.parentId===i.find(b=>b.id===o)?.parentId),d=s.findIndex(u=>u.id===o);if(d<=0)return;const p=i.indexOf(s[d]),c=i.indexOf(s[d-1]);[i[p],i[c]]=[i[c],i[p]],w(m,i,v)}),t.off("click",".me-down").on("click",".me-down",function(){I(m,i);const o=$(this).closest(".menu-tree-row").data("id"),s=i.filter(u=>u.parentId===i.find(b=>b.id===o)?.parentId),d=s.findIndex(u=>u.id===o);if(d===-1||d>=s.length-1)return;const p=i.indexOf(s[d]),c=i.indexOf(s[d+1]);[i[p],i[c]]=[i[c],i[p]],w(m,i,v)}),t.off("click",".me-hidden").on("click",".me-hidden",function(){I(m,i);const o=$(this).closest(".menu-tree-row").data("id"),s=i.find(d=>d.id===o);s&&(s.hidden=!s.hidden),w(m,i,v)}),t.off("click",".me-edit").on("click",".me-edit",function(){const o=$(this).closest(".menu-tree-row").data("id"),s=i.find(d=>d.id===o);!s||s.type==="separator"||Z(s,f.resources||[],S,L,()=>w(m,i,v))}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(e.variant||""),t.find("#me-position").val(e.position||"");const _=t.find("#me-appearOnHover").get(0);_&&(_.checked=e.appearOnHover===!0),t.find("#me-fontFamily").val(e.style?.fontFamily||""),t.find("#me-fontSize").val(e.style?.fontSize||""),t.find("#me-fontWeight").val(e.style?.fontWeight||""),t.find("#me-letterSpacing").val(e.style?.letterSpacing||""),t.find("#me-iconSize").val(e.style?.iconSize||"");const{getProjectFromHash:T}=await import("../lib/project-context.js"),R=n?T():null,N=t.find("#me-project");N.html('<option value="">\u2014 none \u2014</option>'+r.map(o=>`<option value="${O(o.slug)}">${O(o.name||o.slug)}</option>`).join("")),t.find("#me-slug").val(e.slug||""),t.find("#me-name").val(e.name||""),t.find("#me-description").val(e.description||""),N.val(e.meta?.project||R||""),Domma.icons.scan(t.get(0)),t.find("#me-save").on("click",async()=>{I(m,i);const o={variant:t.find("#me-variant").val(),position:t.find("#me-position").val(),appearOnHover:!!(t.find("#me-appearOnHover").get(0)||{}).checked,fontFamily:t.find("#me-fontFamily").val(),fontSize:t.find("#me-fontSize").val(),fontWeight:t.find("#me-fontWeight").val(),letterSpacing:t.find("#me-letterSpacing").val(),iconSize:t.find("#me-iconSize").val()},s={slug:t.find("#me-slug").val().trim(),name:t.find("#me-name").val().trim(),description:t.find("#me-description").val().trim(),project:t.find("#me-project").val()},d=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"],p=Object.fromEntries(Object.entries(o).filter(([u,b])=>d.includes(u)&&b)),c={slug:s.slug,name:s.name,description:s.description,...o.variant&&{variant:o.variant},...o.position&&{position:o.position},...o.appearOnHover&&{appearOnHover:!0},...Object.keys(p).length&&{style:p},items:X(i),meta:{...e.meta||{},project:s.project||null}};try{if(n)await x.menus.create(c);else if(c.slug!==e.slug){await x.menus.create(c);const u=await x.menuLocations.get();let b=!1;for(const[g,j]of Object.entries(u))j===e.slug&&(u[g]=c.slug,b=!0);b&&await x.menuLocations.save(u),await x.menus.remove(e.slug)}else await x.menus.update(e.slug,c);E.toast("Saved.",{type:"success"}),location.hash="#/menus/edit/"+encodeURIComponent(c.slug)}catch(u){E.toast(`Save failed: ${u.message||u}`,{type:"error"})}})}};
28
+ `),e(i.id)}}e(null),Domma.icons.scan(t.get(0)),t.get(0).querySelectorAll("[data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})}function S(t,r){t.find(".menu-tree-row").each(function(){const l=$(this).data("id"),e=r.find(x=>x.id===l);if(!e||e.type==="separator")return;const n=$(this).find(".me-text");n.length&&(e.text=n.val());const i=$(this).find(".me-url");i.length&&(e.url=i.val());const b=$(this).find(".me-icon");b.length&&(e.icon=b.val());const y=$(this).find(".me-vis");y.length&&(e.visibility=y.val());const w=$(this).find(".me-perm");w.length&&(e.permission=w.val())})}function it(t,r){const l={};for(const e of r){const n=t.querySelector(`[name="${e}"]`);n&&(l[e]=n.type==="checkbox"?n.checked:n.value)}return l}function g(t,r={},...l){const e=document.createElement(t);Object.assign(e,r);for(const n of l)n!=null&&e.append(n);return e}function I(t,r,l){const e=g("label",{className:"form-field"},g("span",{className:"form-label",textContent:t}),r);return l&&e.append(g("small",{className:"form-hint",textContent:l,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),e}function Q(t,r){const l=[["","\u2014 Everyone \u2014"],["public","Public \u2014 anyone, incl. logged-out"],...(r||[]).map(n=>[n.name,`${n.label||n.name} and above`]),["private","Private \u2014 super-admin only"]];t&&!l.some(([n])=>n===t)&&l.push([t,`${t} (custom)`]);const e=P(l,t||"");return e.name="visibility",e}function P(t,r){const l=g("select",{className:"form-select"});return t.forEach(([e,n])=>l.add(new Option(n,e))),l.value=r??"",l}function W(t,r){const l=typeof r=="string"&&r.startsWith("#"),e=P([["","\u2014 none \u2014"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],l?"__custom__":r||""),n=g("input",{type:"color",className:"form-input me-colour-hex",value:l?r:"#000000"});n.style.display=l?"":"none",e.addEventListener("change",()=>{n.style.display=e.value==="__custom__"?"":"none"});const i=I(t,e);return i.append(n),i._read=()=>e.value==="__custom__"?n.value:e.value,i}function Y(t,r){const l=g("input",{className:"form-input me-icon",name:"icon",value:r||""}),e=g("span",{className:"me-icon-preview"});r&&e.setAttribute("data-icon",r);const n=g("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),i=b=>{b?e.setAttribute("data-icon",b):e.removeAttribute("data-icon"),Domma.icons.scan(e.parentNode||e)};return n.addEventListener("click",()=>V(b=>{l.value=b,i(b)})),l.addEventListener("input",()=>i(l.value.trim())),I(t,g("div",{className:"me-icon-field"},l,n,e))}function M(...t){return g("div",{className:"me-field-grid"},...t)}async function Z(t,r,l,e,n){const i=m=>g("h4",{className:"me-form-section",textContent:m}),b=I("Label",g("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),y=I("URL",g("input",{className:"form-input",name:"url",value:t.url||""}),"Where it links. Must start with /, #, https://, or mailto: . Leave blank for a parent that only groups sub-items."),w=Y("Icon",t.icon||""),x=I("Visibility",Q(t.visibility||"",e),"Who can see this item, by role seniority. Picking a role shows it to that role and anyone more senior. Blank = everyone."),z=P([["","\u2014 no permission gate \u2014"],...(r||[]).map(m=>[m.key,m.label||m.key])],t.permission||"");z.name="permission";const T=I("Permission",z,"Hide unless the viewer holds this specific permission. Independent of Visibility \u2014 both must pass."),B=I("Badge text",g("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),h=P([["","\u2014 none \u2014"],...(l||[]).map(m=>[m.slug,m.title||m.slug])],t.badge?.countFrom||"");h.name="badgeCount";const o=I("Badge count from",h,"Show a live count of this collection\u2019s entries as the badge."),u=W("Badge colour",t.badge?.variant||""),_=P([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");_.name="renderAs";const j=I("Render as",_,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),C=I("Pill style",P([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));C.querySelector("select").name="pillStyle";const L=W("Pill colour",t.pill?.variant||""),F=g("div",{className:"me-pill-wrap"},M(C,L));F.style.display=t.pill?"":"none",_.addEventListener("change",()=>{F.style.display=_.value==="pill"?"":"none"});const s=W("Text colour",t.colour||""),a=g("button",{className:"btn btn-primary",textContent:"Apply"}),d=g("div",{className:"me-item-form"},i("Link"),b,y,w,i("Access"),M(x,T),i("Badge"),M(B,u),o,i("Appearance"),j,F,s,a),p=E.slideover({title:"Edit menu item",size:"lg",position:"right"});p.element.appendChild(d),p.open();const f=p.element.closest(".dm-slideover")||p.element.querySelector(".dm-slideover")||p.element;f.style.setProperty("width",K+"px","important"),f.style.setProperty("max-width","95vw","important"),Domma.icons.scan(d);const c=m=>d.querySelector(`[name="${m}"]`)?.value||"";a.addEventListener("click",()=>{t.text=c("text"),t.url=c("url"),t.icon=c("icon"),t.visibility=c("visibility"),t.permission=c("permission");const m=c("badgeText"),A=c("badgeCount"),R=u._read();if(t.badge=m||A?{...m&&{text:m},...A&&{countFrom:A},...R&&{variant:R}}:null,c("renderAs")==="pill"){const D=L._read();t.pill={style:c("pillStyle")||"filled",...D&&{variant:D}}}else t.pill=null;t.colour=s._read(),p.close(),n()})}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const r=location.hash.match(/^#\/menus\/edit\/(.+)$/),l=r?decodeURIComponent(r[1]):null,e=!l,n=E.loader(t.get(0),{type:"dots"});let i,b,y,w,x;try{if(e)i={slug:"",name:"",description:"",items:[]};else try{i=await k.menus.get(l)}catch(s){E.toast(`Failed to load menu: ${s.message||s}`,{type:"error"}),location.hash="#/menus";return}b=await k.projects.list().catch(()=>[]),y=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),w=await k.collections.list().catch(()=>[]),x=await k.get("/collections/roles/entries?limit=100").catch(()=>null)}finally{n.destroy()}const T=(Array.isArray(x)?x:x?.entries||[]).map(s=>({name:s.data?.name,label:s.data?.label,level:s.data?.level??99})).filter(s=>s.name).sort((s,a)=>a.level-s.level),B='<option value="">\u2014 no permission gate \u2014</option>',h=s=>B+(y.resources||[]).map(a=>{const d=(s||"")===a.key?" selected":"";return`<option value="${a.key}"${d}>${a.label||a.key}</option>`}).join("");t.find("#me-title").text(e?"New menu":`Edit "${i.slug}"`);let o=q(i.items);const u=t.find("#me-items-list");v(u,o,h),t.find("#me-add-item").on("click",()=>{S(u,o),o.push({id:N(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),v(u,o,h)}),t.find("#me-add-separator").on("click",()=>{S(u,o),o.push({id:N(),parentId:null,type:"separator"}),v(u,o,h)});function _(s){return function(){S(u,o);const a=$(this).closest(".menu-tree-row").data("id"),d=o.findIndex(c=>c.id===a);if(d===-1)return;const p=o[d],f=s==="separator"?{id:N(),parentId:p.parentId,type:"separator"}:{id:N(),parentId:p.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};o.splice(d+1,0,f),v(u,o,h)}}t.off("click",".me-ins-item").on("click",".me-ins-item",_("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",_("separator")),t.off("click",".me-remove").on("click",".me-remove",function(){const s=$(this).closest(".menu-tree-row").data("id");o=o.filter(a=>a.id!==s&&a.parentId!==s),v(u,o,h)}),t.off("click",".me-indent").on("click",".me-indent",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.find(f=>f.id===s);if(!a)return;const d=o.filter(f=>f.parentId===a.parentId),p=d.findIndex(f=>f.id===s);p<=0||d[p-1].type!=="separator"&&(a.parentId=d[p-1].id,v(u,o,h))}),t.off("click",".me-outdent").on("click",".me-outdent",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.find(p=>p.id===s);if(!a||a.parentId==null)return;const d=o.find(p=>p.id===a.parentId);a.parentId=d?d.parentId:null,v(u,o,h)}),t.off("click",".me-up").on("click",".me-up",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.filter(c=>c.parentId===o.find(m=>m.id===s)?.parentId),d=a.findIndex(c=>c.id===s);if(d<=0)return;const p=o.indexOf(a[d]),f=o.indexOf(a[d-1]);[o[p],o[f]]=[o[f],o[p]],v(u,o,h)}),t.off("click",".me-down").on("click",".me-down",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.filter(c=>c.parentId===o.find(m=>m.id===s)?.parentId),d=a.findIndex(c=>c.id===s);if(d===-1||d>=a.length-1)return;const p=o.indexOf(a[d]),f=o.indexOf(a[d+1]);[o[p],o[f]]=[o[f],o[p]],v(u,o,h)}),t.off("click",".me-hidden").on("click",".me-hidden",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.find(d=>d.id===s);a&&(a.hidden=!a.hidden),v(u,o,h)}),t.off("click",".me-edit").on("click",".me-edit",function(){const s=$(this).closest(".menu-tree-row").data("id"),a=o.find(d=>d.id===s);!a||a.type==="separator"||Z(a,y.resources||[],w,T,()=>v(u,o,h))}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(i.variant||""),t.find("#me-position").val(i.position||"");const j=t.find("#me-appearOnHover").get(0);j&&(j.checked=i.appearOnHover===!0),t.find("#me-fontFamily").val(i.style?.fontFamily||""),t.find("#me-fontSize").val(i.style?.fontSize||""),t.find("#me-fontWeight").val(i.style?.fontWeight||""),t.find("#me-letterSpacing").val(i.style?.letterSpacing||""),t.find("#me-iconSize").val(i.style?.iconSize||"");const{getProjectFromHash:C}=await import("../lib/project-context.js"),L=e?C():null,F=t.find("#me-project");F.html('<option value="">\u2014 none \u2014</option>'+b.map(s=>`<option value="${O(s.slug)}">${O(s.name||s.slug)}</option>`).join("")),t.find("#me-slug").val(i.slug||""),t.find("#me-name").val(i.name||""),t.find("#me-description").val(i.description||""),F.val(i.meta?.project||L||""),Domma.icons.scan(t.get(0)),t.find("#me-save").on("click",async()=>{S(u,o);const s={variant:t.find("#me-variant").val(),position:t.find("#me-position").val(),appearOnHover:!!(t.find("#me-appearOnHover").get(0)||{}).checked,fontFamily:t.find("#me-fontFamily").val(),fontSize:t.find("#me-fontSize").val(),fontWeight:t.find("#me-fontWeight").val(),letterSpacing:t.find("#me-letterSpacing").val(),iconSize:t.find("#me-iconSize").val()},a={slug:t.find("#me-slug").val().trim(),name:t.find("#me-name").val().trim(),description:t.find("#me-description").val().trim(),project:t.find("#me-project").val()},d=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"],p=Object.fromEntries(Object.entries(s).filter(([c,m])=>d.includes(c)&&m)),f={slug:a.slug,name:a.name,description:a.description,...s.variant&&{variant:s.variant},...s.position&&{position:s.position},...s.appearOnHover&&{appearOnHover:!0},...Object.keys(p).length&&{style:p},items:X(o),meta:{...i.meta||{},project:a.project||null}};try{if(e)await k.menus.create(f);else if(f.slug!==i.slug){await k.menus.create(f);const c=await k.menuLocations.get();let m=!1;for(const[A,R]of Object.entries(c))R===i.slug&&(c[A]=f.slug,m=!0);m&&await k.menuLocations.save(c),await k.menus.remove(i.slug)}else await k.menus.update(i.slug,f);E.toast("Saved.",{type:"success"}),location.hash="#/menus/edit/"+encodeURIComponent(f.slug)}catch(c){E.toast(`Save failed: ${c.message||c}`,{type:"error"})}})}};
@@ -1 +1 @@
1
- import{api as i}from"../api.js";function m(s){if(!Array.isArray(s)||s.length===0)return 0;let l=0;for(const c of s){const r=Array.isArray(c.items)&&c.items.length?m(c.items):0;r>l&&(l=r)}return l+1}function a(s){return String(s??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}export const menuLocationsView={templateUrl:"/admin/js/templates/menu-locations.html",async onMount(s){const[l,c,r]=await Promise.all([i.menuLocations.registry().catch(()=>[]),i.menuLocations.get().catch(()=>({})),i.menus.list().catch(()=>[])]),u=await Promise.all(r.map(e=>i.menus.get(e.slug).catch(()=>null))),p={};for(const e of u)e&&e.slug&&(p[e.slug]=m(e.items||[]));const d=(l||[]).map(e=>({slot:e.slot,label:e.label,source:e.source,description:e.description,maxDepth:e.maxDepth,menu:c[e.slot]||""}));T.create("#ml-table",{data:d,emptyMessage:"No menu slots registered.",columns:[{key:"slot",title:"Slot",render:e=>`<code>${a(e)}</code>`},{key:"label",title:"Label",render:e=>a(e)},{key:"source",title:"Source",render:e=>e?`<span class="badge badge-secondary">${a(e)}</span>`:""},{key:"description",title:"Description",render:e=>e?`<span class="text-muted">${a(e)}</span>`:""},{key:"menu",title:"Mapped menu",render:(e,t)=>{const n=['<option value="">&mdash; none &mdash;</option>'].concat(r.map(o=>{const g=o.slug===t.menu?" selected":"",h=a(o.name||o.slug);return`<option value="${a(o.slug)}"${g}>${h}</option>`}));return`<select class="form-select ml-select" data-slot="${a(t.slot)}">${n.join("")}</select>`}},{key:"_warning",title:"",render:(e,t)=>{if(!t.menu||t.maxDepth==null)return"";const n=p[t.menu]||0;if(n>t.maxDepth){const o=`This slot supports max depth ${t.maxDepth}; menu is depth ${n}`;return`<span class="badge badge-warning" data-tooltip="${a(o)}">&#9888; depth</span>`}return""}}]}),Domma.icons.scan("#ml-table"),document.querySelectorAll("#ml-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),s.find("#ml-save").on("click",async()=>{const e={};s.find(".ml-select").each(function(){const t=$(this).data("slot"),n=$(this).val();t&&n&&(e[t]=n)});try{await i.menuLocations.save(e),E.toast("Locations saved.",{type:"success"})}catch(t){E.toast(`Save failed: ${t.message||t}`,{type:"error"})}})}};
1
+ import{api as i}from"../api.js";function d(s){if(!Array.isArray(s)||s.length===0)return 0;let c=0;for(const l of s){const r=Array.isArray(l.items)&&l.items.length?d(l.items):0;r>c&&(c=r)}return c+1}function a(s){return String(s??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}export const menuLocationsView={templateUrl:"/admin/js/templates/menu-locations.html",async onMount(s){const c=E.loader(s.get(0),{type:"dots"});let l,r,p,m;try{[l,r,p]=await Promise.all([i.menuLocations.registry().catch(()=>[]),i.menuLocations.get().catch(()=>({})),i.menus.list().catch(()=>[])]),m=await Promise.all(p.map(e=>i.menus.get(e.slug).catch(()=>null)))}finally{c.destroy()}const u={};for(const e of m)e&&e.slug&&(u[e.slug]=d(e.items||[]));const g=(l||[]).map(e=>({slot:e.slot,label:e.label,source:e.source,description:e.description,maxDepth:e.maxDepth,menu:r[e.slot]||""}));T.create("#ml-table",{data:g,emptyMessage:"No menu slots registered.",columns:[{key:"slot",title:"Slot",render:e=>`<code>${a(e)}</code>`},{key:"label",title:"Label",render:e=>a(e)},{key:"source",title:"Source",render:e=>e?`<span class="badge badge-secondary">${a(e)}</span>`:""},{key:"description",title:"Description",render:e=>e?`<span class="text-muted">${a(e)}</span>`:""},{key:"menu",title:"Mapped menu",render:(e,t)=>{const n=['<option value="">&mdash; none &mdash;</option>'].concat(p.map(o=>{const h=o.slug===t.menu?" selected":"",y=a(o.name||o.slug);return`<option value="${a(o.slug)}"${h}>${y}</option>`}));return`<select class="form-select ml-select" data-slot="${a(t.slot)}">${n.join("")}</select>`}},{key:"_warning",title:"",render:(e,t)=>{if(!t.menu||t.maxDepth==null)return"";const n=u[t.menu]||0;if(n>t.maxDepth){const o=`This slot supports max depth ${t.maxDepth}; menu is depth ${n}`;return`<span class="badge badge-warning" data-tooltip="${a(o)}">&#9888; depth</span>`}return""}}]}),Domma.icons.scan("#ml-table"),document.querySelectorAll("#ml-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),s.find("#ml-save").on("click",async()=>{const e={};s.find(".ml-select").each(function(){const t=$(this).data("slot"),n=$(this).val();t&&n&&(e[t]=n)});try{await i.menuLocations.save(e),E.toast("Locations saved.",{type:"success"})}catch(t){E.toast(`Save failed: ${t.message||t}`,{type:"error"})}})}};
@@ -1,5 +1,5 @@
1
- import{api as o}from"../api.js";import{filterByProject as g,getProjectFromHash as b}from"../lib/project-context.js";function l(s){return String(s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}export const menusView={templateUrl:"/admin/js/templates/menus.html",async onMount(s){const[i,d]=await Promise.all([o.menus.list().catch(()=>[]),o.menuLocations.get().catch(()=>({}))]),r=b(),p=r?g(i,r):i,a={};for(const[e,t]of Object.entries(d||{}))t&&(a[t]||(a[t]=[]),a[t].push(e));T.create("#menus-table",{data:p,emptyMessage:'No menus yet. Click "New menu" to create one.',columns:[{key:"slug",title:"Slug",sortable:!0,render:e=>`<code>${l(e)}</code>`},{key:"name",title:"Name",sortable:!0,render:(e,t)=>`<a href="#/menus/edit/${encodeURIComponent(t.slug)}" style="font-weight:600;">${l(e||t.slug)}</a>`},{key:"itemsCount",title:"Items",render:(e,t)=>String(t.itemsCount??(Array.isArray(t.items)?t.items.length:0))},{key:"mapped",title:"Mapped",render:(e,t)=>{const n=a[t.slug]||[];return n.length?n.map(c=>`<span class="badge badge-info">${l(c)}</span>`).join(" "):'<span class="text-muted">\u2014</span>'}},{key:"bundled",title:"Bundled",render:(e,t)=>t.meta?.bundled?'<span class="badge badge-secondary">bundled</span>':""},{key:"_actions",title:"Actions",render:(e,t)=>{const n=(a[t.slug]||[]).length>0,c=n?'data-tooltip="Unmap this menu before deleting"':"",m=n?"disabled":"",u=l(t.slug);return`
1
+ import{api as o}from"../api.js";import{filterByProject as b,getProjectFromHash as y}from"../lib/project-context.js";function l(a){return String(a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}export const menusView={templateUrl:"/admin/js/templates/menus.html",async onMount(a){const p=E.loader(a.get(0),{type:"dots"});let c,r;try{[c,r]=await Promise.all([o.menus.list().catch(()=>[]),o.menuLocations.get().catch(()=>({}))])}finally{p.destroy()}const d=y(),m=d?b(c,d):c,s={};for(const[e,t]of Object.entries(r||{}))t&&(s[t]||(s[t]=[]),s[t].push(e));T.create("#menus-table",{data:m,emptyMessage:'No menus yet. Click "New menu" to create one.',columns:[{key:"slug",title:"Slug",sortable:!0,render:e=>`<code>${l(e)}</code>`},{key:"name",title:"Name",sortable:!0,render:(e,t)=>`<a href="#/menus/edit/${encodeURIComponent(t.slug)}" style="font-weight:600;">${l(e||t.slug)}</a>`},{key:"itemsCount",title:"Items",render:(e,t)=>String(t.itemsCount??(Array.isArray(t.items)?t.items.length:0))},{key:"mapped",title:"Mapped",render:(e,t)=>{const n=s[t.slug]||[];return n.length?n.map(i=>`<span class="badge badge-info">${l(i)}</span>`).join(" "):'<span class="text-muted">\u2014</span>'}},{key:"bundled",title:"Bundled",render:(e,t)=>t.meta?.bundled?'<span class="badge badge-secondary">bundled</span>':""},{key:"_actions",title:"Actions",render:(e,t)=>{const n=(s[t.slug]||[]).length>0,i=n?'data-tooltip="Unmap this menu before deleting"':"",g=n?"disabled":"",u=l(t.slug);return`
2
2
  <a class="btn btn-sm btn-ghost" href="#/menus/edit/${encodeURIComponent(t.slug)}" data-tooltip="Edit"><span data-icon="edit"></span></a>
3
3
  <button class="btn btn-sm btn-ghost btn-duplicate" data-slug="${u}" data-tooltip="Duplicate"><span data-icon="copy"></span></button>
4
- <button class="btn btn-sm btn-danger btn-delete" data-slug="${u}" ${m} ${c}><span data-icon="trash"></span></button>
5
- `}}]}),Domma.icons.scan("#menus-table"),document.querySelectorAll("#menus-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),s.off("click",".btn-duplicate").on("click",".btn-duplicate",async function(){const e=$(this).data("slug");try{const t=await o.menus.duplicate(e);E.toast(`Duplicated as "${t.slug}".`,{type:"success"}),location.reload()}catch(t){E.toast(`Duplicate failed: ${t.message||t}`,{type:"error"})}}),s.off("click",".btn-delete").on("click",".btn-delete",async function(){if(this.disabled)return;const e=$(this).data("slug");if(await E.confirm(`Delete menu "${e}"? This cannot be undone.`))try{await o.menus.remove(e),E.toast("Menu deleted.",{type:"success"}),location.reload()}catch(n){E.toast(`Delete failed: ${n.message||n}`,{type:"error"})}})}};
4
+ <button class="btn btn-sm btn-danger btn-delete" data-slug="${u}" ${g} ${i}><span data-icon="trash"></span></button>
5
+ `}}]}),Domma.icons.scan("#menus-table"),document.querySelectorAll("#menus-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),a.off("click",".btn-duplicate").on("click",".btn-duplicate",async function(){const e=$(this).data("slug");try{const t=await o.menus.duplicate(e);E.toast(`Duplicated as "${t.slug}".`,{type:"success"}),location.reload()}catch(t){E.toast(`Duplicate failed: ${t.message||t}`,{type:"error"})}}),a.off("click",".btn-delete").on("click",".btn-delete",async function(){if(this.disabled)return;const e=$(this).data("slug");if(await E.confirm(`Delete menu "${e}"? This cannot be undone.`))try{await o.menus.remove(e),E.toast("Menu deleted.",{type:"success"}),location.reload()}catch(n){E.toast(`Delete failed: ${n.message||n}`,{type:"error"})}})}};
@@ -1 +1 @@
1
- import{api as l}from"../api.js";function d(s){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"}[s]||"string"}export const myProfileView={templateUrl:"/admin/js/templates/my-profile.html",async onMount(s){const[r,p]=await Promise.all([l.auth.me().catch(()=>null),l.collections.get("user-profiles").catch(()=>null)]);if(!r){E.toast("Could not load your account details.",{type:"error"});return}const m={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"}},password:{type:"password",required:!1,minLength:0,label:"New Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:"Leave blank to keep your current password"}}},u={name:r.name||"",email:r.email||""};F.render("#account-form-container",m,u,{layout:"stacked",submitText:"Save Account",onSubmit:async t=>{if(t.password&&t.password.length<8)return E.toast("Password must be at least 8 characters.",{type:"warning"}),!1;const a={name:t.name,email:t.email};t.password&&(a.password=t.password);try{await l.auth.updateMe(a),E.toast("Account updated.",{type:"success"})}catch(e){return E.toast(`Save failed: ${e.message||"Unknown error"}`,{type:"error"}),!1}}});const i=p?.fields||[];if(i.length>0){s.find("#profile-card").show();const t={},a={};for(const e of i){const o={type:d(e.type),label:e.label||e.name};e.required&&(o.required=!0),e.placeholder&&(o.formConfig={placeholder:e.placeholder}),e.options&&Array.isArray(e.options)&&(o.options=e.options.map(n=>typeof n=="string"?{value:n,label:n}:n)),t[e.name]=o,a[e.name]=r.profile?.[e.name]??""}F.render("#profile-form-container",t,a,{layout:"stacked",submitText:"Save Profile",onSubmit:async e=>{try{await l.auth.updateMe({profile:e}),E.toast("Profile saved.",{type:"success"})}catch(c){return E.toast(`Save failed: ${c.message||"Unknown error"}`,{type:"error"}),!1}}})}Domma.icons.scan()}};
1
+ import{api as s}from"../api.js";function f(o){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"}[o]||"string"}export const myProfileView={templateUrl:"/admin/js/templates/my-profile.html",async onMount(o){const i=E.loader(o.get(0),{type:"dots"});let r,c;try{[r,c]=await Promise.all([s.auth.me().catch(()=>null),s.collections.get("user-profiles").catch(()=>null)])}finally{i.destroy()}if(!r){E.toast("Could not load your account details.",{type:"error"});return}const u={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"}},password:{type:"password",required:!1,minLength:0,label:"New Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:"Leave blank to keep your current password"}}},d={name:r.name||"",email:r.email||""};F.render("#account-form-container",u,d,{layout:"stacked",submitText:"Save Account",onSubmit:async t=>{if(t.password&&t.password.length<8)return E.toast("Password must be at least 8 characters.",{type:"warning"}),!1;const a={name:t.name,email:t.email};t.password&&(a.password=t.password);try{await s.auth.updateMe(a),E.toast("Account updated.",{type:"success"})}catch(e){return E.toast(`Save failed: ${e.message||"Unknown error"}`,{type:"error"}),!1}}});const p=c?.fields||[];if(p.length>0){o.find("#profile-card").show();const t={},a={};for(const e of p){const l={type:f(e.type),label:e.label||e.name};e.required&&(l.required=!0),e.placeholder&&(l.formConfig={placeholder:e.placeholder}),e.options&&Array.isArray(e.options)&&(l.options=e.options.map(n=>typeof n=="string"?{value:n,label:n}:n)),t[e.name]=l,a[e.name]=r.profile?.[e.name]??""}F.render("#profile-form-container",t,a,{layout:"stacked",submitText:"Save Profile",onSubmit:async e=>{try{await s.auth.updateMe({profile:e}),E.toast("Profile saved.",{type:"success"})}catch(m){return E.toast(`Save failed: ${m.message||"Unknown error"}`,{type:"error"}),!1}}})}Domma.icons.scan()}};
@@ -1 +1 @@
1
- import{api as p}from"../api.js";export const notificationsView={templateUrl:"/admin/js/templates/notifications.html",async onMount(r){const f=r.find("#notif-list"),C=r.find("#notif-empty");let o=[];try{o=await p.system.notifications.list()}catch{E.toast("Could not load notifications.",{type:"error"});return}if(!Array.isArray(o)||o.length===0){C.removeAttr("hidden"),Domma.icons.scan(r.get(0));return}o.forEach(e=>{e.unread&&p.system.notifications.markRead(e.id).catch(()=>{})}),o.forEach(e=>{const t=e.data||{},s=t.severity||"info",l=t.createdAt?new Date(t.createdAt).toLocaleString():"",c=s==="critical"?"danger":s==="warning"?"warning":s==="success"?"success":"info",n=document.createElement("div");n.className="card mb-3 notif-card",n.dataset.id=String(e.id);const i=document.createElement("div");i.className="card-header",i.style.cssText="display:flex;align-items:center;gap:.5rem;";const y=document.createElement("span");y.className=`badge badge-${c}`,y.textContent=s.toUpperCase();const g=document.createElement("strong");g.style.flex="1",g.textContent=t.title||"";const h=document.createElement("small");h.className="text-muted",h.textContent=l,i.appendChild(y),i.appendChild(g),i.appendChild(h);const d=document.createElement("div");d.className="card-body";const u=document.createElement("p");if(u.style.cssText="white-space:pre-wrap;margin:0 0 .75rem",u.textContent=t.body||"",d.appendChild(u),t.link){const a=document.createElement("a");a.href=t.link,a.target="_blank",a.rel="noopener",a.className="btn btn-sm btn-secondary",a.textContent="Open Link ";const x=document.createElement("span");x.dataset.icon="external-link",a.appendChild(x),d.appendChild(a)}const b=document.createElement("div");b.style.cssText="margin-top:.5rem;display:flex;gap:.5rem;";const m=document.createElement("button");m.className="btn btn-sm btn-secondary js-dismiss",m.dataset.id=String(e.id),m.textContent="Dismiss",b.appendChild(m),d.appendChild(b),n.appendChild(i),n.appendChild(d),f.get(0).appendChild(n)}),f.get(0).addEventListener("click",async e=>{const t=e.target.closest(".js-dismiss");if(!t)return;const s=t.dataset.id;try{await p.system.notifications.dismiss(s);const l=t.closest(".notif-card");l&&l.remove(),f.get(0).querySelectorAll(".notif-card").length===0&&C.removeAttr("hidden");const{count:c}=await p.system.notifications.unreadCount().catch(()=>({count:0})),n=$("#topbar-bell-badge");c>0?n.text(c>99?"99+":String(c)).removeAttr("hidden"):n.attr("hidden","").text("")}catch{E.toast("Could not dismiss notification.",{type:"error"})}}),Domma.icons.scan(r.get(0))}};
1
+ import{api as p}from"../api.js";export const notificationsView={templateUrl:"/admin/js/templates/notifications.html",async onMount(o){const f=o.find("#notif-list"),C=o.find("#notif-empty"),v=E.loader(o.get(0),{type:"dots"});let c=[];try{c=await p.system.notifications.list()}catch{E.toast("Could not load notifications.",{type:"error"});return}finally{v.destroy()}if(!Array.isArray(c)||c.length===0){C.removeAttr("hidden"),Domma.icons.scan(o.get(0));return}c.forEach(e=>{e.unread&&p.system.notifications.markRead(e.id).catch(()=>{})}),c.forEach(e=>{const t=e.data||{},s=t.severity||"info",l=t.createdAt?new Date(t.createdAt).toLocaleString():"",d=s==="critical"?"danger":s==="warning"?"warning":s==="success"?"success":"info",n=document.createElement("div");n.className="card mb-3 notif-card",n.dataset.id=String(e.id);const i=document.createElement("div");i.className="card-header",i.style.cssText="display:flex;align-items:center;gap:.5rem;";const y=document.createElement("span");y.className=`badge badge-${d}`,y.textContent=s.toUpperCase();const g=document.createElement("strong");g.style.flex="1",g.textContent=t.title||"";const h=document.createElement("small");h.className="text-muted",h.textContent=l,i.appendChild(y),i.appendChild(g),i.appendChild(h);const r=document.createElement("div");r.className="card-body";const u=document.createElement("p");if(u.style.cssText="white-space:pre-wrap;margin:0 0 .75rem",u.textContent=t.body||"",r.appendChild(u),t.link){const a=document.createElement("a");a.href=t.link,a.target="_blank",a.rel="noopener",a.className="btn btn-sm btn-secondary",a.textContent="Open Link ";const x=document.createElement("span");x.dataset.icon="external-link",a.appendChild(x),r.appendChild(a)}const b=document.createElement("div");b.style.cssText="margin-top:.5rem;display:flex;gap:.5rem;";const m=document.createElement("button");m.className="btn btn-sm btn-secondary js-dismiss",m.dataset.id=String(e.id),m.textContent="Dismiss",b.appendChild(m),r.appendChild(b),n.appendChild(i),n.appendChild(r),f.get(0).appendChild(n)}),f.get(0).addEventListener("click",async e=>{const t=e.target.closest(".js-dismiss");if(!t)return;const s=t.dataset.id;try{await p.system.notifications.dismiss(s);const l=t.closest(".notif-card");l&&l.remove(),f.get(0).querySelectorAll(".notif-card").length===0&&C.removeAttr("hidden");const{count:d}=await p.system.notifications.unreadCount().catch(()=>({count:0})),n=$("#topbar-bell-badge");d>0?n.text(d>99?"99+":String(d)).removeAttr("hidden"):n.attr("hidden","").text("")}catch{E.toast("Could not dismiss notification.",{type:"error"})}}),Domma.icons.scan(o.get(0))}};