domma-cms 1.16.1 → 1.16.2
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/CHANGELOG.md +4 -0
- package/admin/js/lib/context-menu-action-help.js +1 -0
- package/admin/js/lib/menu-item-help.js +1 -0
- package/admin/js/templates/context-menu-editor.html +20 -16
- package/admin/js/templates/menu-editor.html +95 -53
- package/admin/js/views/context-menu-editor.js +17 -13
- package/admin/js/views/menu-editor.js +23 -22
- package/admin/js/views/menu-locations.js +3 -2
- package/docs/screenshots/context-menu-action-help.png +0 -0
- package/docs/screenshots/context-menu-help-mobile.png +0 -0
- package/docs/screenshots/menu-option-help.png +0 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as pe}from"../api.js";import{addBannerAction as Re}from"../lib/plugin-chrome.js";import{helpPopovers as me}from"../lib/help-popover.js";import{CONTEXT_ACTION_HELP as dt}from"../lib/context-menu-action-help.js";import{onceClosed as ut,openMenuAt as Pe}from"../lib/tool-kit.js";import{makeIconInput as pt}from"/admin/js/lib/shortcode-modal.js";import{makePageInput as mt}from"/admin/js/lib/page-picker.js";import{ACCENTS as ht,ACTION_ARG as _,ACTION_GROUPS as Fe,EDITABLE_TYPES as vt,NEEDS_TARGET as ft,SELF_DISABLING as bt,checkMenu as yt,duplicateItem as gt,formFromMenu as He,insertItem as xt,itemForm as kt,itemFromForm as Ke,itemRow as wt,moveItem as Be,newItem as Et,previewItems as St,previewable as _e,recordFromForm as Mt,removeItem as X,slugify as At,snapshot as he,splitList as Q,themeFromForm as Ct}from"../lib/context-menu-editor-arrange.js";import{fmtDateTime as Tt}from"../lib/dates.js";import{rowPlaceKeeper as It}from"../lib/menu-drag.js";import{cssClassProblems as Lt}from"/public/js/css-classes.mjs";const Dt=/^\/context-menus\/(new|edit\/[^/?#]+)\/?(\?.*)?$/,F=d=>`context-menu-editor.draft.${d||"new"}`,Ot=`
|
|
2
2
|
<div class="cmx-row" data-index="{{index}}" tabindex="0" data-bind-class="cls">
|
|
3
3
|
<span class="cmx-grip" title="Drag to reorder" aria-hidden="true"></span>
|
|
4
4
|
<span class="cmx-icon" aria-hidden="true" data-name="{{icon}}"></span>
|
|
@@ -10,43 +10,47 @@ import{api as de}from"../api.js";import{addBannerAction as je}from"../lib/plugin
|
|
|
10
10
|
<button type="button" class="cmx-down" data-bind-disabled="last" aria-label="Move down" title="Move down (Alt+\u2193)">\u2193</button>
|
|
11
11
|
</span>
|
|
12
12
|
<button type="button" class="cmx-kebab" aria-label="Actions for {{text}}" title="Actions">\u22EE</button>
|
|
13
|
-
</div
|
|
13
|
+
</div>`,$t='<span class="cmx-chip" data-bind-class="cls">{{text}}</span>',qt="<small>{{text}}</small>";function D(d){return String(d??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function jt(d){const u=document.createElement("template");return u.innerHTML=d.trim(),u.content.firstElementChild}let Z=null;function ve(){if(Z){try{Z.destroy()}catch{}Z=null}const d=document.getElementById("ctx-preview");if(d&&E.contextMenu&&E.contextMenu.registry)for(const u of E.contextMenu.registry(d))try{u.destroy()}catch{}}function fe(d,u){const l=d.querySelector(".dm-plugin-banner-name");if(!l)return;const T=l.querySelector(".dm-plugin-banner-sep");if(T){for(;T.nextSibling;)T.nextSibling.remove();l.appendChild(document.createTextNode(u))}}let A=null,Ue=!1;const ee=d=>String(d||"").split("?")[0].replace(/\/$/,"");function Nt(){Ue||!window.R?.use||(Ue=!0,R.use((d,u,l)=>!(Dt.test(u?.path||"")&&ee(d?.path)!==ee(u?.path))||!A?.dirty()?l():E.confirm("This context menu has unsaved changes. Leave anyway? They are kept as a draft for your next visit.").then(C=>{C?l():history.replaceState(null,"",`#${u.path}`)})),M.subscribe("router:afterChange",({to:d}={})=>{if(A&&ee(d?.path)!==A.path){const u=A;A=null,u.dispose()}}))}export const contextMenuEditorView={templateUrl:"/admin/js/templates/context-menu-editor.html",async onMount(d){if(Nt(),A){const e=A;A=null,e.dispose()}ve();const u=[],l=e=>(u.push(e),e);let T=!1;const C=d.get(0),p=C.querySelector(".cmx")||C,U=()=>!T&&p.isConnected,be=location.hash.match(/^#\/context-menus\/edit\/([^?#]+)/),m=be?decodeURIComponent(be[1]):null,f=!m,ze=ee(location.hash.replace(/^#/,""));let x=null;if(!f){const e=E.loader(C,{type:"dots"});try{x=await pe.contextMenus.get(m)}catch(n){e.destroy(),E.toast(`Could not load "${m}": ${n.message||n}`,{type:"error"});return}if(e.destroy(),!p.isConnected)return}const O=He(x),r={};for(const[e,n]of Object.entries(O))r[e]=M.observable(n);const z=()=>Object.fromEntries(Object.keys(r).map(e=>[e,r[e].peek()])),ye=()=>Object.fromEntries(Object.keys(r).map(e=>[e,r[e].value])),s=M.observable(Array.isArray(x?.items)?x.items:[]),G=M.observable(he(O,s.peek())),V=M.computed(()=>he(ye(),s.value)!==G.value),J=M.observable(!1),Y=M.observable(!1),H=M.observable([]),$=M.computed(()=>yt(ye(),s.value,{isNew:f})),Ge=M.computed(()=>{const e=$.value.errors;if(Y.value)return e;const n={};for(const t of Object.keys(e))t!=="items"&&String(r[t]?.value??"").trim()&&(n[t]=e[t]);return n}),ge=M.computed(()=>{const e=Y.value?$.value.errors:{};return{binding:!!(e.name||e.slug||e.selector),items:!!e.items||$.value.itemErrors.some(Boolean),appearance:!!(e.opacity||e.accent)}});let te=!f;l(M.effect(()=>{const e=r.name.value;te||(r.slug.value=At(e))}));const ne=M.observable([]);l(M.effect(()=>{const e=s.value,n=$.value.itemErrors;ne.value=e.map((t,i)=>({...wt(t,i,e.length,n[i]||""),key:`${i}:${JSON.stringify(t)}`}))}));const b=p.querySelector("#ctx-items");l(M.effect(()=>{ne.value,requestAnimationFrame(()=>{b?.isConnected&&(b.querySelectorAll(".cmx-icon[data-name]").forEach(e=>{const n=e.dataset.name;if(!n||e.dataset.drawn===n)return;e.dataset.drawn=n;const t=document.createElement("span");t.setAttribute("data-icon",n),t.setAttribute("data-icon-size","14"),e.replaceChildren(t)}),I.scan(b))})}));const Ve=M.computed(()=>{const e=[];e.push(r.enabled.value?{text:"On",cls:"is-ok"}:{text:"Off",cls:"is-warn"});const n=Q(r.pages.value).length,t=Q(r.projects.value).length;e.push({text:n||t?`${n+t} scope rule${n+t===1?"":"s"}`:"Site-wide",cls:""});const i=s.value.filter(o=>!["divider","header"].includes(o?.type||"item")).length;return e.push({text:`${i} item${i===1?"":"s"} to click`,cls:i?"":"is-danger"}),r.selector.value.trim()&&e.push({text:r.selector.value.trim(),cls:""}),e});let ae=null;function Je(){if(!U())return;ve();const e=s.peek();if(!_e(e))return;const n=Ct(z());Z=E.contextMenu("#ctx-preview",{cursor:"glyph",items:St(e),inherit:!1,accent:n.accent,transition:n.transition,density:n.density,radius:n.radius,shadow:n.shadow,opacity:n.opacity})}l(M.effect(()=>{s.value,r.accent.value,r.transition.value,r.density.value,r.radius.value,r.shadow.value,r.opacity.value,clearTimeout(ae),ae=setTimeout(Je,120)})),l(()=>{clearTimeout(ae),ve()});const Ye=M.computed(()=>_e(s.value)?"Right-click here to try the menu":"Add an item to try the menu here"),oe=e=>xe(e),k=e=>{s.value=e};function q(e){requestAnimationFrame(()=>b?.querySelector(`.cmx-row[data-index="${e}"]`)?.focus())}function j(e,n){n<0||n>=s.peek().length||(k(Be(s.peek(),e,n)),q(n))}function N(e,n=s.peek().length){const t=Et(e);k(xt(s.peek(),n,t)),e!=="divider"&&xe(n,{isAdd:!0})}function We(e){const n=s.peek(),t=n[e];if(!t)return[];const i=t.type||"item";return[{header:i==="divider"?"Divider":t.text||"(no text)"},{label:i==="divider"?"Change\u2026":"Edit\u2026",icon:"edit",action:()=>oe(e)},{type:"divider"},{label:"Move up",icon:"arrow-up",shortcut:"Alt+\u2191",disabled:e===0,action:()=>j(e,e-1)},{label:"Move down",icon:"arrow-down",shortcut:"Alt+\u2193",disabled:e===n.length-1,action:()=>j(e,e+1)},{label:"Duplicate",icon:"copy",action:()=>{k(gt(s.peek(),e)),q(e+1)}},{label:"Add below",icon:"plus",submenu:[{label:"Item",action:()=>N("item",e+1)},{label:"Heading",action:()=>N("header",e+1)},{label:"Divider",action:()=>N("divider",e+1)}]},{type:"divider"},{label:"Remove",icon:"trash",danger:!0,action:()=>{k(X(s.peek(),e)),q(Math.max(0,e-1))}}]}let h=null;function xe(e,{isAdd:n=!1}={}){const t=s.peek()[e];if(!t)return;if(h)try{h.so.close(!0)}catch{}const i=kt(t),o={};for(const[a,c]of Object.entries(i))o[a]=M.observable(c);const W=()=>Object.fromEntries(Object.keys(o).map(a=>[a,o[a].peek()])),nt=JSON.stringify(i),ie=()=>JSON.stringify(W())!==nt,Ie=!vt.includes(i.type),re=Array.isArray(t.submenu)&&t.submenu.length,Le={...o,locked:Ie,notLocked:!Ie,hasSub:re,subText:re?`Opens a submenu of ${t.submenu.length} - kept as it is (edit it in the file).`:"",isItem:M.computed(()=>o.type.value!=="divider"&&o.type.value!=="header"),hasText:M.computed(()=>o.type.value!=="divider"),showAction:M.computed(()=>o.type.value!=="divider"&&o.type.value!=="header"&&!re),argLabel:M.computed(()=>_[o.action.value]?.label||""),note:M.computed(()=>bt[o.action.value]||ft[o.action.value]||""),problem:M.computed(()=>{const a=Ke(Object.fromEntries(Object.keys(o).map(L=>[L,o[L].value])),t),c=Lt(a.class)[0];return a.type==="divider"?"":String(a.text||"").trim()?c?`CSS classes: ${c}.`:_[a.action?.type]?.required&&!String(a.action?.[_[a.action.type].key]||"").trim()?`Needs a ${_[a.action.type].label.toLowerCase()}.`:"":"Needs text."})},g=E.slideover({title:n?"New item":i.type==="divider"?"Divider":`Edit ${D(i.text||"item")}`,size:"sm",position:"right"}),at=Fe.map(a=>`<optgroup label="${D(a.label)}">${Object.entries(a.actions).map(([c,L])=>`<option value="${D(c)}">${D(L)}</option>`).join("")}</optgroup>`).join(""),w=jt(`
|
|
14
14
|
<div class="cmx-form">
|
|
15
15
|
<div data-bind-hidden="locked">
|
|
16
16
|
<label class="form-label" for="cmx-i-type">Type
|
|
17
|
-
|
|
17
|
+
<span data-help-title="Type" data-help="Item creates a clickable action with text and an optional icon. Heading labels a group without being clickable. Divider draws a separating line and has no text or action. Choose a type appropriate to the row purpose. Every saved menu needs at least one actionable item; a menu made only of headings and dividers is refused."></span></label>
|
|
18
18
|
<select id="cmx-i-type" class="form-select" data-model="type.value">
|
|
19
19
|
<option value="item">Item</option><option value="header">Heading</option><option value="divider">Divider</option>
|
|
20
20
|
</select>
|
|
21
21
|
</div>
|
|
22
|
-
<p class="cmx-note" data-bind-hidden="notLocked">A ${
|
|
22
|
+
<p class="cmx-note" data-bind-hidden="notLocked">A ${D(i.type)} item - its text, icon and action can be changed here; its type is kept.</p>
|
|
23
23
|
<div data-bind-hidden="!hasText.value">
|
|
24
|
-
<label class="form-label" for="cmx-i-text">Text
|
|
24
|
+
<label class="form-label" for="cmx-i-text">Text
|
|
25
|
+
<span data-help-title="Text" data-help="Set the words visitors see for this item or heading, up to 80 characters. Use a short action phrase such as Copy link or Open contact form for clickable items. This label does not choose the behaviour: set Does separately. Dividers have no text. Apply with Done, then Save the menu to keep it."></span></label>
|
|
25
26
|
<input id="cmx-i-text" type="text" class="form-input" maxlength="80" data-model="text.value">
|
|
26
27
|
</div>
|
|
27
28
|
<div data-bind-hidden="!isItem.value">
|
|
28
|
-
<label class="form-label" for="cmx-i-icon">Icon <small class="cmx-hint-inline">(optional)</small
|
|
29
|
+
<label class="form-label" for="cmx-i-icon">Icon <small class="cmx-hint-inline">(optional)</small>
|
|
30
|
+
<span data-help-title="Icon (optional)" data-help="Choose an optional icon shown beside the item text. Use the icon picker to find a recognisable symbol that supports the label, such as a printer for Print. Leaving it empty keeps a text-only item. Choosing an icon does not change the action, and headings and dividers do not need one."></span></label>
|
|
29
31
|
<div class="cmx-icon-mount"></div>
|
|
30
32
|
</div>
|
|
31
|
-
<p class="cmx-note" data-bind-hidden="!hasSub">${
|
|
33
|
+
<p class="cmx-note" data-bind-hidden="!hasSub">${D(Le.subText)}</p>
|
|
32
34
|
<div data-bind-hidden="!showAction.value">
|
|
33
35
|
<label class="form-label" for="cmx-i-action">Does
|
|
34
|
-
|
|
35
|
-
<select id="cmx-i-action" class="form-select" data-model="action.value">${
|
|
36
|
+
<span data-help-title="Does" data-help="Choose the action performed when this item is clicked. Page actions navigate, reload, print or open site search; clipboard actions work on the clicked selection or editable field; display actions target a collection display; site actions open a block or run a saved Action. Extra fields appear when needed. Some actions are unavailable when the click target cannot support them."></span></label>
|
|
37
|
+
<select id="cmx-i-action" class="form-select" data-model="action.value">${at}</select>
|
|
38
|
+
<small class="cmx-hint">About this action <span class="cmx-action-help"></span></small>
|
|
36
39
|
<small class="cmx-hint" data-bind-hidden="!note.value" data-bind-text="note.value"></small>
|
|
37
40
|
</div>
|
|
38
41
|
<div data-bind-hidden="!argLabel.value || !showAction.value">
|
|
39
|
-
<label class="form-label" for="cmx-i-arg" data-bind-text="argLabel.value">Argument</
|
|
42
|
+
<label class="form-label" for="cmx-i-arg"><span data-bind-text="argLabel.value">Argument</span>
|
|
43
|
+
<span data-help-title="Argument" data-help="Supply the value required by the chosen action. URL is a destination; Action slug names a saved Action; Block name names an existing content block. Field names a collection field for filtering or sorting, Format chooses the export format, and Flag field optionally overrides the spam marker. The label and input change with Does. Required values must be supplied before saving."></span></label>
|
|
40
44
|
<div class="cmx-arg-mount"></div>
|
|
41
45
|
</div>
|
|
42
46
|
<div data-bind-hidden="!isItem.value">
|
|
43
47
|
<label class="form-label" for="cmx-i-class">CSS classes <small class="cmx-hint-inline">(optional)</small>
|
|
44
|
-
|
|
48
|
+
<span data-help-title="CSS classes (optional)" data-help="Add CSS class names, separated by spaces, to this item. Style them in Custom CSS to distinguish one action from the rest. Enter class names without leading dots; this field does not accept CSS declarations or scripts. Panel-wide styling belongs in the menu Appearance tab instead."></span></label>
|
|
45
49
|
<input id="cmx-i-class" type="text" class="form-input" placeholder="menu-cta" autocomplete="off" spellcheck="false" data-model="className.value">
|
|
46
50
|
</div>
|
|
47
51
|
<div class="cmx-setting" data-bind-hidden="!isItem.value">
|
|
48
52
|
<label class="form-label" for="cmx-i-danger">Destructive (red)
|
|
49
|
-
|
|
53
|
+
<span data-help-title="Destructive (red)" data-help="Draw this item in red to signal a destructive or sensitive action. This is a visual warning only: it does not add a confirmation, grant permissions or change what the selected action does. Use it for an action whose consequences warrant attention. Done applies the choice to the item; Save keeps the menu."></span></label>
|
|
50
54
|
<input id="cmx-i-danger" type="checkbox" class="form-switch" data-model="danger.value">
|
|
51
55
|
</div>
|
|
52
56
|
<small class="cmx-err" data-bind-hidden="!problem.value" data-bind-text="problem.value"></small>
|
|
@@ -57,4 +61,4 @@ import{api as de}from"../api.js";import{addBannerAction as je}from"../lib/plugin
|
|
|
57
61
|
<button type="button" class="btn btn-sm btn-ghost" data-on-click="cancel">Cancel</button>
|
|
58
62
|
<button type="button" class="btn btn-sm btn-primary" data-on-click="done"><span data-icon="check" data-icon-size="13"></span> Done</button>
|
|
59
63
|
</div>
|
|
60
|
-
</div>`),P=
|
|
64
|
+
</div>`),P=pt("edit",i.icon);P.input.id="cmx-i-icon",P.input.classList.add("form-input");const De=()=>{o.icon.value=P.input.value};P.input.addEventListener("input",De),w.querySelector(".cmx-icon-mount").appendChild(P.el);const ce=w.querySelector(".cmx-arg-mount");let v=null;const le=()=>{o.arg.value=v.value};function Oe(){v?.removeEventListener("input",le),ce.replaceChildren();const a=_[o.action.peek()];if(!a){v=null;return}if(a.key==="url"){const c=mt(a.placeholder,o.arg.peek());v=c.input,ce.appendChild(c.el)}else v=document.createElement("input"),v.type="text",v.className="form-input",v.placeholder=a.placeholder,v.value=o.arg.peek(),ce.appendChild(v);v.id="cmx-i-arg",v.addEventListener("input",le)}let $e=o.action.peek();const ot=M.effect(()=>{const a=o.action.value;a!==$e&&($e=a,o.arg.value="",Oe())});Oe();const qe=()=>{const a=Ke(W(),t),c=[...s.peek()],L=c.indexOf(t);L!==-1&&(c[L]=a,k(c))};let K=!1,de=!1;const je={done:()=>{qe(),K=!0,g.close(),q(e)},cancel:()=>g.close(),remove:()=>{const a=s.peek().indexOf(t);a!==-1&&k(X(s.peek(),a)),K=!0,g.close()}};g.element.appendChild(w);const st=me(w),it=M.applyBindings(Le,w,{methods:je}),B=w.querySelector(".cmx-action-help");let ue=()=>{};const rt=M.effect(()=>{const a=o.action.value;ue(),B.replaceChildren(),B.removeAttribute("data-help-ready"),B.dataset.helpTitle=Fe.flatMap(c=>Object.entries(c.actions)).find(([c])=>c===a)?.[1]||"Selected action",B.dataset.help=dt[a]||"Choose an action to see what it does.",ue=me(B)}),ct=a=>{(a.ctrlKey||a.metaKey)&&a.key==="Enter"&&(a.preventDefault(),je.done())};w.addEventListener("keydown",ct);const Ne={so:g,dirty:ie,apply:qe};h=Ne,ut(g,()=>{ot(),it.dispose(),st(),rt(),ue(),P.input.removeEventListener("input",De),v?.removeEventListener("input",le),h===Ne&&(h=null)});const lt=g.close.bind(g);g.close=async a=>{if(!K&&a!==!0&&ie()){if(de)return;de=!0;const c=await E.confirm("Discard the changes to this item?");if(de=!1,!c)return}if(!K&&n&&a!==!0&&!ie()){const c=s.peek().indexOf(t);c!==-1&&k(X(s.peek(),c))}return K=!0,lt()},I.scan(w),g.open(),requestAnimationFrame(()=>w.querySelector(i.type==="divider"?"#cmx-i-type":"#cmx-i-text")?.focus())}l(()=>{if(h){const e=h;e.dirty()&&e.apply();try{e.so.close(!0)}catch{}}});async function se(){if(J.peek())return;Y.value=!0,H.value=[];const e=$.peek();if(!e.ok){const t=Object.keys(e.errors).length;E.toast(`Not saved yet - ${t===1?"one thing needs":`${t} things need`} fixing (marked in red).`,{type:"warning"});const i=ge.peek(),o=i.binding?0:i.items?1:i.appearance?2:-1;o>=0&&Ze?.activate?.(o);return}J.value=!0;const n=Mt(z(),s.peek());try{if(f)await pe.contextMenus.create(n);else{const t=await pe.contextMenus.update(m,n);t?.items&&(s.value=t.items)}if(!U())return;G.value=he(z(),s.peek());try{S.remove?S.remove(F(m)):S.set(F(m),null)}catch{}window.dispatchEvent(new CustomEvent("dm:sidebar-count",{detail:{url:"#/context-menus"}})),f?(E.toast("Context menu created.",{type:"success"}),location.hash="#/context-menus"):(E.toast("Context menu saved.",{type:"success"}),fe(C,r.name.peek()||m))}catch(t){H.value=String(t.message||t).split("; ").map((i,o)=>({key:String(o),text:i})),p.querySelector(".cmx-alert")?.scrollIntoView({behavior:"smooth",block:"nearest"})}finally{J.value=!1}}function Xe(){E.confirm("Throw away your unsaved changes to this menu?").then(e=>{if(!e||!U())return;const n=He(x);for(const[t,i]of Object.entries(n))r[t].value=i;s.value=Array.isArray(x?.items)?x.items:[],H.value=[]})}p.querySelector("#ctx-items").innerHTML=Ot,p.querySelector(".cmx-chips").innerHTML=$t,p.querySelector(".cmx-server-errors").innerHTML=qt;const ke=p.querySelector("#ctx-accent");if(O.accent&&!ht.includes(O.accent)&&ke){const e=document.createElement("option");e.value=O.accent,e.textContent=`Custom (${O.accent})`,ke.appendChild(e)}const Qe={...r,items:s,rows:ne,chips:Ve,dirty:V,saving:J,err:Ge,tabBad:ge,serverErrors:H,isNew:f,notNew:!f,previewText:Ye,serverError:M.computed(()=>H.value.length>0),saveText:M.computed(()=>f?"Create menu":"Save"),problemText:M.computed(()=>Y.value&&!$.value.ok?" - some need fixing before they can be saved":""),scopeText:M.computed(()=>{const e=Q(r.pages.value),n=Q(r.projects.value);return!e.length&&!n.length?"Site-wide: every page carries this menu.":`Only ${[e.length&&`pages matching ${e.join(", ")}`,n.length&&`pages in ${n.join(", ")}`].filter(Boolean).join(", or ")}.`})},Ze=E.tabs(p.querySelector("#ctx-tabs"),{tabSelector:".tab-item",panelSelector:".tab-panel"}),et=M.applyBindings(Qe,p,{methods:{save:()=>se(),discard:()=>Xe(),addItem:()=>N("item"),addHeader:()=>N("header"),addDivider:()=>N("divider")}});l(()=>et.dispose()),l(me(p,{position:"bottom"}));const we=p.querySelector("#ctx-slug"),Ee=()=>{te=!0};we?.addEventListener("input",Ee),l(()=>we?.removeEventListener("input",Ee));const tt=E.contextMenu(b,{cursor:"glyph",match:".cmx-row",inherit:!1,density:"compact",items:e=>We(Number(e.dataset.index))});l(()=>tt?.destroy?.());const Se=e=>{const n=e.target.closest(".cmx-row");if(!n)return;const t=Number(n.dataset.index);if(e.target.closest(".cmx-kebab")){Pe(e.target.closest(".cmx-kebab"));return}if(e.target.closest(".cmx-up")){j(t,t-1);return}if(e.target.closest(".cmx-down")){j(t,t+1);return}oe(t)},Me=e=>{const n=e.target.closest(".cmx-row");if(!n||e.target!==n)return;const t=Number(n.dataset.index);e.key==="F10"&&e.shiftKey?(e.preventDefault(),Pe(n)):e.key==="Enter"?(e.preventDefault(),oe(t)):e.altKey&&e.key==="ArrowUp"?(e.preventDefault(),j(t,t-1)):e.altKey&&e.key==="ArrowDown"?(e.preventDefault(),j(t,t+1)):e.key==="Delete"&&(e.preventDefault(),k(X(s.peek(),t)),q(Math.max(0,t-1)))};if(b.addEventListener("click",Se),b.addEventListener("keydown",Me),l(()=>{b.removeEventListener("click",Se),b.removeEventListener("keydown",Me)}),typeof E.sortable=="function"){const e=It(),n=E.sortable(b,{items:".cmx-row",key:null,keyboard:!1,onStart:e.start,onEnd:({item:t})=>e.restore(t),onSort:({item:t,from:i,to:o})=>{e.restore(t),k(Be(s.peek(),i,o)),q(o)}});l(()=>n.destroy())}f?fe(C,"New menu"):fe(C,x?.name||m),Re(d,{id:"ctx-editor-back",icon:"arrow-left",title:"All context menus",onClick:()=>{location.hash="#/context-menus"}}),Re(d,{id:"ctx-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>se()});const Ae=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||h||(e.preventDefault(),e.stopImmediatePropagation(),se())};document.addEventListener("keydown",Ae,!0),l(()=>document.removeEventListener("keydown",Ae,!0));const Ce=()=>{if(V.peek())try{S.set(F(m),{form:z(),items:s.peek(),base:G.peek(),at:new Date().toISOString()})}catch{}},Te=e=>{h?.dirty()&&h.apply(),V.peek()&&(Ce(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",Te),l(()=>window.removeEventListener("beforeunload",Te)),A={path:ze,dirty:()=>V.peek()||!!h?.dirty(),dispose(){if(T=!0,h){const e=h;e.dirty()&&e.apply();try{e.so.close(!0)}catch{}}Ce(),u.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(p);let y=null;try{y=S.get(F(m))}catch{}if(y?.form){try{S.remove?S.remove(F(m)):S.set(F(m),null)}catch{}const e=y.base&&y.base!==G.peek(),n=y.at?` (${Tt(y.at)})`:"",t=f?"the new context menu you started":`"${D(x?.name||m)}"`,i=e?`You left unsaved changes to ${t}${n}, but it has been saved since. Restore your version anyway?`:`You left unsaved changes to ${t}${n}. Restore them?`;if(await E.confirm(i)&&U()){for(const[o,W]of Object.entries(y.form))r[o]&&o!==(f?"":"slug")&&(r[o].value=W);f&&y.form.slug&&(te=!0),Array.isArray(y.items)&&(s.value=y.items)}}}};
|
|
@@ -1,32 +1,33 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<div class="menu-tree-row menu-tree-row--separator" data-id="${
|
|
1
|
+
import{api as P}from"../api.js";import{colourToCss as Et,HIGHLIGHT_HOVER as ut,HIGHLIGHT_ACTIVE as pt,HIGHLIGHT_HOVER_LABELS as mt,HIGHLIGHT_ACTIVE_LABELS as ht,cssClassProblems as ft,normaliseCssClasses as At,ENTRANCE_ANIMATIONS as ot,ENTRANCE_ANIMATION_LABELS as Ct}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{openIconPicker as $t}from"../lib/card-builder.js";import{normalisePanel as Lt,DESCRIPTION_MAX as Tt}from"/public/js/menu-look.mjs?v=20261029-bar-shapes";import{sortableMenuRows as _t}from"../lib/menu-drag.js";import{placeTools as Ht,ensureLockedTools as It,isLockedUrl as Nt,groupKeyFor as Ot,slugify as Rt,GROUP_ICON as Bt,TOOLS_FOLDER_KEY as Pt}from"../lib/sidebar-grouping.js";import{flatten as gt,nest as Dt,uid as Xe}from"../lib/menu-rows.js";import{addBannerAction as at}from"../lib/plugin-chrome.js";import{helpPopovers as st}from"../lib/help-popover.js";import{MENU_ITEM_HELP as jt}from"../lib/menu-item-help.js";import{onceClosed as Ft,openMenuAt as lt,sidebarCount as Mt}from"../lib/tool-kit.js";import{addChild as zt,bindingFrom as Ut,duplicateRow as Wt,indentRow as Kt,insertBelow as qt,isStructural as ve,menuFromForm as Gt,menuProblems as Vt,moveRow as vt,outdentRow as Yt,siblingsOf as Xt,snapshot as Je}from"../lib/menu-editor-arrange.js";import{fmtDateTime as Jt}from"../lib/dates.js";const Qt=760,Zt="admin-sidebar",en=/^\/menus\/(new|edit\/[^?#]+)(\?.*)?$/;function x(n){return String(n||"").replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<")}const De=n=>String(n??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");function tn(n,u){let l=0,r=u.find(s=>s.id===n);for(;r&&r.parentId;)l++,r=u.find(s=>s.id===r.parentId);return l}function bt(n){const u=new Set,l=new Set,r=s=>{for(const y of n.filter(T=>T.parentId===s))!y.type&&Nt(y.url)&&!l.has(y.url)&&(l.add(y.url),u.add(y.id)),r(y.id)};return r(null),u}const Qe='<button type="button" class="btn btn-sm btn-ghost mex-kebab" aria-label="Actions" title="Actions (right-click, or Shift+F10)">\u22EE</button>';function nn(n,u,l=!1){const r=l?bt(u):new Set,s=[];function y(T){for(const p of u.filter(G=>G.parentId===T)){const $=`style="margin-left:${tn(p.id,u)*28}px"`;if(p.type==="separator"){s.push(`
|
|
2
|
+
<div class="menu-tree-row menu-tree-row--separator" data-id="${p.id}" tabindex="0" ${$}>
|
|
3
3
|
<span class="menu-tree-grip" data-icon="grip-vertical" data-icon-size="18" title="Drag to move"></span>
|
|
4
4
|
<span class="me-sep-line"><span class="me-sep-label">Separator</span></span>
|
|
5
|
-
${
|
|
6
|
-
</div>`);continue}if(
|
|
7
|
-
<div class="menu-tree-row menu-tree-row--spacer" data-id="${
|
|
5
|
+
${Qe}
|
|
6
|
+
</div>`);continue}if(p.type==="spacer"){const be=["xs","sm","md","lg","xl","flex"].map(ae=>`<option value="${ae}"${(p.size||"md")===ae?" selected":""}>${ae==="flex"?"flexible":ae}</option>`).join("");s.push(`
|
|
7
|
+
<div class="menu-tree-row menu-tree-row--spacer" data-id="${p.id}" tabindex="0" ${$}>
|
|
8
8
|
<span class="menu-tree-grip" data-icon="grip-vertical" data-icon-size="18" title="Drag to move"></span>
|
|
9
9
|
<span class="me-spacer-line">
|
|
10
10
|
<span class="me-spacer-label">Spacer</span>
|
|
11
|
-
<select class="form-input me-spacer-size" title="Spacer size - flexible fills the leftover room" aria-label="Spacer size">${
|
|
11
|
+
<select class="form-input me-spacer-size" title="Spacer size - flexible fills the leftover room" aria-label="Spacer size">${be}</select>
|
|
12
|
+
<span data-help-title="Spacer size" data-help="Choose a fixed gap from xs (smallest) to xl (largest), or flexible to fill leftover room along the menu. A fixed size separates neighbouring links; a flexible spacer can push groups apart in a horizontal row. This row supplies space only, with no label or destination. Save the menu to keep a size change."></span>
|
|
12
13
|
</span>
|
|
13
|
-
${
|
|
14
|
-
</div>`);continue}if(
|
|
15
|
-
<div class="menu-tree-row menu-tree-row--switcher" data-id="${
|
|
14
|
+
${Qe}
|
|
15
|
+
</div>`);continue}if(p.type==="theme-switcher"){s.push(`
|
|
16
|
+
<div class="menu-tree-row menu-tree-row--switcher" data-id="${p.id}" tabindex="0" ${$}>
|
|
16
17
|
<span class="menu-tree-grip" data-icon="grip-vertical" data-icon-size="18" title="Drag to move"></span>
|
|
17
18
|
<span class="me-sep-line"><span class="me-sep-label"><span data-icon="palette"></span> Theme switcher - the themes chosen under Theme > Theme switcher</span></span>
|
|
18
|
-
${
|
|
19
|
-
</div>`);continue}const V=
|
|
20
|
-
<div class="menu-tree-row menu-tree-row--slim${
|
|
19
|
+
${Qe}
|
|
20
|
+
</div>`);continue}const V=p.badge&&(p.badge.text||p.badge.countFrom)?`<span class="dm-menu-badge">${x(String(p.badge.text||"#"))}</span>`:"",_=p.tool,f=_&&(_.persisted||p.touched),re=_?_.orphan?' <span class="badge badge-secondary me-tool-badge" title="A placeholder for a Tool that is not enabled - it returns here when it is">Not enabled</span>':` <span class="badge badge-secondary me-tool-badge" title="${f?"A Tool, placed here by this menu":"A Tool in its default place - move or edit it to place it here"}">${x(_.source||"Tool")}${f?"":" \xB7 default"}</span>`:"",ne=r.has(p.id),ce=Et(p.colour),oe=ce?` style="color:${x(ce)}"`:"",de=ne?'<button type="button" class="btn btn-sm btn-ghost" disabled title="Always shown - it can be moved, not hidden" aria-label="Always shown"><span data-icon="lock"></span></button>':`<button type="button" class="btn btn-sm btn-ghost me-hidden ${p.hidden?"active":""}" title="${p.hidden?"Hidden - click to show":"Hide"}" aria-label="${p.hidden?"Show":"Hide"}" aria-pressed="${p.hidden?"true":"false"}"><span data-icon="${p.hidden?"eye-off":"eye"}"></span></button>`;s.push(`
|
|
21
|
+
<div class="menu-tree-row menu-tree-row--slim${p.hidden?" menu-tree-row--hidden":""}${_?" menu-tree-row--tool":""}${_&&!f?" menu-tree-row--default":""}" data-id="${p.id}" tabindex="0" ${$}>
|
|
21
22
|
<span class="menu-tree-grip" data-icon="grip-vertical" data-icon-size="18" title="Drag to move"></span>
|
|
22
|
-
<span class="me-row-label${!
|
|
23
|
-
<span class="me-row-url">${
|
|
24
|
-
${
|
|
23
|
+
<span class="me-row-label${!p.text&&!_?" mex-untitled":""}"${oe}>${x(p.text||_?.label||"(no label - click to add one)")}${V}${re}</span>
|
|
24
|
+
<span class="me-row-url">${x(p.url||"")}</span>
|
|
25
|
+
${de}
|
|
25
26
|
<button type="button" class="btn btn-sm btn-ghost me-edit" title="Edit" aria-label="Edit"><span data-icon="edit"></span></button>
|
|
26
|
-
${
|
|
27
|
-
</div>`),
|
|
28
|
-
`),placeholder:"One per line, up to six"})),
|
|
29
|
-
`),button:{text:g("featureBtnText"),url:g("featureBtnUrl")}}:null})}Se=!0,_.close(!0),l()})}let q=null,bt=!1;function ln(){bt||!window.R?.use||(bt=!0,R.use((n,u,i)=>{const r=u?.path||"";return!(Zt.test(r)&&(n?.path||"")!==r)||!q?.dirty()?i():E.confirm(`You have unsaved changes to the menu "${De(q.name())}". Leave anyway? They are kept as a draft for your next visit.`).then(w=>{w?i():history.replaceState(null,"",`#${r}`)})}),M.subscribe("router:afterChange",({to:n}={})=>{if(q&&(n?.path||"")!==q.path){const u=q;q=null,u.dispose()}}))}function yt(n,u){const i=n.querySelector(".dm-plugin-banner-name"),r=i?.querySelector(".dm-plugin-banner-sep");if(!(!i||!r)){for(;r.nextSibling;)r.nextSibling.remove();i.appendChild(document.createTextNode(u))}}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(n){if(ln(),q){const e=q;q=null,e.dispose()}const u=[],i=e=>(u.push(e),e);let r=!1;const l=n.get(0).querySelector(".mex")||n.get(0),w=()=>!r&&l.isConnected,L=location.hash.replace(/^#/,""),m=location.hash.match(/^#\/menus\/edit\/([^?#]+)/),G=m?decodeURIComponent(m[1]):null,C=!G,V=`menu-editor.draft.${G||"new"}`,N=E.loader(n.get(0),{type:"dots"});let f,le,Z,se,ee,ie,ve;try{if(C)f={slug:"",name:"",description:"",items:[]};else try{f=await D.menus.get(G)}catch(e){E.toast(`Failed to load menu: ${e.message||e}`,{type:"error"}),location.hash="#/menus";return}[le,Z,se,ee,ie,ve]=await Promise.all([D.projects.list().catch(()=>[]),H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),D.collections.list().catch(()=>[]),D.get("/collections/roles/entries?limit=100").catch(()=>null),D.menuLocations.registry().catch(()=>[]),D.pages.list().catch(()=>[])])}finally{N.destroy()}if(!l.isConnected)return;const be=(Array.isArray(ee)?ee:ee?.entries||[]).map(e=>({name:e.data?.name,label:e.data?.label,level:e.data?.level??99})).filter(e=>e.name).sort((e,o)=>o.level-e.level),P=!C&&G===Qt;let $e=[];if(P&&($e=await H.get("/api/sidebar/registered-items").catch(()=>(E.toast("Could not load the Tools - they are not shown, and saving keeps their places as they are.",{type:"warning",duration:8e3}),[])),!l.isConnected))return;const Ce=e=>ft(P?It(_t(e||[],$e,{forEditor:!0}),{leaveInHiddenFolders:!0}):e);let h=Ce(f.items);const j=M.observable(!1),re=M.observable(!1),je=M.observable(""),Le=M.observable(0);let U="";const T=l.querySelector("#me-items-list");function ye(){tn(T,h,P),Le.value=h.filter(e=>!ge(e)).length}const O=()=>{ye(),X()},Y=()=>P?vt(h):new Set,ce=e=>h.find(o=>o.id===e);function de(e){requestAnimationFrame(()=>T.querySelector(`.menu-tree-row[data-id="${CSS.escape(e)}"]`)?.focus())}function Te(e){const o=v=>h.find(s=>!s.type&&!s.url&&!s.tool&&(s.key||"").toLowerCase()===v)||h.find(s=>!s.type&&!s.url&&!s.tool&&!s.key&&(s.text||"").toLowerCase()===v);let a=o(String(e.tool.folder||"").toLowerCase())||o(Bt);const c=String(e.tool.group||"").trim();if(a&&c){const v=Nt(a.key||Ot(a.text),c),s=d=>d!==e&&!d.type&&!d.url&&!d.tool;let y=h.find(d=>s(d)&&(d.key||"").toLowerCase()===v)||h.find(d=>s(d)&&d.parentId===a.id&&(!d.key||d.key.toLowerCase()===v)&&(d.text||"").toLowerCase()===c.toLowerCase());y||([y]=ft([{text:c,key:v,icon:Rt,synthesised:!0}],a.id),h.push(y)),a=y}e.parentId=a?a.id:null,h.splice(h.indexOf(e),1),h.push(e),Object.assign(e,{text:e.tool.defaults.text,icon:e.tool.defaults.icon,visibility:"",hidden:!1,badge:null,pill:null,colour:"",highlight:null,touched:!1}),e.tool={...e.tool,persisted:!1}}const _e=e=>!Y().has(e.id)&&!(e.tool&&!e.tool.orphan);async function Fe(e){const o=ce(e);if(!o||!_e(o))return;const a=d=>{let J=d;for(;J;){if(J.id===e)return!0;J=h.find(z=>z.id===J.parentId)}return!1},c=h.filter(d=>d.id!==e&&a(d));if(c.length&&!await E.confirm(`Remove "${De(o.text||o.type||"this item")}" and the ${c.length} item${c.length===1?"":"s"} beneath it?`))return;const v=Y(),s=c.filter(d=>d.tool),y=c.filter(d=>!d.tool&&v.has(d.id));h=h.filter(d=>!a(d)||s.includes(d)||y.includes(d));for(const d of y)d.parentId=o.parentId??null;for(const d of s)d.tool.orphan?d.parentId=o.parentId??null:Te(d);O()}function ue(e){const o=ce(e);!o||ge(o)||Y().has(e)||(o.hidden=!o.hidden,o.touched=!0,O())}function x(e){const o=ce(e);!o||ge(o)||(o.locked=P&&Y().has(o.id),an(o,Z.resources||[],se,be,()=>{O(),de(e)},{cards:!P}))}function B(e){const o={id:Ve(),parentId:null,...e==="separator"||e==="theme-switcher"?{type:e}:e==="spacer"?{type:e,size:"md"}:{text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}};h.push(o),O(),ge(o)?de(o.id):x(o.id)}const ke=e=>o=>{e(o)&&(O(),de(o))},Me=ke(e=>gt(h,e,-1)),ze=ke(e=>gt(h,e,1)),Ie=ke(e=>Wt(h,e)),Ue=ke(e=>Vt(h,e));function Je(e){const o=ce(e);if(!o)return[];const a=ge(o),c=Y().has(e),v=Yt(h,e),s=v.findIndex(z=>z.id===e),y=o.tool,d=y&&(y.persisted||o.touched),J=z=>()=>{const Be=Kt(h,e,z,Ve());O(),Be&&!ge(Be)?x(Be.id):Be&&de(Be.id)};return[{header:a?{separator:"Separator",spacer:"Spacer","theme-switcher":"Theme switcher"}[o.type]:o.text||y?.label||"(untitled)"},...a?[]:[{label:"Edit\u2026",icon:"edit",action:()=>x(e)}],{type:"divider"},{label:"Add item below",icon:"plus",action:J("item")},{label:"Add sub-item",icon:"corner-down-right",disabled:a,action:()=>{const z=Mt(h,e,Ve());O(),z&&x(z.id)}},{label:"Add separator below",icon:"minus",action:J("separator")},{label:"Add spacer below",icon:"move",action:J("spacer")},...P?[]:[{label:"Add theme switcher below",icon:"palette",action:J("theme-switcher")}],{type:"divider"},{label:"Move up",icon:"arrow-up",shortcut:"Alt+\u2191",disabled:s<=0,action:()=>Me(e)},{label:"Move down",icon:"arrow-down",shortcut:"Alt+\u2193",disabled:s<0||s>=v.length-1,action:()=>ze(e)},{label:"Indent",icon:"chevron-right",shortcut:"Alt+\u2192",disabled:s<=0||ge(v[s-1]),action:()=>Ie(e)},{label:"Outdent",icon:"chevron-left",shortcut:"Alt+\u2190",disabled:o.parentId==null,action:()=>Ue(e)},{type:"divider"},...a?[]:[{label:o.hidden?"Show":"Hide",icon:o.hidden?"eye":"eye-off",disabled:c,action:()=>ue(e)}],{label:"Duplicate",icon:"copy",disabled:!!y,action:()=>{const z=Ut(h,e,Ve);O(),z&&de(z.id)}},...y&&!y.orphan&&d?[{label:"Back to its default place",icon:"rotate-ccw",action:()=>{Te(o),O()}}]:[],{label:c?"Remove - always in the sidebar":y&&!y.orphan?"Remove - a Tool is hidden, not removed":"Remove\u2026",icon:"trash",danger:!0,disabled:!_e(o),action:()=>Fe(e)}]}const me=E.contextMenu(T,{cursor:"glyph",match:".menu-tree-row",inherit:!1,density:"compact",items:e=>Je(e.dataset.id)});i(()=>me?.destroy?.());const Qe=Tt(T,()=>h,O,{items:".menu-tree-row",handle:".menu-tree-grip"});i(()=>Qe?.destroy());const We=e=>{const o=e.target.closest(".menu-tree-row");if(!o)return;const a=o.dataset.id;if(e.target.closest(".mex-kebab")){at(e.target.closest(".mex-kebab"));return}if(e.target.closest(".me-hidden")){ue(a);return}if(e.target.closest(".me-edit")){x(a);return}e.target.closest("select, .menu-tree-grip, button")||x(a)},Ke=e=>{const o=e.target.closest(".menu-tree-row");if(!o||e.target!==o){o&&e.key==="F10"&&e.shiftKey&&(e.preventDefault(),at(o));return}const a=o.dataset.id;if(e.key==="F10"&&e.shiftKey)e.preventDefault(),at(o);else if(e.key==="Enter")e.preventDefault(),x(a);else if(e.key==="Delete")e.preventDefault(),Fe(a);else if(e.altKey&&e.key==="ArrowUp")e.preventDefault(),Me(a);else if(e.altKey&&e.key==="ArrowDown")e.preventDefault(),ze(a);else if(e.altKey&&e.key==="ArrowRight")e.preventDefault(),Ie(a);else if(e.altKey&&e.key==="ArrowLeft")e.preventDefault(),Ue(a);else if(e.key==="ArrowDown"||e.key==="ArrowUp"){const c=[...T.querySelectorAll(".menu-tree-row")],v=c.indexOf(o)+(e.key==="ArrowDown"?1:-1);c[v]&&(e.preventDefault(),c[v].focus())}},qe=e=>{if(!e.target.matches(".me-spacer-size"))return;const o=ce(e.target.closest(".menu-tree-row")?.dataset.id);o&&(o.size=e.target.value,X())};T.addEventListener("click",We),T.addEventListener("keydown",Ke),T.addEventListener("change",qe),i(()=>{T.removeEventListener("click",We),T.removeEventListener("keydown",Ke),T.removeEventListener("change",qe)});const t=e=>n.find(e),we=t("#me-float-accent"),pe=t("#me-float-accent-hex"),He=()=>{pe.get(0).style.display=we.val()==="__custom__"?"":"none"},Ne=()=>we.val()==="__custom__"?pe.val():we.val(),W=t("#me-highlight-accent"),_=t("#me-highlight-accent-hex"),Oe=()=>{_.get(0).style.display=W.val()==="__custom__"?"":"none"},Ze=()=>W.val()==="__custom__"?_.val():W.val(),Se=()=>{const e=!!(t("#me-entrance-enabled").get(0)||{}).checked;t(".dm-entrance-field").each(function(){this.style.display=e?"":"none"});const o=t("#me-position").val()==="floating";t(".dm-float-field").each(function(){this.style.display=o?"":"none"}),o&&He();const a=t("#me-orientation").val()==="vertical";t(".dm-vertical-field").each(function(){this.style.display=a?"":"none"}),t(".dm-dock-rail-field").each(function(){this.style.display=a&&!o?"":"none"}),t(".dm-dock-bar-field").each(function(){this.style.display=!a&&!o?"":"none"});const c=!!(t("#me-dock-collapsible").get(0)||{}).checked,v=!!(t("#me-dock-autohide").get(0)||{}).checked;t(".dm-dock-collapsed-field").each(function(){this.style.display=c&&!v?"":"none"});const s=!!(t("#me-float-collapsible").get(0)||{}).checked;t(".dm-float-collapsed-field").each(function(){this.style.display=o&&s?"":"none"});const y=!a&&!P;t(".dm-look-field").each(function(){this.style.display=y?"":"none"});const d=y&&t("#me-look-dropdown").val()==="cards";t(".dm-look-cards-field").each(function(){this.style.display=d?"":"none"})},xe=(e,o,a,c)=>{const v=t(e).get(0);v&&(v.innerHTML="",v.add(new Option(c,"")),o.forEach(s=>v.add(new Option(a[s]||s,s))))};xe("#me-highlight-hover",dt,mt,"- theme default -"),xe("#me-highlight-active",ut,pt,"- theme default -");{const e=t("#me-entrance-animation").get(0);e&&(e.innerHTML="",nt.forEach(o=>e.add(new Option(Et[o]||o,o))))}const Re=t("#me-bind-slot"),Ae=t("#me-bind-projects"),g=t("#me-bind-pages");Re.html('<option value="">- not bound -</option>'+(ie||[]).map(e=>`<option value="${A(e.slot)}">${A(e.label||e.slot)} (${A(e.slot)})</option>`).join("")),Ae.html(le.map(e=>`<option value="${A(e.slug)}">${A(e.name||e.slug)}</option>`).join("")),t("#me-bind-page-pick").html('<option value="">- pick a page -</option>'+ve.map(e=>{const o=e.urlPath||e.url||"";return o?`<option value="${A(o)}">${A(e.title||o)} - ${A(o)}</option>`:""}).join(""));const p=()=>zt({slot:Re.val(),projects:Array.from(Ae.get(0).selectedOptions).map(e=>e.value),pagesText:g.val()});function F(){const e=p(),o=t("#me-bind-summary");if(!e){o.html('<span class="text-muted">Not bound - this menu renders only where it is mapped or placed by a shortcode.</span>');return}const a=[];e.projects.length&&a.push(`pages in ${e.projects.map(s=>`<code>${A(s)}</code>`).join(", ")}`),e.pages.length&&a.push(`pages matching ${e.pages.map(s=>`<code>${A(s)}</code>`).join(", ")}`);const c=e.pages.filter(s=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(s)),v=c.length?`<div class="text-danger" style="margin-top:.4rem">Not valid page patterns: ${c.map(s=>`<code>${A(s)}</code>`).join(", ")}</div>`:"";o.html(a.length?`Takes over the <code>${A(e.slot)}</code> slot on ${a.join(" and ")}.${v}`:`<span class="text-warning">Slot <code>${A(e.slot)}</code> selected but no project or page chosen - the binding will never match.</span>`)}const{getProjectFromHash:he}=await import("../lib/project-context.js");if(!w())return;const Ee=C?he():null;t("#me-project").html('<option value="">- none -</option>'+le.map(e=>`<option value="${A(e.slug)}">${A(e.name||e.slug)}</option>`).join(""));function fe(e){h=Ce(e.items),t("#me-variant").val(e.variant||""),t("#me-position").val(e.position||"");const o=t("#me-appearOnHover").get(0);o&&(o.checked=e.appearOnHover===!0),t("#me-float-anchor").val(e.float?.anchor||"TL"),t("#me-float-offsetX").val(e.float?.offsetX??""),t("#me-float-offsetY").val(e.float?.offsetY??""),t("#me-float-width").val(e.float?.width??""),t("#me-float-radius").val(e.float?.radius??""),t("#me-float-shadow").val(e.float?.shadow||""),t("#me-float-opacity").val(e.float?.opacity??""),t("#me-float-title").val(e.float?.title||"");for(const[s,y]of[["#me-float-collapsible",e.float?.collapsible],["#me-float-collapsed",e.float?.collapsed],["#me-float-movable",e.float?.movable],["#me-float-resizable",e.float?.resizable]]){const d=t(s).get(0);d&&(d.checked=y===!0)}const a=e.float?.accent||"";we.val(a.startsWith("#")?"__custom__":a),a.startsWith("#")&&pe.val(a),He(),t("#me-orientation").val(e.orientation||""),t("#me-align").val(e.align||""),t("#me-mobile-hamburger").get(0).checked=e.mobile?.hamburgerOnly===!0,t("#me-mobile-brand").get(0).checked=e.mobile?.hideBrand===!0,t("#me-look-bar").val(e.look?.bar||""),t("#me-look-brand").val(e.look?.brand||""),t("#me-look-strip").val(e.look?.strip||""),t("#me-look-dropdown").val(e.look?.dropdown||""),t("#me-look-tiles").val(e.look?.tiles||""),t("#me-look-morph").get(0).checked=e.look?.morph===!0,t("#me-look-shrink").get(0).checked=e.look?.shrink===!0,t("#me-side").val(e.side||"left"),t("#me-dock-width").val(e.dock?.width??"");for(const s of["collapsible","collapsed","autohide","hideOnScroll"]){const y=t(`#me-dock-${s}`).get(0);y&&(y.checked=e.dock?.[s]===!0)}t("#me-class").val(e.class||""),t("#me-highlight-hover").val(e.highlight?.hover||""),t("#me-highlight-active").val(e.highlight?.active||"");const c=e.highlight?.accent||"";W.val(c.startsWith("#")?"__custom__":c),c.startsWith("#")&&_.val(c),Oe();{const s=t("#me-entrance-enabled").get(0);s&&(s.checked=e.entrance?.enabled===!0)}t("#me-entrance-animation").val(nt.includes(e.entrance?.animation)?e.entrance.animation:nt[0]),t("#me-entrance-stagger").val(e.entrance?.stagger??""),t("#me-entrance-duration").val(e.entrance?.duration??""),t("#me-fontFamily").val(e.style?.fontFamily||""),t("#me-fontSize").val(e.style?.fontSize||""),t("#me-fontWeight").val(e.style?.fontWeight||""),t("#me-letterSpacing").val(e.style?.letterSpacing||""),t("#me-iconSize").val(e.style?.iconSize||""),Re.val(e.binding?.slot||"");const v=new Set(e.binding?.projects||[]);Array.from(Ae.get(0).options).forEach(s=>{s.selected=v.has(s.value)}),g.val((e.binding?.pages||[]).join(`
|
|
30
|
-
`)),t("#me-slug").val(e.slug||""),t("#me-name").val(e.name||""),t("#me-description").val(e.description||""),t("#me-project").val(e.meta?.project||
|
|
31
|
-
`).map(a=>a.trim()).filter(Boolean);o.includes(e)||o.push(e),
|
|
32
|
-
`)),
|
|
27
|
+
${Qe}
|
|
28
|
+
</div>`),y(p.id)}}y(null),n._disposeMenuHelp?.(),n.innerHTML=s.join("")||'<p class="mex-note">No items yet - Add item, or right-click an item for more.</p>',n._disposeMenuHelp=st(n),I.scan(n)}function b(n,u={},...l){const r=document.createElement(n);Object.assign(r,u);for(const s of l)s!=null&&r.append(s);return r}function w(n,u,l){const r=b("label",{className:"form-field"},b("span",{className:"form-label",textContent:n}),u),s=jt[n];if(s){const y=b("span");y.dataset.help=s+(l?" "+l:""),y.dataset.helpTitle=n,r.querySelector(".form-label").append(" ",y)}return l&&r.append(b("small",{className:"form-hint",textContent:l,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),r}function on(n,u){const l=[["","- Everyone -"],["public","Public - anyone, incl. logged-out"],...(u||[]).map(s=>[s.name,`${s.label||s.name} and above`]),...(u||[]).filter(s=>s.level!==0).map(s=>[`=${s.name}`,`${s.label||s.name} only`]),["private","Private - super-admin only"]];n&&!l.some(([s])=>s===n)&&l.push([n,`${n} (custom)`]);const r=K(l,n||"");return r.name="visibility",r}function K(n,u){const l=b("select",{className:"form-select"});return n.forEach(([r,s])=>l.add(new Option(s,r))),l.value=u??"",l}function je(n,u){const l=typeof u=="string"&&u.startsWith("#"),r=K([["","- none -"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],l?"__custom__":u||""),s=b("input",{type:"color",className:"form-input me-colour-hex",value:l?u:"#000000"});s.style.display=l?"":"none",r.addEventListener("change",()=>{s.style.display=r.value==="__custom__"?"":"none"});const y=w(n,r);return y.append(s),y._read=()=>r.value==="__custom__"?s.value:r.value,y}function an(n,u){const l=b("input",{className:"form-input me-icon",name:"icon",value:u||""}),r=b("span",{className:"me-icon-preview"});u&&r.setAttribute("data-icon",u);const s=b("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),y=T=>{T?r.setAttribute("data-icon",T):r.removeAttribute("data-icon"),Domma.icons.scan(r.parentNode||r)};return s.addEventListener("click",()=>$t(T=>{l.value=T,y(T)})),l.addEventListener("input",()=>y(l.value.trim())),w(n,b("div",{className:"me-icon-field"},l,s,r))}function te(...n){return b("div",{className:"me-field-grid"},...n)}async function sn(n,u,l,r,s,{cards:y=!0}={}){const T=n.text||n.tool?.label||"",p=m=>b("h4",{className:"me-form-section",textContent:m}),G=w("Label",b("input",{className:"form-input",name:"text",value:n.text||""}),"The text shown for this item."),$=b("input",{className:"form-input",name:"url",value:n.url||""}),V=w("URL",$,n.tool||n.locked?"A Tool's address is its identity, so it cannot be changed here.":"Where it links. Must start with /, #, https://, or mailto: . Leave blank for a parent that only groups sub-items.");(n.tool||n.locked)&&($.readOnly=!0);const _=an("Icon",n.icon||""),f=on(n.visibility||"",r),re=w("Visibility",f,n.locked?"Always in the sidebar for everyone who may use it, so it cannot be restricted here.":'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.');n.locked&&(f.disabled=!0);const ne=K([["","- no permission gate -"],...(u||[]).map(m=>[m.key,m.label||m.key])],n.permission||"");ne.name="permission";const ce=w("Permission",ne,n.tool||n.locked?"A Tool is guarded by its own permission, which always applies.":"Hide unless the viewer holds this specific permission. Independent of Visibility - both must pass.");(n.tool||n.locked)&&(ne.disabled=!0);const oe=w("Badge text",b("input",{className:"form-input",name:"badgeText",value:n.badge?.text||""})),de=K([["","- none -"],...(l||[]).map(m=>[m.slug,m.title||m.slug])],n.badge?.countFrom||"");de.name="badgeCount";const be=w("Badge count from",de,"Show a live count of this collection\u2019s entries as the badge."),ae=je("Badge colour",n.badge?.variant||""),ye=K([["link","Link"],["pill","Pill"]],n.pill?"pill":"link");ye.name="renderAs";const D=w("Render as",ye,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),$e=w("Pill style",K([["filled","Filled"],["outline","Outline"]],n.pill?.style||"filled"));$e.querySelector("select").name="pillStyle";const Le=je("Pill colour",n.pill?.variant||""),h=b("div",{className:"me-pill-wrap"},te($e,Le));h.style.display=n.pill?"":"none",ye.addEventListener("change",()=>{h.style.display=ye.value==="pill"?"":"none"});const j=je("Text colour",n.colour||""),ue=K([["","- use the menu default -"],...ut.map(m=>[m,mt[m]||m])],n.highlight?.hover||"");ue.name="hlHover";const Fe=w("Hover effect",ue,"Overrides the menu-wide hover effect for this item only."),Te=K([["","- use the menu default -"],...pt.map(m=>[m,ht[m]||m])],n.highlight?.active||"");Te.name="hlActive";const U=w("When selected",Te,"How this item is marked when it points at the page being viewed."),L=je("Highlight colour",n.highlight?.accent||""),ke=b("input",{className:"form-input",name:"class",value:n.class||"",placeholder:"nav-cta push-right",autocomplete:"off",spellcheck:!1}),N=w("CSS classes",ke,"Your own class names, separated by spaces, added to this item's <li> - target the link with .your-class > a. Style them in Custom CSS."),Y=b("small",{className:"text-danger",style:"display:none;margin-top:.25rem"});N.append(Y);const pe=b("textarea",{className:"form-input",name:"description",rows:2,maxLength:Tt,value:n.description||"",placeholder:"Answers every new lead in under a minute."}),me=w("Description",pe,"One short line under the label, shown when the menu's dropdowns are Cards."),_e=je("Tile colour",n.tile||""),He=b("input",{type:"checkbox",name:"chip",checked:!!n.chip,style:"width:auto;margin:0"}),Ie=b("label",{className:"form-field",style:"display:flex;gap:.5rem;align-items:center;font-weight:400"},He,b("span",{textContent:"Show as a small chip in the card's footer strip"})),we=b("span");we.dataset.helpTitle="Show as a small chip",we.dataset.help="Place this sub-item in the compact chip strip at the bottom of its parent Cards dropdown instead of the main entries. Its label and destination remain the same. The parent card can provide a Chip strip label. This applies to sub-items in Cards dropdowns, rather than top-level navbar links.",Ie.append(we);const F=n.parentId==null,C=n.panel||{},O=(m,X,J,A=80)=>b("input",{className:"form-input",name:m,value:X||"",placeholder:J,maxLength:A}),Me=w("Card heading",O("panelHeading",C.heading,"The crew - six agents, one job each"),"Small capitals over the entries."),ze=w("Link text",O("panelLinkText",C.link?.text,"Meet the crew",60)),Ue=w("Link URL",O("panelLinkUrl",C.link?.url,"/crew",500)),Ne=K([["","Automatic"],["1","1"],["2","2"],["3","3"],["4","4"]],C.columns?String(C.columns):"");Ne.name="panelColumns";const Ze=w("Columns",Ne),et=w("Chip strip label",O("panelChipsLabel",C.chipsLabel,"Add to your crew",60),'Words before the chips - tick "Show as a small chip" on the sub-items that belong there.'),he=K([["","- no side box -"],["dark","Dark"],["light","Light"],["accent","Theme colour"]],C.feature?C.feature.tone||"dark":"");he.name="featureTone";const We=w("Side box",he,"A box down the right of the card - a call to action or a short list."),Ke=w("Box small heading",O("featureEyebrow",C.feature?.eyebrow,"Free - 20 minutes",60)),qe=w("Box title",O("featureTitle",C.feature?.title,"See it live")),t=w("Box points",b("textarea",{className:"form-input",name:"featurePoints",rows:3,value:(C.feature?.points||[]).join(`
|
|
29
|
+
`),placeholder:"One per line, up to six"})),Se=w("Button text",O("featureBtnText",C.feature?.button?.text,"Book a demo",60)),Oe=w("Button URL",O("featureBtnUrl",C.feature?.button?.url,"/book",500)),fe=b("div",{},te(Ke,qe),t,te(Se,Oe));fe.style.display=he.value?"":"none",he.addEventListener("change",()=>{fe.style.display=he.value?"":"none"});const tt=y?[p("Card dropdown"),b("small",{className:"form-hint",style:"display:block;opacity:.7;margin-bottom:.4rem",textContent:"Used when the menu's Dropdown style is Cards (Behaviour tab)."}),me,te(_e,F?null:Ie),...F?[Me,te(ze,Ue),te(Ze,et),We,fe]:[]]:[],se=b("button",{className:"btn btn-primary",textContent:"Apply",title:"Apply to the item (Ctrl+Enter) - Save the menu to keep it"}),W=b("div",{className:"me-item-form"},p("Link"),G,V,_,p("Access"),te(re,ce),p("Badge"),te(oe,ae),be,p("Appearance"),D,h,j,p("Highlighting"),te(Fe,U),L,...tt,p("Styling"),N,se),B=E.slideover({title:T?`Edit ${De(T)}`:"Edit menu item",size:"lg",position:"right"});B.element.appendChild(W);const Ge=()=>JSON.stringify([...W.querySelectorAll("input, select, textarea")].map(m=>m.type==="checkbox"?m.checked:m.value)),Ve=Ge();let xe=!1,ge=!1;const Re=st(W,{position:"bottom"});Ft(B,Re);const Ee=B.close.bind(B);B.close=async m=>{if(!xe&&m!==!0&&Ge()!==Ve){if(ge)return;ge=!0;const X=await E.confirm("Discard the changes to this item?");if(ge=!1,!X)return}return xe=!0,Ee()},W.addEventListener("keydown",m=>{(m.ctrlKey||m.metaKey)&&m.key==="Enter"&&(m.preventDefault(),se.click())}),B.open();const Be=B.element.closest(".dm-slideover")||B.element.querySelector(".dm-slideover")||B.element;Be.style.setProperty("width",Qt+"px","important"),Be.style.setProperty("max-width","95vw","important"),Domma.icons.scan(W);const g=m=>W.querySelector(`[name="${m}"]`)?.value||"";se.addEventListener("click",()=>{const m=ft(g("class"));if(m.length){Y.textContent=m.join("; "),Y.style.display="block",ke.focus();return}n.class=At(g("class")),n.text=g("text"),n.icon=g("icon"),n.locked||(n.visibility=g("visibility")),!n.tool&&!n.locked?(n.url=g("url"),n.permission=g("permission")):n.touched=!0;const X=g("badgeText"),J=g("badgeCount"),A=ae._read();if(n.badge=X||J?{...X&&{text:X},...J&&{countFrom:J},...A&&{variant:A}}:null,g("renderAs")==="pill"){const Q=Le._read();n.pill={style:g("pillStyle")||"filled",...Q&&{variant:Q}}}else n.pill=null;n.colour=j._read();const le=g("hlHover"),Ae=g("hlActive"),Ce=L._read();if(n.highlight=le||Ae||Ce?{...le&&{hover:le},...Ae&&{active:Ae},...Ce&&{accent:Ce}}:null,y&&(n.description=g("description").trim(),n.tile=_e._read(),n.chip=!F&&He.checked,F)){const Q=g("featureTone");n.panel=Lt({heading:g("panelHeading"),link:{text:g("panelLinkText"),url:g("panelLinkUrl")},columns:g("panelColumns"),chipsLabel:g("panelChipsLabel"),feature:Q?{tone:Q,eyebrow:g("featureEyebrow"),title:g("featureTitle"),points:g("featurePoints").split(`
|
|
30
|
+
`),button:{text:g("featureBtnText"),url:g("featureBtnUrl")}}:null})}xe=!0,B.close(!0),s()})}let q=null,yt=!1;function ln(){yt||!window.R?.use||(yt=!0,R.use((n,u,l)=>{const r=u?.path||"";return!(en.test(r)&&(n?.path||"")!==r)||!q?.dirty()?l():E.confirm(`You have unsaved changes to the menu "${De(q.name())}". Leave anyway? They are kept as a draft for your next visit.`).then(y=>{y?l():history.replaceState(null,"",`#${r}`)})}),M.subscribe("router:afterChange",({to:n}={})=>{if(q&&(n?.path||"")!==q.path){const u=q;q=null,u.dispose()}}))}function kt(n,u){const l=n.querySelector(".dm-plugin-banner-name"),r=l?.querySelector(".dm-plugin-banner-sep");if(!(!l||!r)){for(;r.nextSibling;)r.nextSibling.remove();l.appendChild(document.createTextNode(u))}}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(n){if(ln(),q){const e=q;q=null,e.dispose()}const u=[],l=e=>(u.push(e),e);let r=!1;const s=n.get(0).querySelector(".mex")||n.get(0),y=()=>!r&&s.isConnected,T=location.hash.replace(/^#/,""),p=location.hash.match(/^#\/menus\/edit\/([^?#]+)/),G=p?decodeURIComponent(p[1]):null,$=!G,V=`menu-editor.draft.${G||"new"}`,_=E.loader(n.get(0),{type:"dots"});let f,re,ne,ce,oe,de,be;try{if($)f={slug:"",name:"",description:"",items:[]};else try{f=await P.menus.get(G)}catch(e){E.toast(`Failed to load menu: ${e.message||e}`,{type:"error"}),location.hash="#/menus";return}[re,ne,ce,oe,de,be]=await Promise.all([P.projects.list().catch(()=>[]),H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),P.collections.list().catch(()=>[]),P.get("/collections/roles/entries?limit=100").catch(()=>null),P.menuLocations.registry().catch(()=>[]),P.pages.list().catch(()=>[])])}finally{_.destroy()}if(!s.isConnected)return;const ye=(Array.isArray(oe)?oe:oe?.entries||[]).map(e=>({name:e.data?.name,label:e.data?.label,level:e.data?.level??99})).filter(e=>e.name).sort((e,o)=>o.level-e.level),D=!$&&G===Zt;let $e=[];if(D&&($e=await H.get("/api/sidebar/registered-items").catch(()=>(E.toast("Could not load the Tools - they are not shown, and saving keeps their places as they are.",{type:"warning",duration:8e3}),[])),!s.isConnected))return;const Le=e=>gt(D?It(Ht(e||[],$e,{forEditor:!0}),{leaveInHiddenFolders:!0}):e);let h=Le(f.items);const j=M.observable(!1),ue=M.observable(!1),Fe=M.observable(""),Te=M.observable(0);let U="";const L=s.querySelector("#me-items-list");l(()=>L._disposeMenuHelp?.());function ke(){nn(L,h,D),Te.value=h.filter(e=>!ve(e)).length}const N=()=>{ke(),Z()},Y=()=>D?bt(h):new Set,pe=e=>h.find(o=>o.id===e);function me(e){requestAnimationFrame(()=>L.querySelector(`.menu-tree-row[data-id="${CSS.escape(e)}"]`)?.focus())}function _e(e){const o=v=>h.find(i=>!i.type&&!i.url&&!i.tool&&(i.key||"").toLowerCase()===v)||h.find(i=>!i.type&&!i.url&&!i.tool&&!i.key&&(i.text||"").toLowerCase()===v);let a=o(String(e.tool.folder||"").toLowerCase())||o(Pt);const c=String(e.tool.group||"").trim();if(a&&c){const v=Ot(a.key||Rt(a.text),c),i=d=>d!==e&&!d.type&&!d.url&&!d.tool;let k=h.find(d=>i(d)&&(d.key||"").toLowerCase()===v)||h.find(d=>i(d)&&d.parentId===a.id&&(!d.key||d.key.toLowerCase()===v)&&(d.text||"").toLowerCase()===c.toLowerCase());k||([k]=gt([{text:c,key:v,icon:Bt,synthesised:!0}],a.id),h.push(k)),a=k}e.parentId=a?a.id:null,h.splice(h.indexOf(e),1),h.push(e),Object.assign(e,{text:e.tool.defaults.text,icon:e.tool.defaults.icon,visibility:"",hidden:!1,badge:null,pill:null,colour:"",highlight:null,touched:!1}),e.tool={...e.tool,persisted:!1}}const He=e=>!Y().has(e.id)&&!(e.tool&&!e.tool.orphan);async function Ie(e){const o=pe(e);if(!o||!He(o))return;const a=d=>{let ee=d;for(;ee;){if(ee.id===e)return!0;ee=h.find(z=>z.id===ee.parentId)}return!1},c=h.filter(d=>d.id!==e&&a(d));if(c.length&&!await E.confirm(`Remove "${De(o.text||o.type||"this item")}" and the ${c.length} item${c.length===1?"":"s"} beneath it?`))return;const v=Y(),i=c.filter(d=>d.tool),k=c.filter(d=>!d.tool&&v.has(d.id));h=h.filter(d=>!a(d)||i.includes(d)||k.includes(d));for(const d of k)d.parentId=o.parentId??null;for(const d of i)d.tool.orphan?d.parentId=o.parentId??null:_e(d);N()}function we(e){const o=pe(e);!o||ve(o)||Y().has(e)||(o.hidden=!o.hidden,o.touched=!0,N())}function F(e){const o=pe(e);!o||ve(o)||(o.locked=D&&Y().has(o.id),sn(o,ne.resources||[],ce,ye,()=>{N(),me(e)},{cards:!D}))}function C(e){const o={id:Xe(),parentId:null,...e==="separator"||e==="theme-switcher"?{type:e}:e==="spacer"?{type:e,size:"md"}:{text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}};h.push(o),N(),ve(o)?me(o.id):F(o.id)}const O=e=>o=>{e(o)&&(N(),me(o))},Me=O(e=>vt(h,e,-1)),ze=O(e=>vt(h,e,1)),Ue=O(e=>Kt(h,e)),Ne=O(e=>Yt(h,e));function Ze(e){const o=pe(e);if(!o)return[];const a=ve(o),c=Y().has(e),v=Xt(h,e),i=v.findIndex(z=>z.id===e),k=o.tool,d=k&&(k.persisted||o.touched),ee=z=>()=>{const Pe=qt(h,e,z,Xe());N(),Pe&&!ve(Pe)?F(Pe.id):Pe&&me(Pe.id)};return[{header:a?{separator:"Separator",spacer:"Spacer","theme-switcher":"Theme switcher"}[o.type]:o.text||k?.label||"(untitled)"},...a?[]:[{label:"Edit\u2026",icon:"edit",action:()=>F(e)}],{type:"divider"},{label:"Add item below",icon:"plus",action:ee("item")},{label:"Add sub-item",icon:"corner-down-right",disabled:a,action:()=>{const z=zt(h,e,Xe());N(),z&&F(z.id)}},{label:"Add separator below",icon:"minus",action:ee("separator")},{label:"Add spacer below",icon:"move",action:ee("spacer")},...D?[]:[{label:"Add theme switcher below",icon:"palette",action:ee("theme-switcher")}],{type:"divider"},{label:"Move up",icon:"arrow-up",shortcut:"Alt+\u2191",disabled:i<=0,action:()=>Me(e)},{label:"Move down",icon:"arrow-down",shortcut:"Alt+\u2193",disabled:i<0||i>=v.length-1,action:()=>ze(e)},{label:"Indent",icon:"chevron-right",shortcut:"Alt+\u2192",disabled:i<=0||ve(v[i-1]),action:()=>Ue(e)},{label:"Outdent",icon:"chevron-left",shortcut:"Alt+\u2190",disabled:o.parentId==null,action:()=>Ne(e)},{type:"divider"},...a?[]:[{label:o.hidden?"Show":"Hide",icon:o.hidden?"eye":"eye-off",disabled:c,action:()=>we(e)}],{label:"Duplicate",icon:"copy",disabled:!!k,action:()=>{const z=Wt(h,e,Xe);N(),z&&me(z.id)}},...k&&!k.orphan&&d?[{label:"Back to its default place",icon:"rotate-ccw",action:()=>{_e(o),N()}}]:[],{label:c?"Remove - always in the sidebar":k&&!k.orphan?"Remove - a Tool is hidden, not removed":"Remove\u2026",icon:"trash",danger:!0,disabled:!He(o),action:()=>Ie(e)}]}const et=E.contextMenu(L,{cursor:"glyph",match:".menu-tree-row",inherit:!1,density:"compact",items:e=>Ze(e.dataset.id)});l(()=>et?.destroy?.());const he=_t(L,()=>h,N,{items:".menu-tree-row",handle:".menu-tree-grip"});l(()=>he?.destroy());const We=e=>{const o=e.target.closest(".menu-tree-row");if(!o)return;const a=o.dataset.id;if(e.target.closest(".mex-kebab")){lt(e.target.closest(".mex-kebab"));return}if(e.target.closest(".me-hidden")){we(a);return}if(e.target.closest(".me-edit")){F(a);return}e.target.closest("select, .menu-tree-grip, button")||F(a)},Ke=e=>{const o=e.target.closest(".menu-tree-row");if(!o||e.target!==o){o&&e.key==="F10"&&e.shiftKey&&(e.preventDefault(),lt(o));return}const a=o.dataset.id;if(e.key==="F10"&&e.shiftKey)e.preventDefault(),lt(o);else if(e.key==="Enter")e.preventDefault(),F(a);else if(e.key==="Delete")e.preventDefault(),Ie(a);else if(e.altKey&&e.key==="ArrowUp")e.preventDefault(),Me(a);else if(e.altKey&&e.key==="ArrowDown")e.preventDefault(),ze(a);else if(e.altKey&&e.key==="ArrowRight")e.preventDefault(),Ue(a);else if(e.altKey&&e.key==="ArrowLeft")e.preventDefault(),Ne(a);else if(e.key==="ArrowDown"||e.key==="ArrowUp"){const c=[...L.querySelectorAll(".menu-tree-row")],v=c.indexOf(o)+(e.key==="ArrowDown"?1:-1);c[v]&&(e.preventDefault(),c[v].focus())}},qe=e=>{if(!e.target.matches(".me-spacer-size"))return;const o=pe(e.target.closest(".menu-tree-row")?.dataset.id);o&&(o.size=e.target.value,Z())};L.addEventListener("click",We),L.addEventListener("keydown",Ke),L.addEventListener("change",qe),l(()=>{L.removeEventListener("click",We),L.removeEventListener("keydown",Ke),L.removeEventListener("change",qe)});const t=e=>n.find(e),Se=t("#me-float-accent"),Oe=t("#me-float-accent-hex"),fe=()=>{Oe.get(0).style.display=Se.val()==="__custom__"?"":"none"},tt=()=>Se.val()==="__custom__"?Oe.val():Se.val(),se=t("#me-highlight-accent"),W=t("#me-highlight-accent-hex"),B=()=>{W.get(0).style.display=se.val()==="__custom__"?"":"none"},Ge=()=>se.val()==="__custom__"?W.val():se.val(),Ve=()=>{const e=!!(t("#me-entrance-enabled").get(0)||{}).checked;t(".dm-entrance-field").each(function(){this.style.display=e?"":"none"});const o=t("#me-position").val()==="floating";t(".dm-float-field").each(function(){this.style.display=o?"":"none"}),o&&fe();const a=t("#me-orientation").val()==="vertical";t(".dm-vertical-field").each(function(){this.style.display=a?"":"none"}),t(".dm-dock-rail-field").each(function(){this.style.display=a&&!o?"":"none"}),t(".dm-dock-bar-field").each(function(){this.style.display=!a&&!o?"":"none"});const c=!!(t("#me-dock-collapsible").get(0)||{}).checked,v=!!(t("#me-dock-autohide").get(0)||{}).checked;t(".dm-dock-collapsed-field").each(function(){this.style.display=c&&!v?"":"none"});const i=!!(t("#me-float-collapsible").get(0)||{}).checked;t(".dm-float-collapsed-field").each(function(){this.style.display=o&&i?"":"none"});const k=!a&&!D;t(".dm-look-field").each(function(){this.style.display=k?"":"none"});const d=k&&t("#me-look-dropdown").val()==="cards";t(".dm-look-cards-field").each(function(){this.style.display=d?"":"none"})},xe=(e,o,a,c)=>{const v=t(e).get(0);v&&(v.innerHTML="",v.add(new Option(c,"")),o.forEach(i=>v.add(new Option(a[i]||i,i))))};xe("#me-highlight-hover",ut,mt,"- theme default -"),xe("#me-highlight-active",pt,ht,"- theme default -");{const e=t("#me-entrance-animation").get(0);e&&(e.innerHTML="",ot.forEach(o=>e.add(new Option(Ct[o]||o,o))))}const ge=t("#me-bind-slot"),Re=t("#me-bind-projects"),Ee=t("#me-bind-pages");ge.html('<option value="">- not bound -</option>'+(de||[]).map(e=>`<option value="${x(e.slot)}">${x(e.label||e.slot)} (${x(e.slot)})</option>`).join("")),Re.html(re.map(e=>`<option value="${x(e.slug)}">${x(e.name||e.slug)}</option>`).join("")),t("#me-bind-page-pick").html('<option value="">- pick a page -</option>'+be.map(e=>{const o=e.urlPath||e.url||"";return o?`<option value="${x(o)}">${x(e.title||o)} - ${x(o)}</option>`:""}).join(""));const Be=()=>Ut({slot:ge.val(),projects:Array.from(Re.get(0).selectedOptions).map(e=>e.value),pagesText:Ee.val()});function g(){const e=Be(),o=t("#me-bind-summary");if(!e){o.html('<span class="text-muted">Not bound - this menu renders only where it is mapped or placed by a shortcode.</span>');return}const a=[];e.projects.length&&a.push(`pages in ${e.projects.map(i=>`<code>${x(i)}</code>`).join(", ")}`),e.pages.length&&a.push(`pages matching ${e.pages.map(i=>`<code>${x(i)}</code>`).join(", ")}`);const c=e.pages.filter(i=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(i)),v=c.length?`<div class="text-danger" style="margin-top:.4rem">Not valid page patterns: ${c.map(i=>`<code>${x(i)}</code>`).join(", ")}</div>`:"";o.html(a.length?`Takes over the <code>${x(e.slot)}</code> slot on ${a.join(" and ")}.${v}`:`<span class="text-warning">Slot <code>${x(e.slot)}</code> selected but no project or page chosen - the binding will never match.</span>`)}const{getProjectFromHash:m}=await import("../lib/project-context.js");if(!y())return;const X=$?m():null;t("#me-project").html('<option value="">- none -</option>'+re.map(e=>`<option value="${x(e.slug)}">${x(e.name||e.slug)}</option>`).join(""));function J(e){h=Le(e.items),t("#me-variant").val(e.variant||""),t("#me-position").val(e.position||"");const o=t("#me-appearOnHover").get(0);o&&(o.checked=e.appearOnHover===!0),t("#me-float-anchor").val(e.float?.anchor||"TL"),t("#me-float-offsetX").val(e.float?.offsetX??""),t("#me-float-offsetY").val(e.float?.offsetY??""),t("#me-float-width").val(e.float?.width??""),t("#me-float-radius").val(e.float?.radius??""),t("#me-float-shadow").val(e.float?.shadow||""),t("#me-float-opacity").val(e.float?.opacity??""),t("#me-float-title").val(e.float?.title||"");for(const[i,k]of[["#me-float-collapsible",e.float?.collapsible],["#me-float-collapsed",e.float?.collapsed],["#me-float-movable",e.float?.movable],["#me-float-resizable",e.float?.resizable]]){const d=t(i).get(0);d&&(d.checked=k===!0)}const a=e.float?.accent||"";Se.val(a.startsWith("#")?"__custom__":a),a.startsWith("#")&&Oe.val(a),fe(),t("#me-orientation").val(e.orientation||""),t("#me-align").val(e.align||""),t("#me-mobile-hamburger").get(0).checked=e.mobile?.hamburgerOnly===!0,t("#me-mobile-brand").get(0).checked=e.mobile?.hideBrand===!0,t("#me-look-bar").val(e.look?.bar||""),t("#me-look-brand").val(e.look?.brand||""),t("#me-look-strip").val(e.look?.strip||""),t("#me-look-dropdown").val(e.look?.dropdown||""),t("#me-look-tiles").val(e.look?.tiles||""),t("#me-look-morph").get(0).checked=e.look?.morph===!0,t("#me-look-shrink").get(0).checked=e.look?.shrink===!0,t("#me-side").val(e.side||"left"),t("#me-dock-width").val(e.dock?.width??"");for(const i of["collapsible","collapsed","autohide","hideOnScroll"]){const k=t(`#me-dock-${i}`).get(0);k&&(k.checked=e.dock?.[i]===!0)}t("#me-class").val(e.class||""),t("#me-highlight-hover").val(e.highlight?.hover||""),t("#me-highlight-active").val(e.highlight?.active||"");const c=e.highlight?.accent||"";se.val(c.startsWith("#")?"__custom__":c),c.startsWith("#")&&W.val(c),B();{const i=t("#me-entrance-enabled").get(0);i&&(i.checked=e.entrance?.enabled===!0)}t("#me-entrance-animation").val(ot.includes(e.entrance?.animation)?e.entrance.animation:ot[0]),t("#me-entrance-stagger").val(e.entrance?.stagger??""),t("#me-entrance-duration").val(e.entrance?.duration??""),t("#me-fontFamily").val(e.style?.fontFamily||""),t("#me-fontSize").val(e.style?.fontSize||""),t("#me-fontWeight").val(e.style?.fontWeight||""),t("#me-letterSpacing").val(e.style?.letterSpacing||""),t("#me-iconSize").val(e.style?.iconSize||""),ge.val(e.binding?.slot||"");const v=new Set(e.binding?.projects||[]);Array.from(Re.get(0).options).forEach(i=>{i.selected=v.has(i.value)}),Ee.val((e.binding?.pages||[]).join(`
|
|
31
|
+
`)),t("#me-slug").val(e.slug||""),t("#me-name").val(e.name||""),t("#me-description").val(e.description||""),t("#me-project").val(e.meta?.project||X||""),Ve(),g(),ke()}const A=e=>!!(t(e).get(0)||{}).checked;function le(){const e={variant:t("#me-variant").val(),position:t("#me-position").val(),appearOnHover:A("#me-appearOnHover"),floatAnchor:t("#me-float-anchor").val(),floatOffsetX:t("#me-float-offsetX").val(),floatOffsetY:t("#me-float-offsetY").val(),floatWidth:t("#me-float-width").val(),floatRadius:t("#me-float-radius").val(),floatShadow:t("#me-float-shadow").val(),floatAccent:tt(),floatOpacity:t("#me-float-opacity").val(),floatTitle:t("#me-float-title").val(),floatCollapsible:A("#me-float-collapsible"),floatCollapsed:A("#me-float-collapsed"),floatMovable:A("#me-float-movable"),floatResizable:A("#me-float-resizable"),highlightHover:t("#me-highlight-hover").val(),highlightActive:t("#me-highlight-active").val(),highlightAccent:Ge(),class:t("#me-class").val(),entranceEnabled:A("#me-entrance-enabled"),entranceAnimation:t("#me-entrance-animation").val(),entranceStagger:t("#me-entrance-stagger").val(),entranceDuration:t("#me-entrance-duration").val(),orientation:t("#me-orientation").val(),align:t("#me-align").val(),side:t("#me-side").val(),fontFamily:t("#me-fontFamily").val(),fontSize:t("#me-fontSize").val(),fontWeight:t("#me-fontWeight").val(),letterSpacing:t("#me-letterSpacing").val(),iconSize:t("#me-iconSize").val(),dockWidth:t("#me-dock-width").val(),dockCollapsible:A("#me-dock-collapsible"),dockCollapsed:A("#me-dock-collapsed"),dockAutohide:A("#me-dock-autohide"),mobileHamburgerOnly:A("#me-mobile-hamburger"),mobileHideBrand:A("#me-mobile-brand"),lookBar:t("#me-look-bar").val(),lookDropdown:t("#me-look-dropdown").val(),lookBrand:t("#me-look-brand").val(),lookStrip:t("#me-look-strip").val(),lookTiles:t("#me-look-tiles").val(),lookMorph:A("#me-look-morph"),lookShrink:A("#me-look-shrink"),dockHideOnScroll:A("#me-dock-hideOnScroll")},o={slug:t("#me-slug").val(),name:t("#me-name").val(),description:t("#me-description").val(),project:t("#me-project").val()};return Gt(e,o,Be(),Dt(h),f.meta||{})}const Ae=e=>[...Vt(e),...ft(e.class).map(o=>`CSS classes: ${o} (Behaviour tab).`)];let Ce=!1,Q=null;function Z(){if(!U)return;const e=le();Q=e,j.value=Je(e)!==U,Fe.value=j.peek()||Ce?Ae(e).join(" "):""}const Ye=e=>{e.target.closest("#me-items-list")||(e.target.matches("#me-position, #me-orientation, #me-look-dropdown, #me-float-collapsible, #me-dock-collapsible, #me-dock-autohide, #me-entrance-enabled")&&Ve(),e.target.matches("#me-float-accent")&&fe(),e.target.matches("#me-highlight-accent")&&B(),e.target.closest("#me-binding")&&g(),Z())};s.addEventListener("input",Ye),s.addEventListener("change",Ye),l(()=>{s.removeEventListener("input",Ye),s.removeEventListener("change",Ye)}),t("#me-bind-page-add").on("click",()=>{const e=t("#me-bind-page-pick").val();if(!e)return;const o=Ee.val().split(`
|
|
32
|
+
`).map(a=>a.trim()).filter(Boolean);o.includes(e)||o.push(e),Ee.val(o.join(`
|
|
33
|
+
`)),g(),Z()}),J(f),U=Je(le()),Z();const it=()=>{try{S.remove?S.remove(V):S.set(V,null)}catch{}};async function nt(){if(ue.peek())return;const e=le(),o=Ae(e);if(o.length){Ce=!0,Z(),E.toast(o.join(" "),{type:"warning",duration:7e3});return}ue.value=!0;let a=null;try{if($)await P.menus.create(e);else if(e.slug!==f.slug){await P.menus.create(e);const c=await P.menuLocations.get();let v=!1;for(const[i,k]of Object.entries(c))k===f.slug&&(c[i]=e.slug,v=!0);v&&await P.menuLocations.save(c),await P.menus.remove(f.slug)}else{const c=await wt(e);if(!c)return;a=c.revision}if(U=Je(e),j.value=!1,it(),Mt("#/menus"),E.toast("Saved.",{type:"success"}),$||e.slug!==f.slug)location.hash="#/menus/edit/"+encodeURIComponent(e.slug);else{f={...f,...e,revision:a};for(const c of h)c.tool&&c.touched&&(c.tool={...c.tool,persisted:!0});ke(),U=Je(le()),Z(),kt(n.get(0),e.name||e.slug)}if(D)try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}}catch(c){E.toast(`Save failed: ${c.message||c}`,{type:"error"})}finally{ue.value=!1}}async function wt(e){try{return await P.menus.update(f.slug,{...e,revision:f.revision})}catch(a){if(a.status!==409)throw a}if(await E.confirm(`The menu "${De(f.name||f.slug)}" was changed somewhere else after you opened it - in another tab, by someone else, or on the server. Saving now would undo those changes.<br><br><strong>OK</strong> saves yours over them. <strong>Cancel</strong> loads the latest version, and offers your changes back as a draft.`))return P.menus.update(f.slug,e);try{S.set(V,{menu:e,base:U,at:new Date().toISOString()})}catch{}return j.value=!1,location.reload(),null}async function St(){!j.peek()||!await E.confirm("Throw away the changes you have not saved?")||(J(f),Z())}E.tabs(t("#me-tabs").get(0));const xt=M.applyBindings({dirty:j,saving:ue,problems:Fe,itemCount:Te},s,{methods:{save:()=>nt(),discard:()=>St(),addItem:()=>C("item"),addSeparator:()=>C("separator"),addSpacer:()=>C("spacer"),addSwitcher:()=>C("theme-switcher")}});l(()=>xt.dispose()),l(st(s,{position:"bottom"})),D&&t("#me-add-switcher").hide(),kt(n.get(0),$?"New menu":f.name||f.slug),at(n,{id:"menu-editor-back",icon:"arrow-left",title:"All menus",onClick:()=>{location.hash="#/menus"}}),at(n,{id:"menu-editor-locations",icon:"map-pin",title:"Menu locations",onClick:()=>{location.hash="#/menu-locations"}}),at(n,{id:"menu-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>nt()});const rt=e=>{if(!(!(e.ctrlKey||e.metaKey)||e.key!=="s")){if(e.preventDefault(),e.stopImmediatePropagation(),document.querySelector(".me-item-form")?.offsetParent){E.toast("Apply the item first (Ctrl+Enter), then Save.",{type:"info"});return}nt()}};document.addEventListener("keydown",rt,!0),l(()=>document.removeEventListener("keydown",rt,!0));const ct=()=>{if(!(!j.peek()||!Q))try{S.set(V,{menu:Q,base:U,at:new Date().toISOString()})}catch{}},dt=e=>{j.peek()&&(ct(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",dt),l(()=>window.removeEventListener("beforeunload",dt)),q={path:T,dirty:()=>j.peek(),name:()=>t("#me-name").val()||f.name||f.slug||"new menu",dispose(){r=!0,ct(),u.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(s);let ie=null;try{ie=S.get(V)}catch{}if(ie?.menu){it();const e=ie.base&&ie.base!==U,o=ie.at?` (${Jt(ie.at)})`:"",a=$?"the new menu you started":`the menu "${De(f.name||f.slug)}"`,c=e?`You left unsaved changes to ${a}${o}, but the saved menu has changed since. Restore your version anyway?`:`You left unsaved changes to ${a}${o}. Restore them?`;await E.confirm(c)&&y()&&(J({...ie.menu,meta:{...f.meta||{},...ie.menu.meta||{}}}),Z())}}};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as y}from"../api.js";import{addBannerAction as N}from"../lib/plugin-chrome.js";import{helpPopovers as ee}from"../lib/help-popover.js";import{openMenuAt as V,sidebarCount as te}from"../lib/tool-kit.js";import{depthOfItems as ne,locationRows as ae,locationsPayload as se,locationsSnapshot as w}from"../lib/menu-editor-arrange.js";import{fmtDateTime as oe}from"../lib/dates.js";const K=/^\/menu-locations\/?(\?.*)?$/,h="menu-locations.draft",le=`
|
|
2
2
|
<div class="mlx-row" data-slot="{{slot}}" tabindex="0" data-bind-class="(changed ? 'is-changed ' : '') + (tooDeep || missing ? 'is-bad' : '')">
|
|
3
3
|
<span class="mlx-main">
|
|
4
4
|
<strong>{{label}}</strong>
|
|
@@ -12,5 +12,6 @@ import{api as g}from"../api.js";import{addBannerAction as N}from"../lib/plugin-c
|
|
|
12
12
|
<span class="mlx-chip is-changed" data-bind-hidden="!changed">Changed</span>
|
|
13
13
|
</span>
|
|
14
14
|
<select class="form-input mlx-select" aria-label="Menu for {{label}}"></select>
|
|
15
|
+
<span data-help-title="Menu for this location" data-help="Choose the menu shown in this slot across the site, then Save to apply the mapping. None clears the mapping; the slot description says what happens when empty. A menu with its own page or project binding can override this default on matching pages. This changes placement only, not the menu items. Edit the selected menu to change its links or appearance."></span>
|
|
15
16
|
<button type="button" class="mlx-kebab" aria-label="Actions for {{label}}" title="Actions">\u22EE</button>
|
|
16
|
-
</div>`,ie=o=>String(o??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");let u=null,W=!1;function re(){W||!window.R?.use||(W=!0,R.use((o,p,s)=>!(K.test(p?.path||"")&&!K.test(o?.path||""))||!u?.dirty()?s():E.confirm("You have unsaved menu locations. Leave anyway? They are kept as a draft for your next visit.").then(d=>{d?s():history.replaceState(null,"","#/menu-locations")})),M.subscribe("router:afterChange",({to:o}={})=>{if(u&&!K.test(o?.path||"")){const p=u;u=null,p.dispose()}}))}export const menuLocationsView={templateUrl:"/admin/js/templates/menu-locations.html",async onMount(o){if(re(),u){const e=u;u=null,e.dispose()}const p=[],s=e=>(p.push(e),e);let k=!1;const d=o.get(0).querySelector(".mlx")||o.get(0),
|
|
17
|
+
</div>`,ie=o=>String(o??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");let u=null,W=!1;function re(){W||!window.R?.use||(W=!0,R.use((o,p,s)=>!(K.test(p?.path||"")&&!K.test(o?.path||""))||!u?.dirty()?s():E.confirm("You have unsaved menu locations. Leave anyway? They are kept as a draft for your next visit.").then(d=>{d?s():history.replaceState(null,"","#/menu-locations")})),M.subscribe("router:afterChange",({to:o}={})=>{if(u&&!K.test(o?.path||"")){const p=u;u=null,p.dispose()}}))}export const menuLocationsView={templateUrl:"/admin/js/templates/menu-locations.html",async onMount(o){if(re(),u){const e=u;u=null,e.dispose()}const p=[],s=e=>(p.push(e),e);let k=!1;const d=o.get(0).querySelector(".mlx")||o.get(0),v=()=>!k&&d.isConnected,x=M.observable([]),C=M.observable([]),O=M.observable({}),l=M.observable({}),a=M.observable({}),L=M.observable(!0),A=M.observable(""),b=M.observable(!1),f=M.computed(()=>w(a.value)!==w(l.value)),g=M.observable([]);s(M.effect(()=>{g.value=ae(x.value,a.value,l.value,C.value,O.value)}));const H=M.computed(()=>{const e=g.value;return`${e.filter(n=>!n.empty).length} of ${e.length} slot${e.length===1?"":"s"} have a menu`});async function _(){try{const[e,t,n]=await Promise.all([y.menuLocations.registry(),y.menuLocations.get().catch(()=>({})),y.menus.list().catch(()=>[])]),r=await Promise.all((n||[]).map(c=>y.menus.get(c.slug).catch(()=>null)));if(!v())return;x.value=Array.isArray(e)?e:[],C.value=n||[],O.value=Object.fromEntries(r.filter(Boolean).map(c=>[c.slug,ne(c.items||[])])),l.value={...t||{}},a.value={...t||{}},A.value=""}catch(e){A.value=`Could not load the locations: ${e.message||e}`}finally{L.value=!1}}const $=(e,t)=>{a.value={...a.peek(),[e]:t||""}};async function D(){if(!(b.peek()||L.peek())){b.value=!0;try{const e=se(a.peek(),x.peek());if(await y.menuLocations.save(e),!v())return;l.value={...e},a.value={...e};try{S.remove?S.remove(h):S.set(h,null)}catch{}te("#/menus"),E.toast("Locations saved.",{type:"success"})}catch(e){E.toast(`Save failed: ${e.message||e}`,{type:"error"})}finally{b.value=!1}}}async function z(){!f.peek()||!await E.confirm("Throw away the changes you have not saved?")||(a.value={...l.peek()})}const i=d.querySelector(".mlx-list");i.innerHTML=le;const G=M.applyBindings({rows:g,loading:L,loadError:A,saving:b,dirty:f,summary:H},d,{methods:{save:()=>D(),discard:()=>z()}});s(()=>G.dispose()),s(ee(d,{position:"bottom"}));const J=()=>{const e=[["","- none -"],...C.peek().map(t=>[t.slug,t.name||t.slug])];for(const t of i.querySelectorAll(".mlx-select")){const n=t.closest(".mlx-row")?.dataset.slot;if(!t.options.length)for(const[c,Z]of e)t.add(new Option(Z,c));const r=a.peek()[n]||"";r&&![...t.options].some(c=>c.value===r)&&t.add(new Option(`${r} (missing)`,r)),t.value!==r&&(t.value=r)}};s(M.effect(()=>{g.value,requestAnimationFrame(()=>{v()&&J()})}));const P=e=>{e.target.matches(".mlx-select")&&$(e.target.closest(".mlx-row").dataset.slot,e.target.value)},T=e=>{const t=a.peek()[e];t&&(location.hash=`#/menus/edit/${encodeURIComponent(t)}`)},q=(e,t)=>navigator.clipboard?.writeText(e).then(()=>E.toast(`${t} copied.`,{type:"success"}),()=>E.toast(e,{type:"info",duration:8e3})),Q=e=>{const t=g.peek().find(n=>n.slot===e);return t?[{header:t.label},{label:t.menu?`Edit "${t.menuName}"`:"Edit its menu",icon:"edit",disabled:!t.menu||t.missing,action:()=>T(e)},{label:"Clear this slot",icon:"x-circle",disabled:!t.menu,action:()=>$(e,"")},{label:"Undo the change",icon:"rotate-ccw",disabled:!t.changed,action:()=>$(e,l.peek()[e]||"")},{type:"divider"},{label:"Copy slot name",icon:"copy",action:()=>q(e,"Slot name")},{label:"Copy [menu location] shortcode",icon:"code",action:()=>q(`[menu location="${e}" /]`,"Shortcode")}]:[]},X=E.contextMenu(i,{cursor:"glyph",match:".mlx-row",inherit:!1,density:"compact",items:e=>Q(e.dataset.slot)});s(()=>X?.destroy?.());const U=e=>{const t=e.target.closest(".mlx-row");if(!t)return;const n=e.target.closest(".mlx-kebab");if(n){V(n);return}e.target.closest("select")||T(t.dataset.slot)},Y=e=>{const t=e.target.closest(".mlx-row");t&&(e.key==="F10"&&e.shiftKey?(e.preventDefault(),V(t)):e.key==="Enter"&&e.target===t&&T(t.dataset.slot))};i.addEventListener("change",P),i.addEventListener("click",U),i.addEventListener("keydown",Y),s(()=>{i.removeEventListener("change",P),i.removeEventListener("click",U),i.removeEventListener("keydown",Y)}),N(o,{id:"menu-locations-back",icon:"arrow-left",title:"All menus",onClick:()=>{location.hash="#/menus"}}),N(o,{id:"menu-locations-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>D()});const j=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||(e.preventDefault(),e.stopImmediatePropagation(),D())};document.addEventListener("keydown",j,!0),s(()=>document.removeEventListener("keydown",j,!0));const B=()=>{if(f.peek())try{S.set(h,{map:a.peek(),base:w(l.peek()),at:new Date().toISOString()})}catch{}},F=e=>{f.peek()&&(B(),e.preventDefault(),e.returnValue="")};if(window.addEventListener("beforeunload",F),s(()=>window.removeEventListener("beforeunload",F)),u={dirty:()=>f.peek(),dispose(){k=!0,B(),p.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(d),await _(),!v())return;let m=null;try{m=S.get(h)}catch{}if(m?.map){try{S.remove?S.remove(h):S.set(h,null)}catch{}const e=m.base&&m.base!==w(l.peek()),t=m.at?` (${oe(m.at)})`:"",n=e?`You left unsaved menu locations${t}, but the saved locations have changed since. Restore your version anyway?`:`You left unsaved menu locations${t}. Restore them?`;await E.confirm(ie(n))&&v()&&(a.value={...l.peek(),...m.map})}}};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|