domma-cms 0.70.1 → 0.71.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/CLAUDE.md +24 -3
- package/admin/css/admin.css +1 -1
- package/admin/js/app.js +3 -3
- package/admin/js/lib/admin-scope.js +1 -0
- package/admin/js/lib/sidebar-renderer.js +4 -4
- package/admin/js/templates/page-editor.html +1 -4
- package/admin/js/templates/role-editor.html +28 -0
- package/admin/js/views/menu-editor.js +5 -5
- package/admin/js/views/page-editor.js +26 -26
- package/admin/js/views/role-editor.js +3 -1
- package/package.json +1 -1
- package/plugins/_template/plugin.js +5 -2
- package/plugins/blog/CLAUDE.md +20 -5
- package/plugins/blog/admin/views/kit.js +8 -10
- package/plugins/blog/lib/layouts.js +36 -0
- package/plugins/blog/lib/nav-link.js +22 -0
- package/plugins/blog/lib/page.js +13 -1
- package/plugins/blog/plugin.js +28 -9
- package/plugins/blog/plugin.json +6 -6
- package/plugins/blog/plugin.public.js +102 -80
- package/plugins/blog/tests/layouts.test.js +29 -0
- package/plugins/blog/tests/nav-link.test.js +10 -1
- package/plugins/blog/tests/public.test.js +21 -0
- package/plugins/free-tier.lock.json +13 -13
- package/server/middleware/auth.js +21 -0
- package/server/routes/api/auth.js +15 -7
- package/server/routes/api/collections.js +40 -3
- package/server/routes/api/sidebar.js +26 -10
- package/server/routes/public.js +13 -17
- package/server/services/menus.js +1 -1
- package/server/services/permissionRegistry.js +10 -7
- package/server/services/pluginInstaller.js +23 -9
- package/server/services/plugins.js +245 -40
- package/server/services/roles.js +170 -5
|
@@ -60,6 +60,34 @@
|
|
|
60
60
|
</div>
|
|
61
61
|
</div>
|
|
62
62
|
</div>
|
|
63
|
+
<div class="card mt-3" id="role-admin-scope-card">
|
|
64
|
+
<div class="card-body">
|
|
65
|
+
<h3 class="card-title"><span data-icon="lock"></span> Admin area</h3>
|
|
66
|
+
<p class="form-hint">Leave both blank to let this role use every admin screen its permissions
|
|
67
|
+
allow. Fill in the allowed screens to confine it - customers who should only ever see their
|
|
68
|
+
own portal, say. A user is confined only if every role they hold is; My Profile is always
|
|
69
|
+
open.</p>
|
|
70
|
+
<div class="row">
|
|
71
|
+
<div class="col-6">
|
|
72
|
+
<div class="mb-3">
|
|
73
|
+
<label class="form-label">Allowed screens</label>
|
|
74
|
+
<textarea id="role-admin-allow" class="form-input" rows="4"
|
|
75
|
+
placeholder="/portal"></textarea>
|
|
76
|
+
<p class="form-hint">One admin route per line. A route also allows everything under
|
|
77
|
+
it (<code>/portal</code> allows <code>/portal/orders</code>).</p>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="col-6">
|
|
81
|
+
<div class="mb-3">
|
|
82
|
+
<label class="form-label">Home screen</label>
|
|
83
|
+
<input id="role-admin-home" type="text" class="form-input" placeholder="/portal">
|
|
84
|
+
<p class="form-hint">Where the user lands, and where they are sent from anywhere
|
|
85
|
+
else. Defaults to the first allowed screen.</p>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
63
91
|
</div>
|
|
64
92
|
|
|
65
93
|
<!-- Permissions Tab -->
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as k}from"../api.js";import{colourToCss as xt,HIGHLIGHT_HOVER as rt,HIGHLIGHT_ACTIVE as pt,HIGHLIGHT_HOVER_LABELS as mt,HIGHLIGHT_ACTIVE_LABELS as ft}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{openIconPicker as At}from"../lib/card-builder.js";import{applyDrop as _t}from"../lib/menu-tree.js";let kt=1;const G=()=>"i_"+kt++,It=760;function ut(t,
|
|
1
|
+
import{api as k}from"../api.js";import{colourToCss as xt,HIGHLIGHT_HOVER as rt,HIGHLIGHT_ACTIVE as pt,HIGHLIGHT_HOVER_LABELS as mt,HIGHLIGHT_ACTIVE_LABELS as ft}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{openIconPicker as At}from"../lib/card-builder.js";import{applyDrop as _t}from"../lib/menu-tree.js";let kt=1;const G=()=>"i_"+kt++,It=760;function ut(t,s=null,l=[]){for(const i of t||[]){const n=G();if(i&&i.type==="separator"){l.push({id:n,parentId:s,type:"separator"});continue}if(i&&i.type==="spacer"){l.push({id:n,parentId:s,type:"spacer",size:i.size||"md"});continue}l.push({id:n,parentId:s,text:i.text||"",url:i.url||"",icon:i.icon||"",visibility:i.visibility||"",permission:i.permission||"",hidden:!!i.hidden,bundled:!!i.bundled,badge:i.badge?{...i.badge}:null,pill:i.pill?{...i.pill}:null,colour:i.colour||"",highlight:i.highlight?{...i.highlight}:null}),Array.isArray(i.items)&&i.items.length&&ut(i.items,n,l)}return l}function Ht(t){const s=new Map;for(const i of t)s.has(i.parentId)||s.set(i.parentId,[]),s.get(i.parentId).push(i);function l(i){return(s.get(i)||[]).map(n=>{if(n.type==="separator")return{type:"separator"};if(n.type==="spacer")return{type:"spacer",...n.size&&n.size!=="md"&&{size:n.size}};const o=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},...n.highlight&&(n.highlight.hover||n.highlight.active||n.highlight.accent)?{highlight:{...n.highlight.hover&&{hover:n.highlight.hover},...n.highlight.active&&{active:n.highlight.active},...n.highlight.accent&&{accent:n.highlight.accent}}}:{},...o.length&&{items:o}}})}return l(null)}function h(t){return String(t||"").replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<")}function St(t,s){let l=0,i=s.find(n=>n.id===t);for(;i&&i.parentId;)l++,i=s.find(n=>n.id===i.parentId);return l}function ht(t,s){const{top:l,height:i}=t.getBoundingClientRect(),n=i?(s-l)/i:.5;return n<.25?"above":n>.75?"below":"into"}const gt=["me-drop-above","me-drop-below","me-drop-into"];function st(t){t.get(0).querySelectorAll(".menu-tree-row").forEach(s=>s.classList.remove(...gt))}function Ot(t,s,l){let i=null;t.get(0).querySelectorAll(".menu-tree-row").forEach(n=>{const o=n.querySelector(".menu-tree-grip");n.draggable=!1,o&&(o.addEventListener("mousedown",()=>{n.draggable=!0}),o.addEventListener("mouseup",()=>{n.draggable=!1})),n.addEventListener("dragstart",p=>{i=n.dataset.id,n.style.opacity="0.45",p.dataTransfer.effectAllowed="move";try{p.dataTransfer.setData("text/plain",i)}catch{}}),n.addEventListener("dragend",()=>{n.style.opacity="",n.draggable=!1,i=null,st(t)}),n.addEventListener("dragover",p=>{if(!i||i===n.dataset.id)return;p.preventDefault(),p.dataTransfer.dropEffect="move";const w=ht(n,p.clientY);st(t),n.classList.add(`me-drop-${w}`)}),n.addEventListener("dragleave",()=>n.classList.remove(...gt)),n.addEventListener("drop",p=>{p.preventDefault();const w=n.dataset.id,A=ht(n,p.clientY);if(st(t),!i)return;const y=_t(s(),i,w,A);i=null,y&&l()})})}function jt(t,s,l=()=>""){t.html("");function i(n){for(const o of s.filter(p=>p.parentId===n)){const p=St(o.id,s);if(o.type==="separator"){t.append(`
|
|
2
2
|
<div class="menu-tree-row menu-tree-row--separator" data-id="${o.id}" style="margin-left:${p*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>
|
|
@@ -10,12 +10,12 @@ import{api as k}from"../api.js";import{colourToCss as xt,HIGHLIGHT_HOVER as rt,H
|
|
|
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-ghost me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
12
12
|
</div>
|
|
13
|
-
`);continue}if(o.type==="spacer"){const
|
|
13
|
+
`);continue}if(o.type==="spacer"){const R=["xs","sm","md","lg","xl","flex"].map(L=>`<option value="${L}"${(o.size||"md")===L?" selected":""}>${L==="flex"?"flexible":L}</option>`).join("");t.append(`
|
|
14
14
|
<div class="menu-tree-row menu-tree-row--spacer" data-id="${o.id}" style="margin-left:${p*28}px">
|
|
15
15
|
<span class="menu-tree-grip" data-icon="move"></span>
|
|
16
16
|
<span class="me-spacer-line">
|
|
17
17
|
<span class="me-spacer-label">Spacer</span>
|
|
18
|
-
<select class="form-input me-spacer-size" data-tooltip="Spacer size - flexible fills the leftover room">${
|
|
18
|
+
<select class="form-input me-spacer-size" data-tooltip="Spacer size - flexible fills the leftover room">${R}</select>
|
|
19
19
|
</span>
|
|
20
20
|
<button class="btn btn-sm btn-ghost me-ins-item" data-tooltip="Add item below"><span data-icon="plus"></span></button>
|
|
21
21
|
<button class="btn btn-sm btn-ghost me-ins-sep" data-tooltip="Add separator below"><span data-icon="minus"></span></button>
|
|
@@ -40,8 +40,8 @@ import{api as k}from"../api.js";import{colourToCss as xt,HIGHLIGHT_HOVER as rt,H
|
|
|
40
40
|
<button class="btn btn-sm btn-secondary me-edit" data-tooltip="Edit"><span data-icon="edit"></span></button>
|
|
41
41
|
<button class="btn btn-sm btn-ghost me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
42
42
|
</div>
|
|
43
|
-
`),i(o.id)}}i(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 j(t,d){t.find(".menu-tree-row").each(function(){const s=$(this).data("id"),i=d.find(y=>y.id===s);if(!i||i.type==="separator")return;if(i.type==="spacer"){const y=$(this).find(".me-spacer-size");y.length&&(i.size=y.val());return}const n=$(this).find(".me-text");n.length&&(i.text=n.val());const o=$(this).find(".me-url");o.length&&(i.url=o.val());const p=$(this).find(".me-icon");p.length&&(i.icon=p.val());const w=$(this).find(".me-vis");w.length&&(i.visibility=w.val());const A=$(this).find(".me-perm");A.length&&(i.permission=A.val())})}function Nt(t,d){const s={};for(const i of d){const n=t.querySelector(`[name="${i}"]`);n&&(s[i]=n.type==="checkbox"?n.checked:n.value)}return s}function b(t,d={},...s){const i=document.createElement(t);Object.assign(i,d);for(const n of s)n!=null&&i.append(n);return i}function I(t,d,s){const i=b("label",{className:"form-field"},b("span",{className:"form-label",textContent:t}),d);return s&&i.append(b("small",{className:"form-hint",textContent:s,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),i}function Lt(t,d){const s=[["","- Everyone -"],["public","Public - anyone, incl. logged-out"],...(d||[]).map(n=>[n.name,`${n.label||n.name} and above`]),["private","Private - super-admin only"]];t&&!s.some(([n])=>n===t)&&s.push([t,`${t} (custom)`]);const i=P(s,t||"");return i.name="visibility",i}function P(t,d){const s=b("select",{className:"form-select"});return t.forEach(([i,n])=>s.add(new Option(n,i))),s.value=d??"",s}function it(t,d){const s=typeof d=="string"&&d.startsWith("#"),i=P([["","- none -"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],s?"__custom__":d||""),n=b("input",{type:"color",className:"form-input me-colour-hex",value:s?d:"#000000"});n.style.display=s?"":"none",i.addEventListener("change",()=>{n.style.display=i.value==="__custom__"?"":"none"});const o=I(t,i);return o.append(n),o._read=()=>i.value==="__custom__"?n.value:i.value,o}function zt(t,d){const s=b("input",{className:"form-input me-icon",name:"icon",value:d||""}),i=b("span",{className:"me-icon-preview"});d&&i.setAttribute("data-icon",d);const n=b("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),o=p=>{p?i.setAttribute("data-icon",p):i.removeAttribute("data-icon"),Domma.icons.scan(i.parentNode||i)};return n.addEventListener("click",()=>At(p=>{s.value=p,o(p)})),s.addEventListener("input",()=>o(s.value.trim())),I(t,b("div",{className:"me-icon-field"},s,n,i))}function ot(...t){return b("div",{className:"me-field-grid"},...t)}async function Ct(t,d,s,i,n){const o=m=>b("h4",{className:"me-form-section",textContent:m}),p=I("Label",b("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),w=I("URL",b("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."),A=zt("Icon",t.icon||""),y=I("Visibility",Lt(t.visibility||"",i),"Who can see this item, by role seniority. Picking a role shows it to that role and anyone more senior. Blank = everyone."),N=P([["","- no permission gate -"],...(d||[]).map(m=>[m.key,m.label||m.key])],t.permission||"");N.name="permission";const L=I("Permission",N,"Hide unless the viewer holds this specific permission. Independent of Visibility - both must pass."),lt=I("Badge text",b("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),J=P([["","- none -"],...(s||[]).map(m=>[m.slug,m.title||m.slug])],t.badge?.countFrom||"");J.name="badgeCount";const at=I("Badge count from",J,"Show a live count of this collection\u2019s entries as the badge."),Q=it("Badge colour",t.badge?.variant||""),l=P([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");l.name="renderAs";const x=I("Render as",l,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),v=I("Pill style",P([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));v.querySelector("select").name="pillStyle";const Y=it("Pill colour",t.pill?.variant||""),W=b("div",{className:"me-pill-wrap"},ot(v,Y));W.style.display=t.pill?"":"none",l.addEventListener("change",()=>{W.style.display=l.value==="pill"?"":"none"});const D=it("Text colour",t.colour||""),X=P([["","- use the menu default -"],...rt.map(m=>[m,mt[m]||m])],t.highlight?.hover||"");X.name="hlHover";const R=I("Hover effect",X,"Overrides the menu-wide hover effect for this item only."),M=P([["","- use the menu default -"],...pt.map(m=>[m,ft[m]||m])],t.highlight?.active||"");M.name="hlActive";const U=I("When selected",M,"How this item is marked when it points at the page being viewed."),tt=it("Highlight colour",t.highlight?.accent||""),B=b("button",{className:"btn btn-primary",textContent:"Apply"}),V=b("div",{className:"me-item-form"},o("Link"),p,w,A,o("Access"),ot(y,L),o("Badge"),ot(lt,Q),at,o("Appearance"),x,W,D,o("Highlighting"),ot(R,U),tt,B),S=E.slideover({title:"Edit menu item",size:"lg",position:"right"});S.element.appendChild(V),S.open();const K=S.element.closest(".dm-slideover")||S.element.querySelector(".dm-slideover")||S.element;K.style.setProperty("width",It+"px","important"),K.style.setProperty("max-width","95vw","important"),Domma.icons.scan(V);const g=m=>V.querySelector(`[name="${m}"]`)?.value||"";B.addEventListener("click",()=>{t.text=g("text"),t.url=g("url"),t.icon=g("icon"),t.visibility=g("visibility"),t.permission=g("permission");const m=g("badgeText"),q=g("badgeCount"),et=Q._read();if(t.badge=m||q?{...m&&{text:m},...q&&{countFrom:q},...et&&{variant:et}}:null,g("renderAs")==="pill"){const nt=Y._read();t.pill={style:g("pillStyle")||"filled",...nt&&{variant:nt}}}else t.pill=null;t.colour=D._read();const F=g("hlHover"),T=g("hlActive"),z=tt._read();t.highlight=F||T||z?{...F&&{hover:F},...T&&{active:T},...z&&{accent:z}}:null,S.close(),n()})}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const d=location.hash.match(/^#\/menus\/edit\/(.+)$/),s=d?decodeURIComponent(d[1]):null,i=!s,n=E.loader(t.get(0),{type:"dots"});let o,p,w,A,y,N,L;try{if(i)o={slug:"",name:"",description:"",items:[]};else try{o=await k.menus.get(s)}catch(e){E.toast(`Failed to load menu: ${e.message||e}`,{type:"error"}),location.hash="#/menus";return}p=await k.projects.list().catch(()=>[]),w=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),A=await k.collections.list().catch(()=>[]),y=await k.get("/collections/roles/entries?limit=100").catch(()=>null),N=await k.menuLocations.registry().catch(()=>[]),L=await k.pages.list().catch(()=>[])}finally{n.destroy()}const J=(Array.isArray(y)?y:y?.entries||[]).map(e=>({name:e.data?.name,label:e.data?.label,level:e.data?.level??99})).filter(e=>e.name).sort((e,a)=>a.level-e.level),at='<option value="">- no permission gate -</option>',Q=e=>at+(w.resources||[]).map(a=>{const c=(e||"")===a.key?" selected":"";return`<option value="${a.key}"${c}>${a.label||a.key}</option>`}).join("");t.find("#me-title").text(i?"New menu":`Edit "${o.slug}"`);let l=ut(o.items);const x=t.find("#me-items-list");function v(){jt(x,l,Q),Ot(x,()=>l,v)}v(),t.find("#me-add-item").on("click",()=>{j(x,l),l.push({id:G(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),v()}),t.find("#me-add-separator").on("click",()=>{j(x,l),l.push({id:G(),parentId:null,type:"separator"}),v()}),t.find("#me-add-spacer").on("click",()=>{j(x,l),l.push({id:G(),parentId:null,type:"spacer",size:"md"}),v()});function Y(e){return function(){j(x,l);const a=$(this).closest(".menu-tree-row").data("id"),c=l.findIndex(_=>_.id===a);if(c===-1)return;const u=l[c],r={separator:{type:"separator"},spacer:{type:"spacer",size:"md"}}[e],f=r?{id:G(),parentId:u.parentId,...r}:{id:G(),parentId:u.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};l.splice(c+1,0,f),v()}}t.off("click",".me-ins-item").on("click",".me-ins-item",Y("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",Y("separator")),t.off("click",".me-remove").on("click",".me-remove",function(){const e=$(this).closest(".menu-tree-row").data("id");l=l.filter(a=>a.id!==e&&a.parentId!==e),v()}),t.off("click",".me-indent").on("click",".me-indent",function(){j(x,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.find(r=>r.id===e);if(!a)return;const c=l.filter(r=>r.parentId===a.parentId),u=c.findIndex(r=>r.id===e);u<=0||["separator","spacer"].includes(c[u-1].type)||(a.parentId=c[u-1].id,v())}),t.off("click",".me-outdent").on("click",".me-outdent",function(){j(x,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.find(u=>u.id===e);if(!a||a.parentId==null)return;const c=l.find(u=>u.id===a.parentId);a.parentId=c?c.parentId:null,v()}),t.off("click",".me-up").on("click",".me-up",function(){j(x,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.filter(f=>f.parentId===l.find(_=>_.id===e)?.parentId),c=a.findIndex(f=>f.id===e);if(c<=0)return;const u=l.indexOf(a[c]),r=l.indexOf(a[c-1]);[l[u],l[r]]=[l[r],l[u]],v()}),t.off("click",".me-down").on("click",".me-down",function(){j(x,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.filter(f=>f.parentId===l.find(_=>_.id===e)?.parentId),c=a.findIndex(f=>f.id===e);if(c===-1||c>=a.length-1)return;const u=l.indexOf(a[c]),r=l.indexOf(a[c+1]);[l[u],l[r]]=[l[r],l[u]],v()}),t.off("click",".me-hidden").on("click",".me-hidden",function(){j(x,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.find(c=>c.id===e);a&&(a.hidden=!a.hidden),v()}),t.off("click",".me-edit").on("click",".me-edit",function(){const e=$(this).closest(".menu-tree-row").data("id"),a=l.find(c=>c.id===e);!a||["separator","spacer"].includes(a.type)||Ct(a,w.resources||[],A,J,()=>v())}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(o.variant||""),t.find("#me-position").val(o.position||"");const W=t.find("#me-appearOnHover").get(0);W&&(W.checked=o.appearOnHover===!0),t.find("#me-float-anchor").val(o.float?.anchor||"TL"),t.find("#me-float-offsetX").val(o.float?.offsetX??""),t.find("#me-float-offsetY").val(o.float?.offsetY??""),t.find("#me-float-width").val(o.float?.width??""),t.find("#me-float-radius").val(o.float?.radius??""),t.find("#me-float-shadow").val(o.float?.shadow||""),t.find("#me-float-opacity").val(o.float?.opacity??""),t.find("#me-float-title").val(o.float?.title||"");for(const[e,a]of[["#me-float-collapsible",o.float?.collapsible],["#me-float-collapsed",o.float?.collapsed],["#me-float-movable",o.float?.movable],["#me-float-resizable",o.float?.resizable]]){const c=t.find(e).get(0);c&&(c.checked=a===!0)}const D=o.float?.accent||"",X=D.startsWith("#"),R=t.find("#me-float-accent"),M=t.find("#me-float-accent-hex");R.val(X?"__custom__":D),X&&M.val(D);const U=()=>{M.get(0).style.display=R.val()==="__custom__"?"":"none"};U(),R.on("change",U);const tt=()=>R.val()==="__custom__"?M.val():R.val(),B=()=>{const e=t.find("#me-position").val()==="floating";t.find(".dm-float-field").each(function(){this.style.display=e?"":"none"}),e&&U();const a=t.find("#me-orientation").val()==="vertical";t.find(".dm-vertical-field").each(function(){this.style.display=a?"":"none"});const c=!!(t.find("#me-float-collapsible").get(0)||{}).checked;t.find(".dm-float-collapsed-field").each(function(){this.style.display=e&&c?"":"none"})};t.find("#me-orientation").val(o.orientation||""),t.find("#me-align").val(o.align||""),t.find("#me-side").val(o.side||"left"),B(),t.find("#me-position").on("change",B),t.find("#me-orientation").on("change",B),t.find("#me-float-collapsible").on("change",B);const V=(e,a,c,u)=>{const r=t.find(e).get(0);r&&(r.innerHTML="",r.add(new Option(u,"")),a.forEach(f=>r.add(new Option(c[f]||f,f))))};V("#me-highlight-hover",rt,mt,"- theme default -"),V("#me-highlight-active",pt,ft,"- theme default -"),t.find("#me-highlight-hover").val(o.highlight?.hover||""),t.find("#me-highlight-active").val(o.highlight?.active||"");const S=o.highlight?.accent||"",K=S.startsWith("#"),g=t.find("#me-highlight-accent"),m=t.find("#me-highlight-accent-hex");g.val(K?"__custom__":S),K&&m.val(S);const q=()=>{m.get(0).style.display=g.val()==="__custom__"?"":"none"};q(),g.on("change",q);const et=()=>g.val()==="__custom__"?m.val():g.val();t.find("#me-fontFamily").val(o.style?.fontFamily||""),t.find("#me-fontSize").val(o.style?.fontSize||""),t.find("#me-fontWeight").val(o.style?.fontWeight||""),t.find("#me-letterSpacing").val(o.style?.letterSpacing||""),t.find("#me-iconSize").val(o.style?.iconSize||"");const F=t.find("#me-bind-slot"),T=t.find("#me-bind-projects"),z=t.find("#me-bind-pages");F.html('<option value="">- not bound -</option>'+(N||[]).map(e=>`<option value="${h(e.slot)}">${h(e.label||e.slot)} (${h(e.slot)})</option>`).join("")),T.html(p.map(e=>`<option value="${h(e.slug)}">${h(e.name||e.slug)}</option>`).join("")),F.val(o.binding?.slot||"");const nt=new Set(o.binding?.projects||[]);Array.from(T.get(0).options).forEach(e=>{e.selected=nt.has(e.value)}),z.val((o.binding?.pages||[]).join(`
|
|
43
|
+
`),i(o.id)}}i(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 j(t,s){t.find(".menu-tree-row").each(function(){const l=$(this).data("id"),i=s.find(y=>y.id===l);if(!i||i.type==="separator")return;if(i.type==="spacer"){const y=$(this).find(".me-spacer-size");y.length&&(i.size=y.val());return}const n=$(this).find(".me-text");n.length&&(i.text=n.val());const o=$(this).find(".me-url");o.length&&(i.url=o.val());const p=$(this).find(".me-icon");p.length&&(i.icon=p.val());const w=$(this).find(".me-vis");w.length&&(i.visibility=w.val());const A=$(this).find(".me-perm");A.length&&(i.permission=A.val())})}function Rt(t,s){const l={};for(const i of s){const n=t.querySelector(`[name="${i}"]`);n&&(l[i]=n.type==="checkbox"?n.checked:n.value)}return l}function b(t,s={},...l){const i=document.createElement(t);Object.assign(i,s);for(const n of l)n!=null&&i.append(n);return i}function I(t,s,l){const i=b("label",{className:"form-field"},b("span",{className:"form-label",textContent:t}),s);return l&&i.append(b("small",{className:"form-hint",textContent:l,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),i}function Lt(t,s){const l=[["","- Everyone -"],["public","Public - anyone, incl. logged-out"],...(s||[]).map(n=>[n.name,`${n.label||n.name} and above`]),...(s||[]).filter(n=>n.level!==0).map(n=>[`=${n.name}`,`${n.label||n.name} only`]),["private","Private - super-admin only"]];t&&!l.some(([n])=>n===t)&&l.push([t,`${t} (custom)`]);const i=P(l,t||"");return i.name="visibility",i}function P(t,s){const l=b("select",{className:"form-select"});return t.forEach(([i,n])=>l.add(new Option(n,i))),l.value=s??"",l}function it(t,s){const l=typeof s=="string"&&s.startsWith("#"),i=P([["","- none -"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],l?"__custom__":s||""),n=b("input",{type:"color",className:"form-input me-colour-hex",value:l?s:"#000000"});n.style.display=l?"":"none",i.addEventListener("change",()=>{n.style.display=i.value==="__custom__"?"":"none"});const o=I(t,i);return o.append(n),o._read=()=>i.value==="__custom__"?n.value:i.value,o}function zt(t,s){const l=b("input",{className:"form-input me-icon",name:"icon",value:s||""}),i=b("span",{className:"me-icon-preview"});s&&i.setAttribute("data-icon",s);const n=b("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),o=p=>{p?i.setAttribute("data-icon",p):i.removeAttribute("data-icon"),Domma.icons.scan(i.parentNode||i)};return n.addEventListener("click",()=>At(p=>{l.value=p,o(p)})),l.addEventListener("input",()=>o(l.value.trim())),I(t,b("div",{className:"me-icon-field"},l,n,i))}function ot(...t){return b("div",{className:"me-field-grid"},...t)}async function Ct(t,s,l,i,n){const o=m=>b("h4",{className:"me-form-section",textContent:m}),p=I("Label",b("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),w=I("URL",b("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."),A=zt("Icon",t.icon||""),y=I("Visibility",Lt(t.visibility||"",i),'Who can see this item. "Role and above" shows it to that role and anyone more senior; "Role only" shows it to holders of that role alone (and the super-admin). Blank = everyone.'),R=P([["","- no permission gate -"],...(s||[]).map(m=>[m.key,m.label||m.key])],t.permission||"");R.name="permission";const L=I("Permission",R,"Hide unless the viewer holds this specific permission. Independent of Visibility - both must pass."),lt=I("Badge text",b("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),J=P([["","- none -"],...(l||[]).map(m=>[m.slug,m.title||m.slug])],t.badge?.countFrom||"");J.name="badgeCount";const at=I("Badge count from",J,"Show a live count of this collection\u2019s entries as the badge."),Q=it("Badge colour",t.badge?.variant||""),d=P([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");d.name="renderAs";const x=I("Render as",d,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),v=I("Pill style",P([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));v.querySelector("select").name="pillStyle";const Y=it("Pill colour",t.pill?.variant||""),W=b("div",{className:"me-pill-wrap"},ot(v,Y));W.style.display=t.pill?"":"none",d.addEventListener("change",()=>{W.style.display=d.value==="pill"?"":"none"});const D=it("Text colour",t.colour||""),X=P([["","- use the menu default -"],...rt.map(m=>[m,mt[m]||m])],t.highlight?.hover||"");X.name="hlHover";const N=I("Hover effect",X,"Overrides the menu-wide hover effect for this item only."),M=P([["","- use the menu default -"],...pt.map(m=>[m,ft[m]||m])],t.highlight?.active||"");M.name="hlActive";const U=I("When selected",M,"How this item is marked when it points at the page being viewed."),tt=it("Highlight colour",t.highlight?.accent||""),B=b("button",{className:"btn btn-primary",textContent:"Apply"}),V=b("div",{className:"me-item-form"},o("Link"),p,w,A,o("Access"),ot(y,L),o("Badge"),ot(lt,Q),at,o("Appearance"),x,W,D,o("Highlighting"),ot(N,U),tt,B),S=E.slideover({title:"Edit menu item",size:"lg",position:"right"});S.element.appendChild(V),S.open();const K=S.element.closest(".dm-slideover")||S.element.querySelector(".dm-slideover")||S.element;K.style.setProperty("width",It+"px","important"),K.style.setProperty("max-width","95vw","important"),Domma.icons.scan(V);const g=m=>V.querySelector(`[name="${m}"]`)?.value||"";B.addEventListener("click",()=>{t.text=g("text"),t.url=g("url"),t.icon=g("icon"),t.visibility=g("visibility"),t.permission=g("permission");const m=g("badgeText"),q=g("badgeCount"),et=Q._read();if(t.badge=m||q?{...m&&{text:m},...q&&{countFrom:q},...et&&{variant:et}}:null,g("renderAs")==="pill"){const nt=Y._read();t.pill={style:g("pillStyle")||"filled",...nt&&{variant:nt}}}else t.pill=null;t.colour=D._read();const F=g("hlHover"),T=g("hlActive"),z=tt._read();t.highlight=F||T||z?{...F&&{hover:F},...T&&{active:T},...z&&{accent:z}}:null,S.close(),n()})}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const s=location.hash.match(/^#\/menus\/edit\/(.+)$/),l=s?decodeURIComponent(s[1]):null,i=!l,n=E.loader(t.get(0),{type:"dots"});let o,p,w,A,y,R,L;try{if(i)o={slug:"",name:"",description:"",items:[]};else try{o=await k.menus.get(l)}catch(e){E.toast(`Failed to load menu: ${e.message||e}`,{type:"error"}),location.hash="#/menus";return}p=await k.projects.list().catch(()=>[]),w=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),A=await k.collections.list().catch(()=>[]),y=await k.get("/collections/roles/entries?limit=100").catch(()=>null),R=await k.menuLocations.registry().catch(()=>[]),L=await k.pages.list().catch(()=>[])}finally{n.destroy()}const J=(Array.isArray(y)?y:y?.entries||[]).map(e=>({name:e.data?.name,label:e.data?.label,level:e.data?.level??99})).filter(e=>e.name).sort((e,a)=>a.level-e.level),at='<option value="">- no permission gate -</option>',Q=e=>at+(w.resources||[]).map(a=>{const c=(e||"")===a.key?" selected":"";return`<option value="${a.key}"${c}>${a.label||a.key}</option>`}).join("");t.find("#me-title").text(i?"New menu":`Edit "${o.slug}"`);let d=ut(o.items);const x=t.find("#me-items-list");function v(){jt(x,d,Q),Ot(x,()=>d,v)}v(),t.find("#me-add-item").on("click",()=>{j(x,d),d.push({id:G(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),v()}),t.find("#me-add-separator").on("click",()=>{j(x,d),d.push({id:G(),parentId:null,type:"separator"}),v()}),t.find("#me-add-spacer").on("click",()=>{j(x,d),d.push({id:G(),parentId:null,type:"spacer",size:"md"}),v()});function Y(e){return function(){j(x,d);const a=$(this).closest(".menu-tree-row").data("id"),c=d.findIndex(_=>_.id===a);if(c===-1)return;const u=d[c],r={separator:{type:"separator"},spacer:{type:"spacer",size:"md"}}[e],f=r?{id:G(),parentId:u.parentId,...r}:{id:G(),parentId:u.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};d.splice(c+1,0,f),v()}}t.off("click",".me-ins-item").on("click",".me-ins-item",Y("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",Y("separator")),t.off("click",".me-remove").on("click",".me-remove",function(){const e=$(this).closest(".menu-tree-row").data("id");d=d.filter(a=>a.id!==e&&a.parentId!==e),v()}),t.off("click",".me-indent").on("click",".me-indent",function(){j(x,d);const e=$(this).closest(".menu-tree-row").data("id"),a=d.find(r=>r.id===e);if(!a)return;const c=d.filter(r=>r.parentId===a.parentId),u=c.findIndex(r=>r.id===e);u<=0||["separator","spacer"].includes(c[u-1].type)||(a.parentId=c[u-1].id,v())}),t.off("click",".me-outdent").on("click",".me-outdent",function(){j(x,d);const e=$(this).closest(".menu-tree-row").data("id"),a=d.find(u=>u.id===e);if(!a||a.parentId==null)return;const c=d.find(u=>u.id===a.parentId);a.parentId=c?c.parentId:null,v()}),t.off("click",".me-up").on("click",".me-up",function(){j(x,d);const e=$(this).closest(".menu-tree-row").data("id"),a=d.filter(f=>f.parentId===d.find(_=>_.id===e)?.parentId),c=a.findIndex(f=>f.id===e);if(c<=0)return;const u=d.indexOf(a[c]),r=d.indexOf(a[c-1]);[d[u],d[r]]=[d[r],d[u]],v()}),t.off("click",".me-down").on("click",".me-down",function(){j(x,d);const e=$(this).closest(".menu-tree-row").data("id"),a=d.filter(f=>f.parentId===d.find(_=>_.id===e)?.parentId),c=a.findIndex(f=>f.id===e);if(c===-1||c>=a.length-1)return;const u=d.indexOf(a[c]),r=d.indexOf(a[c+1]);[d[u],d[r]]=[d[r],d[u]],v()}),t.off("click",".me-hidden").on("click",".me-hidden",function(){j(x,d);const e=$(this).closest(".menu-tree-row").data("id"),a=d.find(c=>c.id===e);a&&(a.hidden=!a.hidden),v()}),t.off("click",".me-edit").on("click",".me-edit",function(){const e=$(this).closest(".menu-tree-row").data("id"),a=d.find(c=>c.id===e);!a||["separator","spacer"].includes(a.type)||Ct(a,w.resources||[],A,J,()=>v())}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(o.variant||""),t.find("#me-position").val(o.position||"");const W=t.find("#me-appearOnHover").get(0);W&&(W.checked=o.appearOnHover===!0),t.find("#me-float-anchor").val(o.float?.anchor||"TL"),t.find("#me-float-offsetX").val(o.float?.offsetX??""),t.find("#me-float-offsetY").val(o.float?.offsetY??""),t.find("#me-float-width").val(o.float?.width??""),t.find("#me-float-radius").val(o.float?.radius??""),t.find("#me-float-shadow").val(o.float?.shadow||""),t.find("#me-float-opacity").val(o.float?.opacity??""),t.find("#me-float-title").val(o.float?.title||"");for(const[e,a]of[["#me-float-collapsible",o.float?.collapsible],["#me-float-collapsed",o.float?.collapsed],["#me-float-movable",o.float?.movable],["#me-float-resizable",o.float?.resizable]]){const c=t.find(e).get(0);c&&(c.checked=a===!0)}const D=o.float?.accent||"",X=D.startsWith("#"),N=t.find("#me-float-accent"),M=t.find("#me-float-accent-hex");N.val(X?"__custom__":D),X&&M.val(D);const U=()=>{M.get(0).style.display=N.val()==="__custom__"?"":"none"};U(),N.on("change",U);const tt=()=>N.val()==="__custom__"?M.val():N.val(),B=()=>{const e=t.find("#me-position").val()==="floating";t.find(".dm-float-field").each(function(){this.style.display=e?"":"none"}),e&&U();const a=t.find("#me-orientation").val()==="vertical";t.find(".dm-vertical-field").each(function(){this.style.display=a?"":"none"});const c=!!(t.find("#me-float-collapsible").get(0)||{}).checked;t.find(".dm-float-collapsed-field").each(function(){this.style.display=e&&c?"":"none"})};t.find("#me-orientation").val(o.orientation||""),t.find("#me-align").val(o.align||""),t.find("#me-side").val(o.side||"left"),B(),t.find("#me-position").on("change",B),t.find("#me-orientation").on("change",B),t.find("#me-float-collapsible").on("change",B);const V=(e,a,c,u)=>{const r=t.find(e).get(0);r&&(r.innerHTML="",r.add(new Option(u,"")),a.forEach(f=>r.add(new Option(c[f]||f,f))))};V("#me-highlight-hover",rt,mt,"- theme default -"),V("#me-highlight-active",pt,ft,"- theme default -"),t.find("#me-highlight-hover").val(o.highlight?.hover||""),t.find("#me-highlight-active").val(o.highlight?.active||"");const S=o.highlight?.accent||"",K=S.startsWith("#"),g=t.find("#me-highlight-accent"),m=t.find("#me-highlight-accent-hex");g.val(K?"__custom__":S),K&&m.val(S);const q=()=>{m.get(0).style.display=g.val()==="__custom__"?"":"none"};q(),g.on("change",q);const et=()=>g.val()==="__custom__"?m.val():g.val();t.find("#me-fontFamily").val(o.style?.fontFamily||""),t.find("#me-fontSize").val(o.style?.fontSize||""),t.find("#me-fontWeight").val(o.style?.fontWeight||""),t.find("#me-letterSpacing").val(o.style?.letterSpacing||""),t.find("#me-iconSize").val(o.style?.iconSize||"");const F=t.find("#me-bind-slot"),T=t.find("#me-bind-projects"),z=t.find("#me-bind-pages");F.html('<option value="">- not bound -</option>'+(R||[]).map(e=>`<option value="${h(e.slot)}">${h(e.label||e.slot)} (${h(e.slot)})</option>`).join("")),T.html(p.map(e=>`<option value="${h(e.slug)}">${h(e.name||e.slug)}</option>`).join("")),F.val(o.binding?.slot||"");const nt=new Set(o.binding?.projects||[]);Array.from(T.get(0).options).forEach(e=>{e.selected=nt.has(e.value)}),z.val((o.binding?.pages||[]).join(`
|
|
44
44
|
`)),t.find("#me-bind-page-pick").html('<option value="">- pick a page -</option>'+L.map(e=>{const a=e.urlPath||e.url||"";return a?`<option value="${h(a)}">${h(e.title||a)} - ${h(a)}</option>`:""}).join("")),t.find("#me-bind-page-add").on("click",()=>{const e=t.find("#me-bind-page-pick").val();if(!e)return;const a=z.val().split(`
|
|
45
45
|
`).map(c=>c.trim()).filter(Boolean);a.includes(e)||a.push(e),z.val(a.join(`
|
|
46
46
|
`)),Z()});function dt(){const e=F.val();return e?{slot:e,projects:Array.from(T.get(0).selectedOptions).map(a=>a.value),pages:z.val().split(`
|
|
47
|
-
`).map(a=>a.trim()).filter(Boolean)}:null}function Z(){const e=dt(),a=t.find("#me-bind-summary");if(!e){a.html('<span class="text-muted">Not bound - this menu renders only where it is mapped or placed by a shortcode.</span>');return}const c=[];e.projects.length&&c.push(`pages in ${e.projects.map(f=>`<code>${h(f)}</code>`).join(", ")}`),e.pages.length&&c.push(`pages matching ${e.pages.map(f=>`<code>${h(f)}</code>`).join(", ")}`);const u=e.pages.filter(f=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(f)),r=u.length?`<div class="text-danger" style="margin-top:.4rem">Not valid page patterns: ${u.map(f=>`<code>${h(f)}</code>`).join(", ")}</div>`:"";a.html(c.length?`Takes over the <code>${h(e.slot)}</code> slot on ${c.join(" and ")}.${r}`:`<span class="text-warning">Slot <code>${h(e.slot)}</code> selected but no project or page chosen - the binding will never match.</span>`)}F.on("change",Z),T.on("change",Z),z.on("input",Z),Z();const{getProjectFromHash:bt}=await import("../lib/project-context.js"),vt=i?bt():null,ct=t.find("#me-project");ct.html('<option value="">- none -</option>'+p.map(e=>`<option value="${h(e.slug)}">${h(e.name||e.slug)}</option>`).join("")),t.find("#me-slug").val(o.slug||""),t.find("#me-name").val(o.name||""),t.find("#me-description").val(o.description||""),ct.val(o.meta?.project||vt||""),Domma.icons.scan(t.get(0)),t.find("#me-save").on("click",async()=>{j(x,
|
|
47
|
+
`).map(a=>a.trim()).filter(Boolean)}:null}function Z(){const e=dt(),a=t.find("#me-bind-summary");if(!e){a.html('<span class="text-muted">Not bound - this menu renders only where it is mapped or placed by a shortcode.</span>');return}const c=[];e.projects.length&&c.push(`pages in ${e.projects.map(f=>`<code>${h(f)}</code>`).join(", ")}`),e.pages.length&&c.push(`pages matching ${e.pages.map(f=>`<code>${h(f)}</code>`).join(", ")}`);const u=e.pages.filter(f=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(f)),r=u.length?`<div class="text-danger" style="margin-top:.4rem">Not valid page patterns: ${u.map(f=>`<code>${h(f)}</code>`).join(", ")}</div>`:"";a.html(c.length?`Takes over the <code>${h(e.slot)}</code> slot on ${c.join(" and ")}.${r}`:`<span class="text-warning">Slot <code>${h(e.slot)}</code> selected but no project or page chosen - the binding will never match.</span>`)}F.on("change",Z),T.on("change",Z),z.on("input",Z),Z();const{getProjectFromHash:bt}=await import("../lib/project-context.js"),vt=i?bt():null,ct=t.find("#me-project");ct.html('<option value="">- none -</option>'+p.map(e=>`<option value="${h(e.slug)}">${h(e.name||e.slug)}</option>`).join("")),t.find("#me-slug").val(o.slug||""),t.find("#me-name").val(o.name||""),t.find("#me-description").val(o.description||""),ct.val(o.meta?.project||vt||""),Domma.icons.scan(t.get(0)),t.find("#me-save").on("click",async()=>{j(x,d);const e={variant:t.find("#me-variant").val(),position:t.find("#me-position").val(),appearOnHover:!!(t.find("#me-appearOnHover").get(0)||{}).checked,floatAnchor:t.find("#me-float-anchor").val(),floatOffsetX:t.find("#me-float-offsetX").val(),floatOffsetY:t.find("#me-float-offsetY").val(),floatWidth:t.find("#me-float-width").val(),floatRadius:t.find("#me-float-radius").val(),floatShadow:t.find("#me-float-shadow").val(),floatAccent:tt(),floatOpacity:t.find("#me-float-opacity").val(),floatTitle:t.find("#me-float-title").val().trim(),floatCollapsible:!!(t.find("#me-float-collapsible").get(0)||{}).checked,floatCollapsed:!!(t.find("#me-float-collapsed").get(0)||{}).checked,floatMovable:!!(t.find("#me-float-movable").get(0)||{}).checked,floatResizable:!!(t.find("#me-float-resizable").get(0)||{}).checked,highlightHover:t.find("#me-highlight-hover").val(),highlightActive:t.find("#me-highlight-active").val(),highlightAccent:et(),orientation:t.find("#me-orientation").val(),align:t.find("#me-align").val(),side:t.find("#me-side").val(),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()},c=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"],u=Object.fromEntries(Object.entries(e).filter(([O,C])=>c.includes(O)&&C)),r={};if(e.position==="floating"){e.floatAnchor&&(r.anchor=e.floatAnchor);for(const[O,C]of[["offsetX",e.floatOffsetX],["offsetY",e.floatOffsetY],["width",e.floatWidth],["radius",e.floatRadius],["opacity",e.floatOpacity]])C!==""&&C!=null&&Number.isFinite(Number(C))&&(r[O]=Number(C));e.floatShadow&&(r.shadow=e.floatShadow),e.floatAccent&&(r.accent=e.floatAccent),e.floatTitle&&(r.title=e.floatTitle),e.floatCollapsible&&(r.collapsible=!0),e.floatCollapsible&&e.floatCollapsed&&(r.collapsed=!0),e.floatMovable&&(r.movable=!0),e.floatResizable&&(r.resizable=!0)}const f={};e.highlightHover&&(f.hover=e.highlightHover),e.highlightActive&&(f.active=e.highlightActive),e.highlightAccent&&(f.accent=e.highlightAccent);const _={slug:a.slug,name:a.name,description:a.description,...e.variant&&{variant:e.variant},...e.position&&{position:e.position},...e.orientation&&{orientation:e.orientation},...e.orientation==="vertical"&&e.side&&{side:e.side},...e.orientation!=="vertical"&&e.align&&{align:e.align},...Object.keys(r).length&&{float:r},...Object.keys(f).length&&{highlight:f},...e.appearOnHover&&{appearOnHover:!0},...Object.keys(u).length&&{style:u},binding:dt(),items:Ht(d),meta:{...o.meta||{},project:a.project||null}};try{if(i)await k.menus.create(_);else if(_.slug!==o.slug){await k.menus.create(_);const O=await k.menuLocations.get();let C=!1;for(const[yt,wt]of Object.entries(O))wt===o.slug&&(O[yt]=_.slug,C=!0);C&&await k.menuLocations.save(O),await k.menus.remove(o.slug)}else await k.menus.update(o.slug,_);E.toast("Saved.",{type:"success"}),location.hash="#/menus/edit/"+encodeURIComponent(_.slug)}catch(O){E.toast(`Save failed: ${O.message||O}`,{type:"error"})}})}};
|