domma-cms 1.6.9 → 1.6.11

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 CHANGED
@@ -428,7 +428,7 @@ Keep `E.loader` for work in progress (a save, an upload), where there is no shap
428
428
 
429
429
  ## Key Gotchas
430
430
 
431
- 1. **Structural menu items**: `{type:'separator'}` and `{type:'spacer', size}` carry no text/url - every renderer (menuRender, site.js navbar+footer, sidebar-renderer, menu-editor flatten/nest) needs a branch, and anything zipping items against rendered DOM must skip them or the indices shift. Domma's navbar has no spacer concept, so site.js strips them pre-render and re-inserts `<li>`s afterwards **against a snapshot** of the children, not the live HTMLCollection. `size: 'flex'` needs a flex parent, so the renderers stamp `dm-menu--has-flex-spacer` / `dm-admin-sidebar--has-flex-spacer` rather than making every list flex.
431
+ 1. **Structural menu items**: `{type:'separator'}`, `{type:'spacer', size}` and `{type:'theme-switcher'}` carry no text/url - every renderer (menuRender, site.js navbar+footer, sidebar-renderer, menu-editor flatten/nest) needs a branch, and anything zipping items against rendered DOM must skip them or the indices shift. Domma's navbar has no spacer concept, so site.js strips them pre-render and re-inserts `<li>`s afterwards **against a snapshot** of the children, not the live HTMLCollection. `size: 'flex'` needs a flex parent, so the renderers stamp `dm-menu--has-flex-spacer` / `dm-admin-sidebar--has-flex-spacer` rather than making every list flex.
432
432
  2. **Menu item sub-items**: use `items` key, NOT `children` - Domma navbar and the `[menu]` shortcode both read `items`.
433
433
  3. **Domma collections**: use `.get(0)` NOT `[0]` to get native DOM elements.
434
434
  4. **`.html()` strips interactive elements**: `E.modal().setContent(x)` also strips HTML - use light DOM slot
@@ -1 +1 @@
1
- export const CORE_HOTKEYS=Object.freeze({"#/":"d","#/pages":"p","#/media":"m","#/collections":"l","#/forms":"f","#/views":"v","#/users":"u","#/analytics":"a","#/notes":"n","#/todo":"t","#/contacts":"c","#/settings":","}),LEADER="g",SEQUENCE_MS=1500,FREQUENT_KEYS=9;export function normaliseHotkey(e){if(typeof e!="string")return null;const r=e.trim().toLowerCase();return r.length!==1||r===LEADER?null:/^[a-z,]$/.test(r)?r:null}export function hotkeyLabel(e){return`G then ${String(e).toUpperCase()}`}export function paletteKeyLabel(e=!1){return e?"\u2318K or \u2318D":"Ctrl+K or Ctrl+D"}export function sidebarTools(e){const r=[],o=new Set,n=(l,s)=>{for(const t of l||[]){if(!t||typeof t!="object"||t.hidden||t.frequent||t.type==="separator"||t.type==="spacer")continue;const i=String(t.text||"").replace(/\s*\(\d+\)\s*$/,"").trim(),c=typeof t.url=="string"?t.url:"";if(c.startsWith("#/")&&i&&!o.has(c)){o.add(c);const a=normaliseHotkey(t.hotkey),f=CORE_HOTKEYS[c]||null;r.push({url:c,text:i,icon:t.icon||null,path:s,wants:f||a,core:!!f})}Array.isArray(t.items)&&t.items.length&&n(t.items,i?[...s,i]:s)}};return n(e,[]),r}export function assignHotkeys(e){const r=sidebarTools(e),o=new Map,n=new Map,l=[],s=t=>{if(!t.wants)return;const i=o.get(t.wants);if(i){l.push({url:t.url,text:t.text,key:t.wants,takenBy:i.text});return}o.set(t.wants,t),n.set(t.url,t.wants)};return r.filter(t=>t.core).forEach(s),r.filter(t=>!t.core).forEach(s),{tools:r.map(t=>({...t,key:n.get(t.url)||null})),byKey:o,byUrl:n,conflicts:l}}export function frequentKeys(e){return((e||[]).find(o=>o&&o.frequent)?.items||[]).filter(o=>o&&typeof o.url=="string"&&o.url.startsWith("#/")).slice(0,FREQUENT_KEYS).map((o,n)=>({n:n+1,url:o.url,text:String(o.text||"")}))}export const CHIP_DELAY_MS=300;export function chipEntries(e,{max:r=14}={}){const o=(e?.frequent||[]).slice(0,r).map(s=>({n:s.n,text:s.text})),n=[...(e?.byKey||new Map).entries()].map(([s,t])=>({key:s,text:t.text})).sort((s,t)=>(s.key===","?1:0)-(t.key===","?1:0)||s.key.localeCompare(t.key)),l=Math.max(r-o.length,0);return{frequent:o,letters:n.slice(0,l),more:Math.max(n.length-l,0)}}export function createSequence(e){let r=null;const o=n=>r!==null&&n-r<=SEQUENCE_MS;return{pending:o,reset(){r=null},press(n,l){const s=typeof n=="string"&&n.length===1?n.toLowerCase():null;if(o(l)){if(r=null,!s)return{type:"miss"};const{byKey:t,frequent:i}=e();if(/^[1-9]$/.test(s)){const a=(i||[]).find(f=>f.n===Number(s));return a?{type:"go",url:a.url}:{type:"miss"}}const c=t?.get(s);return c?{type:"go",url:c.url}:{type:"miss"}}return r=null,s===LEADER?(r=l,{type:"start"}):null}}}export function matchTools(e,r,o=50){const n=String(r||"").trim().toLowerCase(),l=(t,i)=>(i.t.key?1:0)-(t.t.key?1:0)||t.t.path.length-i.t.path.length||t.at-i.at;if(!n)return e.map((t,i)=>({t,at:i})).sort(l).slice(0,o).map(t=>t.t);const s=[];return e.forEach((t,i)=>{const c=t.text.toLowerCase(),a=c.split(/[\s\-_/&]+/).filter(Boolean),f=a.map(p=>p[0]).join(""),y=t.path.join(" ").toLowerCase();let u=0;n.length===1&&t.key===n?u=120:c===n?u=110:c.startsWith(n)?u=100:a.some(p=>p.startsWith(n))?u=80:n.length>1&&f.startsWith(n)?u=70:c.includes(n)?u=50:y.includes(n)&&(u=20),u&&s.push({t,score:u,at:i})}),s.sort((t,i)=>i.score-t.score||l(t,i)),s.slice(0,o).map(t=>t.t)}export function isTypingTarget(e){if(!e||typeof e!="object")return!1;const r=String(e.tagName||"").toUpperCase();if(r==="TEXTAREA"||r==="SELECT")return!0;if(r==="INPUT"){const o=String(e.type||"text").toLowerCase();return!["button","submit","reset","checkbox","radio","range","color","file","image"].includes(o)}return e.isContentEditable?!0:!!(e.closest&&e.closest('[contenteditable=""], [contenteditable="true"], .cm-editor, .CodeMirror, .monaco-editor, .ProseMirror'))}export function isPaletteKey(e){if(!e||e.altKey||e.shiftKey||!(e.ctrlKey||e.metaKey)||e.ctrlKey&&e.metaKey)return!1;const r=String(e.key||"").toLowerCase();return r==="d"?!isTypingTarget(e.target):r==="k"?!g(e.target):!1}function g(e){return!e||typeof e!="object"?!1:e.isContentEditable?!0:!!(e.closest&&e.closest('[contenteditable=""], [contenteditable="true"], .cm-editor, .CodeMirror, .monaco-editor, .ProseMirror'))}
1
+ export const CORE_HOTKEYS=Object.freeze({"#/":"d","#/pages":"p","#/media":"m","#/collections":"l","#/forms":"f","#/views":"v","#/users":"u","#/analytics":"a","#/notes":"n","#/todo":"t","#/contacts":"c","#/settings":","}),LEADER="g",SEQUENCE_MS=1500,FREQUENT_KEYS=9;export function normaliseHotkey(e){if(typeof e!="string")return null;const r=e.trim().toLowerCase();return r.length!==1||r===LEADER?null:/^[a-z,]$/.test(r)?r:null}export function hotkeyLabel(e){return`G then ${String(e).toUpperCase()}`}export function paletteKeyLabel(e=!1){return e?"\u2318K or \u2318D":"Ctrl+K or Ctrl+D"}export function sidebarTools(e){const r=[],o=new Set,n=(c,s)=>{for(const t of c||[]){if(!t||typeof t!="object"||t.hidden||t.frequent||t.type==="separator"||t.type==="spacer"||t.type==="theme-switcher")continue;const i=String(t.text||"").replace(/\s*\(\d+\)\s*$/,"").trim(),l=typeof t.url=="string"?t.url:"";if(l.startsWith("#/")&&i&&!o.has(l)){o.add(l);const a=normaliseHotkey(t.hotkey),f=CORE_HOTKEYS[l]||null;r.push({url:l,text:i,icon:t.icon||null,path:s,wants:f||a,core:!!f})}Array.isArray(t.items)&&t.items.length&&n(t.items,i?[...s,i]:s)}};return n(e,[]),r}export function assignHotkeys(e){const r=sidebarTools(e),o=new Map,n=new Map,c=[],s=t=>{if(!t.wants)return;const i=o.get(t.wants);if(i){c.push({url:t.url,text:t.text,key:t.wants,takenBy:i.text});return}o.set(t.wants,t),n.set(t.url,t.wants)};return r.filter(t=>t.core).forEach(s),r.filter(t=>!t.core).forEach(s),{tools:r.map(t=>({...t,key:n.get(t.url)||null})),byKey:o,byUrl:n,conflicts:c}}export function frequentKeys(e){return((e||[]).find(o=>o&&o.frequent)?.items||[]).filter(o=>o&&typeof o.url=="string"&&o.url.startsWith("#/")).slice(0,FREQUENT_KEYS).map((o,n)=>({n:n+1,url:o.url,text:String(o.text||"")}))}export const CHIP_DELAY_MS=300;export function chipEntries(e,{max:r=14}={}){const o=(e?.frequent||[]).slice(0,r).map(s=>({n:s.n,text:s.text})),n=[...(e?.byKey||new Map).entries()].map(([s,t])=>({key:s,text:t.text})).sort((s,t)=>(s.key===","?1:0)-(t.key===","?1:0)||s.key.localeCompare(t.key)),c=Math.max(r-o.length,0);return{frequent:o,letters:n.slice(0,c),more:Math.max(n.length-c,0)}}export function createSequence(e){let r=null;const o=n=>r!==null&&n-r<=SEQUENCE_MS;return{pending:o,reset(){r=null},press(n,c){const s=typeof n=="string"&&n.length===1?n.toLowerCase():null;if(o(c)){if(r=null,!s)return{type:"miss"};const{byKey:t,frequent:i}=e();if(/^[1-9]$/.test(s)){const a=(i||[]).find(f=>f.n===Number(s));return a?{type:"go",url:a.url}:{type:"miss"}}const l=t?.get(s);return l?{type:"go",url:l.url}:{type:"miss"}}return r=null,s===LEADER?(r=c,{type:"start"}):null}}}export function matchTools(e,r,o=50){const n=String(r||"").trim().toLowerCase(),c=(t,i)=>(i.t.key?1:0)-(t.t.key?1:0)||t.t.path.length-i.t.path.length||t.at-i.at;if(!n)return e.map((t,i)=>({t,at:i})).sort(c).slice(0,o).map(t=>t.t);const s=[];return e.forEach((t,i)=>{const l=t.text.toLowerCase(),a=l.split(/[\s\-_/&]+/).filter(Boolean),f=a.map(p=>p[0]).join(""),y=t.path.join(" ").toLowerCase();let u=0;n.length===1&&t.key===n?u=120:l===n?u=110:l.startsWith(n)?u=100:a.some(p=>p.startsWith(n))?u=80:n.length>1&&f.startsWith(n)?u=70:l.includes(n)?u=50:y.includes(n)&&(u=20),u&&s.push({t,score:u,at:i})}),s.sort((t,i)=>i.score-t.score||c(t,i)),s.slice(0,o).map(t=>t.t)}export function isTypingTarget(e){if(!e||typeof e!="object")return!1;const r=String(e.tagName||"").toUpperCase();if(r==="TEXTAREA"||r==="SELECT")return!0;if(r==="INPUT"){const o=String(e.type||"text").toLowerCase();return!["button","submit","reset","checkbox","radio","range","color","file","image"].includes(o)}return e.isContentEditable?!0:!!(e.closest&&e.closest('[contenteditable=""], [contenteditable="true"], .cm-editor, .CodeMirror, .monaco-editor, .ProseMirror'))}export function isPaletteKey(e){if(!e||e.altKey||e.shiftKey||!(e.ctrlKey||e.metaKey)||e.ctrlKey&&e.metaKey)return!1;const r=String(e.key||"").toLowerCase();return r==="d"?!isTypingTarget(e.target):r==="k"?!m(e.target):!1}function m(e){return!e||typeof e!="object"?!1:e.isContentEditable?!0:!!(e.closest&&e.closest('[contenteditable=""], [contenteditable="true"], .cm-editor, .CodeMirror, .monaco-editor, .ProseMirror'))}
@@ -1 +1 @@
1
- export const WIDTHS=["narrow","normal","wide","full"],WIDTH_LABELS={narrow:"Narrow",normal:"Normal",wide:"Wide",full:"Full width"},WIDTH_HINTS={narrow:"768px - a reading column",normal:"1100px",wide:"75% of the window, up to 2000px",full:"edge to edge"},WIDTH_SHARE={narrow:"40%",normal:"62%",wide:"82%",full:"100%"},FILTERS=["all","builtin","custom"];const u=/^[a-zA-Z0-9#(),.\s%/-]+$/;export function layoutKey(e){return String(e??"").toLowerCase().trim().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"")}const d=(e,r,n=`${r}s`)=>`${e} ${e===1?r:n}`;export function chromeText(e){const r=[e.navbar!==!1&&"navbar",e.footer!==!1&&"footer",e.sidebar===!0&&"sidebar"].filter(Boolean);return r.length?r.join(", "):"no navbar or footer"}export function arrangeLayouts(e,r={},{filter:n="all",query:o=""}={}){const s=String(o).trim().toLowerCase(),l=Object.entries(e||{}).map(([t,a])=>({...a,key:t})),i=l.filter(t=>t.builtin),c=l.filter(t=>!t.builtin).sort((t,a)=>String(t.label||t.key).localeCompare(String(a.label||a.key)));return[...i,...c].filter(t=>n==="builtin"?t.builtin:n==="custom"?!t.builtin:!0).filter(t=>!s||[t.key,t.label,t.description,t.class].some(a=>String(a||"").toLowerCase().includes(s))).map(t=>layoutRow(t,(r.byKey||{})[t.key]||[]))}export function layoutRow(e,r=[]){const n=WIDTHS.includes(e.width)?e.width:"normal";return{key:e.key,label:e.label||e.key,meta:[e.key,e.description,chromeText(e)].filter(Boolean).join(" \xB7 "),builtin:e.builtin===!0,widthLabel:WIDTH_LABELS[n],used:r.length,usedText:r.length?d(r.length,"page"):"Unused",usedTone:r.length?"is-info":"is-muted",hasNav:e.navbar!==!1,hasFoot:e.footer!==!1,hasSide:e.sidebar===!0,share:WIDTH_SHARE[n],bg:e.bgColor&&u.test(e.bgColor)?e.bgColor:""}}export function formFromPreset(e={}){return{label:e.label||"",description:e.description||"",width:WIDTHS.includes(e.width)?e.width:"normal",navbar:e.navbar!==!1,footer:e.footer!==!1,sidebar:e.sidebar===!0,useBg:!!e.bgColor,bgColor:e.bgColor&&/^#[0-9a-f]{6}$/i.test(e.bgColor)?e.bgColor:e.bgColor||"#ffffff",bgImage:e.bgImage||"",cssClass:e.class||""}}export function checkLayout(e,{isNew:r,taken:n=[]}={}){const o=e||{},s={},l=String(o.label??"").trim(),i=r?layoutKey(l):"";l?l.length>60?s.label="Up to 60 characters.":r&&!i?s.label="Needs at least one letter or number.":r&&n.includes(i)&&(s.label=`"${i}" is already a layout - choose another name.`):s.label="A layout needs a name.";const c=o.useBg?String(o.bgColor||"").trim():"";c&&!u.test(c)&&(s.bgColor="Not a colour the server accepts.");const t=String(o.bgImage??"").trim(),a=String(o.cssClass??"").trim();/[^a-zA-Z0-9\s_-]/.test(a)&&(s.cssClass="Letters, numbers, spaces, hyphens and underscores only.");const b={label:l,description:String(o.description??"").trim().slice(0,200),width:WIDTHS.includes(o.width)?o.width:"normal",navbar:o.navbar!==!1,footer:o.footer!==!1,sidebar:o.sidebar===!0,bgColor:c,bgImage:t,class:a};return{errors:s,payload:b,key:i}}export function checkOptions(e){const r={},n=String(e?.spacerSize??"").trim(),o=Number(n);(n===""||!Number.isInteger(o)||o<0||o>500)&&(r.spacerSize="A whole number of pixels, 0 to 500.");const s=String(e?.spacerClass??"").trim();return/[^a-zA-Z0-9\s_-]/.test(s)&&(r.spacerClass="Letters, numbers, spaces, hyphens and underscores only."),{errors:r,payload:{spacerSize:Number.isInteger(o)?o:8,spacerClass:s}}}
1
+ export const WIDTHS=["narrow","normal","wide","full"],WIDTH_LABELS={narrow:"Narrow",normal:"Normal",wide:"Wide",full:"Full width"},WIDTH_HINTS={narrow:"768px - a reading column",normal:"1100px",wide:"75% of the window, up to 2000px",full:"edge to edge"},WIDTH_SHARE={narrow:"40%",normal:"62%",wide:"82%",full:"100%"},FILTERS=["all","builtin","custom"];const b=/^[a-zA-Z0-9#(),.\s%/-]+$/;export function layoutKey(e){return String(e??"").toLowerCase().trim().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"")}const d=(e,r,a=`${r}s`)=>`${e} ${e===1?r:a}`;export function chromeText(e){const r=[e.navbar!==!1&&"navbar",e.footer!==!1&&"footer",e.sidebar===!0&&"sidebar"].filter(Boolean);return r.length?r.join(", "):"no navbar or footer"}export function arrangeLayouts(e,r={},{filter:a="all",query:o=""}={}){const s=String(o).trim().toLowerCase(),n=Object.entries(e||{}).map(([t,i])=>({...i,key:t})),l=n.filter(t=>t.builtin),c=n.filter(t=>!t.builtin).sort((t,i)=>String(t.label||t.key).localeCompare(String(i.label||i.key)));return[...l,...c].filter(t=>a==="builtin"?t.builtin:a==="custom"?!t.builtin:!0).filter(t=>!s||[t.key,t.label,t.description,t.class].some(i=>String(i||"").toLowerCase().includes(s))).map(t=>layoutRow(t,(r.byKey||{})[t.key]||[]))}export function layoutRow(e,r=[]){const a=WIDTHS.includes(e.width)?e.width:"normal";return{key:e.key,label:e.label||e.key,meta:[e.key,e.description,chromeText(e)].filter(Boolean).join(" \xB7 "),builtin:e.builtin===!0,widthLabel:WIDTH_LABELS[a],used:r.length,usedText:r.length?d(r.length,"page"):"Unused",usedTone:r.length?"is-info":"is-muted",hasNav:e.navbar!==!1,hasFoot:e.footer!==!1,hasSide:e.sidebar===!0,sideRight:e.sidebar===!0&&e.sidebarSide==="right",share:WIDTH_SHARE[a],bg:e.bgColor&&b.test(e.bgColor)?e.bgColor:""}}export function formFromPreset(e={}){return{label:e.label||"",description:e.description||"",width:WIDTHS.includes(e.width)?e.width:"normal",navbar:e.navbar!==!1,footer:e.footer!==!1,sidebar:e.sidebar===!0,sidebarSide:e.sidebarSide==="right"?"right":"left",useBg:!!e.bgColor,bgColor:e.bgColor&&/^#[0-9a-f]{6}$/i.test(e.bgColor)?e.bgColor:e.bgColor||"#ffffff",bgImage:e.bgImage||"",cssClass:e.class||""}}export function checkLayout(e,{isNew:r,taken:a=[]}={}){const o=e||{},s={},n=String(o.label??"").trim(),l=r?layoutKey(n):"";n?n.length>60?s.label="Up to 60 characters.":r&&!l?s.label="Needs at least one letter or number.":r&&a.includes(l)&&(s.label=`"${l}" is already a layout - choose another name.`):s.label="A layout needs a name.";const c=o.useBg?String(o.bgColor||"").trim():"";c&&!b.test(c)&&(s.bgColor="Not a colour the server accepts.");const t=String(o.bgImage??"").trim(),i=String(o.cssClass??"").trim();/[^a-zA-Z0-9\s_-]/.test(i)&&(s.cssClass="Letters, numbers, spaces, hyphens and underscores only.");const u={label:n,description:String(o.description??"").trim().slice(0,200),width:WIDTHS.includes(o.width)?o.width:"normal",navbar:o.navbar!==!1,footer:o.footer!==!1,sidebar:o.sidebar===!0,sidebarSide:o.sidebarSide==="right"?"right":"left",bgColor:c,bgImage:t,class:i};return{errors:s,payload:u,key:l}}export function checkOptions(e){const r={},a=String(e?.spacerSize??"").trim(),o=Number(a);(a===""||!Number.isInteger(o)||o<0||o>500)&&(r.spacerSize="A whole number of pixels, 0 to 500.");const s=String(e?.spacerClass??"").trim();return/[^a-zA-Z0-9\s_-]/.test(s)&&(r.spacerClass="Letters, numbers, spaces, hyphens and underscores only."),{errors:r,payload:{spacerSize:Number.isInteger(o)?o:8,spacerClass:s}}}
@@ -1,2 +1,2 @@
1
- const d=["separator","spacer"];export const isStructural=t=>d.includes(t?.type);export function siblingsOf(t,n){const e=t.find(i=>i.id===n);return e?t.filter(i=>i.parentId===e.parentId):[]}export function moveRow(t,n,e){const i=siblingsOf(t,n),r=i.findIndex(a=>a.id===n),s=r+(e<0?-1:1);if(r<0||s<0||s>=i.length)return!1;i[r].touched=!0;const o=t.indexOf(i[r]),l=t.indexOf(i[s]);return[t[o],t[l]]=[t[l],t[o]],!0}export function indentRow(t,n){const e=siblingsOf(t,n),i=e.findIndex(r=>r.id===n);return i<=0||isStructural(e[i-1])?!1:(e[i].parentId=e[i-1].id,e[i].touched=!0,!0)}export function outdentRow(t,n){const e=t.find(r=>r.id===n);if(!e||e.parentId==null)return!1;const i=t.find(r=>r.id===e.parentId);return e.parentId=i?i.parentId:null,e.touched=!0,!0}export function newRow(t,n,e=null){return t==="separator"?{id:n,parentId:e,type:"separator"}:t==="spacer"?{id:n,parentId:e,type:"spacer",size:"md"}:{id:n,parentId:e,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}}export function insertBelow(t,n,e,i){const r=t.findIndex(o=>o.id===n);if(r===-1)return null;const s=newRow(e,i,t[r].parentId);return t.splice(r+1,0,s),s}export function addChild(t,n,e){const i=t.find(s=>s.id===n);if(!i||isStructural(i))return null;const r=newRow("item",e,i.id);return t.push(r),i.touched=!0,r}export function duplicateRow(t,n,e){const i=t.find(a=>a.id===n);if(!i||i.tool)return null;const r=new Map,s=[],o=(a,c)=>{if(a.tool)return;const u={...g(a),id:e(),parentId:c};delete u.tool,delete u.locked,u.touched=!0,r.set(a.id,u.id),s.push(u);for(const f of t.filter(p=>p.parentId===a.id))o(f,u.id)};if(o(i,i.parentId),!s.length)return null;s[0].text&&(s[0].text=`${s[0].text} (copy)`);const l=t.indexOf(i);return t.splice(l+1,0,s[0]),t.push(...s.slice(1)),s[0]}function g(t){return JSON.parse(JSON.stringify(t))}export function bindingFrom({slot:t="",projects:n=[],pagesText:e=""}={}){return t?{slot:t,projects:[...n],pages:String(e).split(`
2
- `).map(i=>i.trim()).filter(Boolean)}:null}export function badPagePatterns(t=[]){return t.filter(n=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(n))}const m=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"];export function menuFromForm(t={},n={},e=null,i=[],r={}){const s=Object.fromEntries(m.filter(a=>t[a]).map(a=>[a,t[a]])),o={};if(t.position==="floating"){t.floatAnchor&&(o.anchor=t.floatAnchor);for(const[a,c]of[["offsetX",t.floatOffsetX],["offsetY",t.floatOffsetY],["width",t.floatWidth],["radius",t.floatRadius],["opacity",t.floatOpacity]])c!==""&&c!=null&&Number.isFinite(Number(c))&&(o[a]=Number(c));t.floatShadow&&(o.shadow=t.floatShadow),t.floatAccent&&(o.accent=t.floatAccent),t.floatTitle&&(o.title=String(t.floatTitle).trim()),t.floatCollapsible&&(o.collapsible=!0),t.floatCollapsible&&t.floatCollapsed&&(o.collapsed=!0),t.floatMovable&&(o.movable=!0),t.floatResizable&&(o.resizable=!0)}const l={};return t.highlightHover&&(l.hover=t.highlightHover),t.highlightActive&&(l.active=t.highlightActive),t.highlightAccent&&(l.accent=t.highlightAccent),{slug:String(n.slug||"").trim(),name:String(n.name||"").trim(),description:String(n.description||"").trim(),...t.variant&&{variant:t.variant},...t.position&&{position:t.position},...t.orientation&&{orientation:t.orientation},...t.orientation==="vertical"&&t.side&&{side:t.side},...t.orientation!=="vertical"&&t.align&&{align:t.align},...Object.keys(o).length&&{float:o},...Object.keys(l).length&&{highlight:l},...t.appearOnHover&&{appearOnHover:!0},...t.class&&String(t.class).trim()&&{class:String(t.class).trim()},...Object.keys(s).length&&{style:s},binding:e,items:i,meta:{...r||{},project:n.project||null}}}export function menuProblems(t){const n=[];/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(t.slug||"")||n.push("The slug must be lowercase letters, numbers and hyphens, no hyphen at either end (Meta tab)."),t.name||n.push("The menu needs a name (Meta tab).");const e=countUnlabelled(t.items);if(e&&n.push(`${e} item${e===1?" has":"s have"} no label - every item needs one (Items tab).`),t.binding){const i=badPagePatterns(t.binding.pages);i.length&&n.push(`Not valid page patterns: ${i.join(", ")} (Binding tab).`)}return n}export function countUnlabelled(t=[]){let n=0;for(const e of t||[])!isStructural(e)&&!(e.ref===!0&&e.url)&&!String(e.text||"").trim()&&n++,n+=countUnlabelled(e.items);return n}export const snapshot=t=>JSON.stringify(t);export function depthOfItems(t){if(!Array.isArray(t)||!t.length)return 0;let n=0;for(const e of t){const i=Array.isArray(e.items)&&e.items.length?depthOfItems(e.items):0;i>n&&(n=i)}return n+1}export function locationRows(t=[],n={},e={},i=[],r={}){const s=Object.fromEntries(i.map(o=>[o.slug,o.name||o.slug]));return t.map(o=>{const l=n[o.slot]||"",a=l&&r[l]||0,c=!!l&&o.maxDepth!=null&&a>o.maxDepth,u=o.slot==="overlay";return{slot:o.slot,label:o.label||o.slot,source:o.source||"",description:o.description||"",menu:l,menuName:l?s[l]||`${l} (missing)`:"",missing:!!l&&!s[l],tooDeep:c,depthText:c?`Nested ${a} deep - this slot shows ${o.maxDepth}`:"",empty:!l,emptyText:l?"":u?"Optional":"Nothing mapped",changed:(n[o.slot]||"")!==(e[o.slot]||"")}})}export function locationsPayload(t={},n=[]){const e=new Set(n.map(i=>i.slot));return Object.fromEntries(Object.entries(t).filter(([i,r])=>r&&(!n.length||e.has(i))))}export function locationsSnapshot(t={}){return JSON.stringify(Object.keys(t).filter(n=>t[n]).sort().map(n=>[n,t[n]]))}
1
+ const d=["separator","spacer","theme-switcher"];export const isStructural=t=>d.includes(t?.type);export function siblingsOf(t,e){const n=t.find(i=>i.id===e);return n?t.filter(i=>i.parentId===n.parentId):[]}export function moveRow(t,e,n){const i=siblingsOf(t,e),r=i.findIndex(a=>a.id===e),s=r+(n<0?-1:1);if(r<0||s<0||s>=i.length)return!1;i[r].touched=!0;const o=t.indexOf(i[r]),l=t.indexOf(i[s]);return[t[o],t[l]]=[t[l],t[o]],!0}export function indentRow(t,e){const n=siblingsOf(t,e),i=n.findIndex(r=>r.id===e);return i<=0||isStructural(n[i-1])?!1:(n[i].parentId=n[i-1].id,n[i].touched=!0,!0)}export function outdentRow(t,e){const n=t.find(r=>r.id===e);if(!n||n.parentId==null)return!1;const i=t.find(r=>r.id===n.parentId);return n.parentId=i?i.parentId:null,n.touched=!0,!0}export function newRow(t,e,n=null){return t==="separator"?{id:e,parentId:n,type:"separator"}:t==="theme-switcher"?{id:e,parentId:n,type:"theme-switcher"}:t==="spacer"?{id:e,parentId:n,type:"spacer",size:"md"}:{id:e,parentId:n,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}}export function insertBelow(t,e,n,i){const r=t.findIndex(o=>o.id===e);if(r===-1)return null;const s=newRow(n,i,t[r].parentId);return t.splice(r+1,0,s),s}export function addChild(t,e,n){const i=t.find(s=>s.id===e);if(!i||isStructural(i))return null;const r=newRow("item",n,i.id);return t.push(r),i.touched=!0,r}export function duplicateRow(t,e,n){const i=t.find(a=>a.id===e);if(!i||i.tool)return null;const r=new Map,s=[],o=(a,c)=>{if(a.tool)return;const u={...g(a),id:n(),parentId:c};delete u.tool,delete u.locked,u.touched=!0,r.set(a.id,u.id),s.push(u);for(const f of t.filter(p=>p.parentId===a.id))o(f,u.id)};if(o(i,i.parentId),!s.length)return null;s[0].text&&(s[0].text=`${s[0].text} (copy)`);const l=t.indexOf(i);return t.splice(l+1,0,s[0]),t.push(...s.slice(1)),s[0]}function g(t){return JSON.parse(JSON.stringify(t))}export function bindingFrom({slot:t="",projects:e=[],pagesText:n=""}={}){return t?{slot:t,projects:[...e],pages:String(n).split(`
2
+ `).map(i=>i.trim()).filter(Boolean)}:null}export function badPagePatterns(t=[]){return t.filter(e=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(e))}const m=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"];export function menuFromForm(t={},e={},n=null,i=[],r={}){const s=Object.fromEntries(m.filter(a=>t[a]).map(a=>[a,t[a]])),o={};if(t.position==="floating"){t.floatAnchor&&(o.anchor=t.floatAnchor);for(const[a,c]of[["offsetX",t.floatOffsetX],["offsetY",t.floatOffsetY],["width",t.floatWidth],["radius",t.floatRadius],["opacity",t.floatOpacity]])c!==""&&c!=null&&Number.isFinite(Number(c))&&(o[a]=Number(c));t.floatShadow&&(o.shadow=t.floatShadow),t.floatAccent&&(o.accent=t.floatAccent),t.floatTitle&&(o.title=String(t.floatTitle).trim()),t.floatCollapsible&&(o.collapsible=!0),t.floatCollapsible&&t.floatCollapsed&&(o.collapsed=!0),t.floatMovable&&(o.movable=!0),t.floatResizable&&(o.resizable=!0)}const l={};return t.highlightHover&&(l.hover=t.highlightHover),t.highlightActive&&(l.active=t.highlightActive),t.highlightAccent&&(l.accent=t.highlightAccent),{slug:String(e.slug||"").trim(),name:String(e.name||"").trim(),description:String(e.description||"").trim(),...t.variant&&{variant:t.variant},...t.position&&{position:t.position},...t.orientation&&{orientation:t.orientation},...t.orientation==="vertical"&&t.side&&{side:t.side},...t.orientation!=="vertical"&&t.align&&{align:t.align},...Object.keys(o).length&&{float:o},...Object.keys(l).length&&{highlight:l},...t.appearOnHover&&{appearOnHover:!0},...t.class&&String(t.class).trim()&&{class:String(t.class).trim()},...Object.keys(s).length&&{style:s},binding:n,items:i,meta:{...r||{},project:e.project||null}}}export function menuProblems(t){const e=[];/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(t.slug||"")||e.push("The slug must be lowercase letters, numbers and hyphens, no hyphen at either end (Meta tab)."),t.name||e.push("The menu needs a name (Meta tab).");const n=countUnlabelled(t.items);if(n&&e.push(`${n} item${n===1?" has":"s have"} no label - every item needs one (Items tab).`),t.binding){const i=badPagePatterns(t.binding.pages);i.length&&e.push(`Not valid page patterns: ${i.join(", ")} (Binding tab).`)}return e}export function countUnlabelled(t=[]){let e=0;for(const n of t||[])!isStructural(n)&&!(n.ref===!0&&n.url)&&!String(n.text||"").trim()&&e++,e+=countUnlabelled(n.items);return e}export const snapshot=t=>JSON.stringify(t);export function depthOfItems(t){if(!Array.isArray(t)||!t.length)return 0;let e=0;for(const n of t){const i=Array.isArray(n.items)&&n.items.length?depthOfItems(n.items):0;i>e&&(e=i)}return e+1}export function locationRows(t=[],e={},n={},i=[],r={}){const s=Object.fromEntries(i.map(o=>[o.slug,o.name||o.slug]));return t.map(o=>{const l=e[o.slot]||"",a=l&&r[l]||0,c=!!l&&o.maxDepth!=null&&a>o.maxDepth,u=o.slot==="overlay";return{slot:o.slot,label:o.label||o.slot,source:o.source||"",description:o.description||"",menu:l,menuName:l?s[l]||`${l} (missing)`:"",missing:!!l&&!s[l],tooDeep:c,depthText:c?`Nested ${a} deep - this slot shows ${o.maxDepth}`:"",empty:!l,emptyText:l?"":u?"Optional":"Nothing mapped",changed:(e[o.slot]||"")!==(n[o.slot]||"")}})}export function locationsPayload(t={},e=[]){const n=new Set(e.map(i=>i.slot));return Object.fromEntries(Object.entries(t).filter(([i,r])=>r&&(!e.length||n.has(i))))}export function locationsSnapshot(t={}){return JSON.stringify(Object.keys(t).filter(e=>t[e]).sort().map(e=>[e,t[e]]))}
@@ -1 +1 @@
1
- let d=1;export const uid=()=>"i_"+d++;export function flatten(g,l=null,e=[]){for(const i of g||[]){const o=uid();if(i&&i.type==="separator"){e.push({id:o,parentId:l,type:"separator"});continue}if(i&&i.type==="spacer"){e.push({id:o,parentId:l,type:"spacer",size:i.size||"md"});continue}e.push({id:o,parentId:l,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,class:i.class||"",key:i.key||"",ref:!!i.ref,tool:i.$tool||null,touched:!1,synthesised:!!i.synthesised}),Array.isArray(i.items)&&i.items.length&&flatten(i.items,o,e)}return e}export function nest(g){const l=new Map;for(const t of g)l.has(t.parentId)||l.set(t.parentId,[]),l.get(t.parentId).push(t);function e(t){const n=[];let s=!1;const a=l.get(t)||[];for(let h=a.length-1;h>=0;h--){const c=a[h].tool?o(a[h],s):i(a[h]);c&&(n.unshift(c),s=!0)}return n}function i(t){if(t.type==="separator")return{type:"separator"};if(t.type==="spacer")return{type:"spacer",...t.size&&t.size!=="md"&&{size:t.size}};const n=e(t.id);return t.synthesised&&!n.length?null:{text:(t.text||"").trim(),url:(t.url||"").trim(),...t.icon&&{icon:t.icon.trim()},...t.visibility&&{visibility:t.visibility},...t.permission&&{permission:t.permission},...t.hidden&&{hidden:!0},...t.bundled&&{bundled:!0},...t.badge&&(t.badge.text||t.badge.countFrom)?{badge:{...t.badge.text&&{text:t.badge.text},...t.badge.countFrom&&{countFrom:t.badge.countFrom},...t.badge.variant&&{variant:t.badge.variant}}}:{},...t.pill&&t.pill.variant?{pill:{style:t.pill.style||"filled",variant:t.pill.variant}}:{},...t.colour&&{colour:t.colour},...t.class&&t.class.trim()&&{class:t.class.trim()},...t.key&&{key:t.key},...t.ref&&{ref:!0},...t.highlight&&(t.highlight.hover||t.highlight.active||t.highlight.accent)?{highlight:{...t.highlight.hover&&{hover:t.highlight.hover},...t.highlight.active&&{active:t.highlight.active},...t.highlight.accent&&{accent:t.highlight.accent}}}:{},...n.length&&{items:n}}}function o(t,n=!1){const s=e(t.id);if(!t.tool.persisted&&!t.touched&&!s.length&&!n)return null;const a=t.tool.defaults,h=(t.text||"").trim(),c=(t.icon||"").trim();return{url:(t.url||"").trim(),ref:!0,...h&&{text:h},...c&&c!==a.icon&&{icon:c},...t.visibility&&{visibility:t.visibility},...t.hidden&&{hidden:!0},...t.badge&&(t.badge.text||t.badge.countFrom)?{badge:{...t.badge.text&&{text:t.badge.text},...t.badge.countFrom&&{countFrom:t.badge.countFrom},...t.badge.variant&&{variant:t.badge.variant}}}:{},...t.colour&&{colour:t.colour},...t.class&&t.class.trim()&&{class:t.class.trim()},...t.highlight&&(t.highlight.hover||t.highlight.active||t.highlight.accent)?{highlight:{...t.highlight.hover&&{hover:t.highlight.hover},...t.highlight.active&&{active:t.highlight.active},...t.highlight.accent&&{accent:t.highlight.accent}}}:{},...s.length&&{items:s}}}return e(null)}
1
+ let u=1;export const uid=()=>"i_"+u++;export function flatten(g,l=null,e=[]){for(const i of g||[]){const s=uid();if(i&&i.type==="separator"){e.push({id:s,parentId:l,type:"separator"});continue}if(i&&i.type==="spacer"){e.push({id:s,parentId:l,type:"spacer",size:i.size||"md"});continue}if(i&&i.type==="theme-switcher"){e.push({id:s,parentId:l,type:"theme-switcher"});continue}e.push({id:s,parentId:l,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,class:i.class||"",key:i.key||"",ref:!!i.ref,tool:i.$tool||null,touched:!1,synthesised:!!i.synthesised}),Array.isArray(i.items)&&i.items.length&&flatten(i.items,s,e)}return e}export function nest(g){const l=new Map;for(const t of g)l.has(t.parentId)||l.set(t.parentId,[]),l.get(t.parentId).push(t);function e(t){const h=[];let o=!1;const a=l.get(t)||[];for(let n=a.length-1;n>=0;n--){const c=a[n].tool?s(a[n],o):i(a[n]);c&&(h.unshift(c),o=!0)}return h}function i(t){if(t.type==="separator")return{type:"separator"};if(t.type==="spacer")return{type:"spacer",...t.size&&t.size!=="md"&&{size:t.size}};if(t.type==="theme-switcher")return{type:"theme-switcher"};const h=e(t.id);return t.synthesised&&!h.length?null:{text:(t.text||"").trim(),url:(t.url||"").trim(),...t.icon&&{icon:t.icon.trim()},...t.visibility&&{visibility:t.visibility},...t.permission&&{permission:t.permission},...t.hidden&&{hidden:!0},...t.bundled&&{bundled:!0},...t.badge&&(t.badge.text||t.badge.countFrom)?{badge:{...t.badge.text&&{text:t.badge.text},...t.badge.countFrom&&{countFrom:t.badge.countFrom},...t.badge.variant&&{variant:t.badge.variant}}}:{},...t.pill&&t.pill.variant?{pill:{style:t.pill.style||"filled",variant:t.pill.variant}}:{},...t.colour&&{colour:t.colour},...t.class&&t.class.trim()&&{class:t.class.trim()},...t.key&&{key:t.key},...t.ref&&{ref:!0},...t.highlight&&(t.highlight.hover||t.highlight.active||t.highlight.accent)?{highlight:{...t.highlight.hover&&{hover:t.highlight.hover},...t.highlight.active&&{active:t.highlight.active},...t.highlight.accent&&{accent:t.highlight.accent}}}:{},...h.length&&{items:h}}}function s(t,h=!1){const o=e(t.id);if(!t.tool.persisted&&!t.touched&&!o.length&&!h)return null;const a=t.tool.defaults,n=(t.text||"").trim(),c=(t.icon||"").trim();return{url:(t.url||"").trim(),ref:!0,...n&&{text:n},...c&&c!==a.icon&&{icon:c},...t.visibility&&{visibility:t.visibility},...t.hidden&&{hidden:!0},...t.badge&&(t.badge.text||t.badge.countFrom)?{badge:{...t.badge.text&&{text:t.badge.text},...t.badge.countFrom&&{countFrom:t.badge.countFrom},...t.badge.variant&&{variant:t.badge.variant}}}:{},...t.colour&&{colour:t.colour},...t.class&&t.class.trim()&&{class:t.class.trim()},...t.highlight&&(t.highlight.hover||t.highlight.active||t.highlight.accent)?{highlight:{...t.highlight.hover&&{hover:t.highlight.hover},...t.highlight.active&&{active:t.highlight.active},...t.highlight.accent&&{accent:t.highlight.accent}}}:{},...o.length&&{items:o}}}return e(null)}
@@ -1 +1 @@
1
- export function isWithin(e,s,p){let t=e.find(i=>i.id===s);for(;t;){if(t.id===p)return!0;t=t.parentId?e.find(i=>i.id===t.parentId):null}return!1}export function applyDrop(e,s,p,t){if(s===p)return!1;const i=e.find(n=>n.id===s),r=e.find(n=>n.id===p);if(!i||!r||isWithin(e,p,s))return!1;const o=r.type==="separator"||r.type==="spacer",c=t==="into"&&o?"below":t;if(e.splice(e.indexOf(i),1),c==="into"){i.parentId=r.id;let n=e.indexOf(r)+1;for(;n<e.length&&isWithin(e,e[n].id,r.id);)n++;e.splice(n,0,i)}else{i.parentId=r.parentId;const n=e.indexOf(r);if(c==="above")e.splice(n,0,i);else{let d=n+1;for(;d<e.length&&isWithin(e,e[d].id,r.id);)d++;e.splice(d,0,i)}}return!0}
1
+ export function isWithin(e,s,p){let t=e.find(i=>i.id===s);for(;t;){if(t.id===p)return!0;t=t.parentId?e.find(i=>i.id===t.parentId):null}return!1}export function applyDrop(e,s,p,t){if(s===p)return!1;const i=e.find(n=>n.id===s),r=e.find(n=>n.id===p);if(!i||!r||isWithin(e,p,s))return!1;const o=r.type==="separator"||r.type==="spacer"||r.type==="theme-switcher",c=t==="into"&&o?"below":t;if(e.splice(e.indexOf(i),1),c==="into"){i.parentId=r.id;let n=e.indexOf(r)+1;for(;n<e.length&&isWithin(e,e[n].id,r.id);)n++;e.splice(n,0,i)}else{i.parentId=r.parentId;const n=e.indexOf(r);if(c==="above")e.splice(n,0,i);else{let d=n+1;for(;d<e.length&&isWithin(e,e[d].id,r.id);)d++;e.splice(d,0,i)}}return!0}
@@ -44,4 +44,4 @@ Optional body content.
44
44
  [/table]`,attrs:[{name:"striped",type:"checkbox",label:"Striped"},{name:"bordered",type:"checkbox",label:"Bordered"},{name:"compact",type:"checkbox",label:"Compact"},{name:"caption",type:"text",label:"Caption"},{name:"class",type:"text",label:"CSS class"},{name:"id",type:"text",label:"ID"}]},{name:"badge",label:"Badge",icon:"badge",category:"Content",selfClosing:!1,container:!1,wrappable:!1,template:'[badge variant="success"]New[/badge]',inline:!0,attrs:[{name:"variant",type:"select",label:"Variant",options:[["primary","Primary"],["secondary","Secondary"],["success","Success"],["danger","Danger"],["warning","Warning"],["info","Info"],["light","Light"],["dark","Dark"]]},{name:"pill",type:"checkbox",label:"Pill"},{name:"outline",type:"checkbox",label:"Outline"},{name:"size",type:"select",label:"Size",options:[["","Default"],["small","Small"],["large","Large"]]}]},{name:"text",label:"Text",icon:"text-style",preview:"text",category:"Content",selfClosing:!1,container:!0,wrappable:!0,template:"[text]Styled text[/text]",inline:!0,attrs:[{name:"size",type:"select",label:"Size",options:[["xs","XS"],["sm","Small"],["base","Base"],["lg","Large"],["xl","XL"],["2xl","2XL"],["3xl","3XL"],["4xl","4XL"]]},{name:"weight",type:"select",label:"Weight",options:[["thin","Thin"],["light","Light"],["normal","Normal"],["medium","Medium"],["semibold","Semibold"],["bold","Bold"],["extrabold","Extra bold"],["black","Black"]]},{name:"point-size",type:"number",label:"Point size"},{name:"bold",type:"checkbox",label:"Bold"},{name:"italic",type:"checkbox",label:"Italic"},{name:"color",type:"colour",label:"Colour"},{name:"font",type:"select",label:"Font",options:[["Georgia","Georgia"],["Arial","Arial"],["Verdana","Verdana"],["Courier New","Courier New"],["Times New Roman","Times New Roman"],["Trebuchet MS","Trebuchet MS"]]},{name:"transform",type:"select",label:"Transform",options:[["upper","Uppercase"],["lower","Lowercase"],["capitalize","Capitalise"],["none","None"]]},{name:"decoration",type:"select",label:"Decoration",options:[["underline","Underline"],["line-through","Line through"],["none","None"]]},{name:"spacing",type:"select",label:"Letter spacing",options:[["tight","Tight"],["normal","Normal"],["wide","Wide"],["wider","Wider"]]},{name:"align",type:"select",label:"Align",options:[["","None"],["left","Left"],["center","Centre"],["right","Right"]]},{name:"style",type:"text",label:"Inline CSS"},{name:"class",type:"text",label:"CSS class"},{name:"id",type:"text",label:"ID"}]},{name:"button",label:"Button",icon:"btn-insert",category:"Content",selfClosing:!0,container:!1,wrappable:!1,template:'[button href="/contact" variant="primary" label="Get in touch" /]',attrs:[{name:"href",type:"url",label:"Link"},{name:"label",type:"text",label:"Label"},{name:"variant",type:"select",label:"Variant",options:[["primary","Primary"],["secondary","Secondary"],["success","Success"],["danger","Danger"],["warning","Warning"],["info","Info"],["outline","Outline"],["ghost","Ghost"],["link","Link"],["outline-success","Outline success"],["outline-danger","Outline danger"],["outline-warning","Outline warning"],["outline-info","Outline info"]]},{name:"size",type:"select",label:"Size",options:[["","Default"],["sm","Small"],["lg","Large"]]},{name:"icon",type:"icon",label:"Icon (before)"},{name:"icon-after",type:"icon",label:"Icon (after)"},{name:"target",type:"text",label:"Target"},{name:"class",type:"text",label:"CSS class"},{name:"id",type:"text",label:"ID"}]},{name:"link",label:"Link",icon:"link-shortcode",category:"Content",selfClosing:!0,container:!1,wrappable:!1,template:'[link href="/about" label="About us" /]',formats:[{value:"markdown",label:"Markdown link",emit:e=>Object.keys(e).some(t=>t!=="href"&&t!=="label"&&e[t])||!e.href?null:`[${e.label||e.href}](${e.href})`},{value:"shortcode",label:"Link shortcode"}],attrs:[{name:"href",type:"url",label:"Link"},{name:"label",type:"text",label:"Label"},{name:"target",type:"text",label:"Target"},{name:"class",type:"text",label:"CSS class"},{name:"icon",type:"icon",label:"Icon (before)"},{name:"icon-after",type:"icon",label:"Icon (after)"},{name:"id",type:"text",label:"ID"}]},{name:"listgroup",label:"List group",icon:"list",category:"Content",selfClosing:!1,container:!0,wrappable:!1,template:`[listgroup]
45
45
  [item]First item[/item]
46
46
  [item]Second item[/item]
47
- [/listgroup]`,children:{name:"item",min:1,max:20},attrs:[{name:"variant",type:"select",label:"Variant",options:[["","Default"],["flush","Flush"],["numbered","Numbered"],["horizontal","Horizontal"]]},{name:"size",type:"select",label:"Size",options:[["","Default"],["sm","Small"],["lg","Large"]]},{name:"itemvariant",type:"select",label:"Item variant",options:[["","Default"],["primary","Primary"],["secondary","Secondary"],["success","Success"],["danger","Danger"],["warning","Warning"],["info","Info"]]},{name:"action",type:"checkbox",label:"Actionable items"}]},{name:"collection",label:"Collection",icon:"database",category:"Data",selfClosing:!0,container:!1,wrappable:!1,template:'[collection slug="enquiries" display="table" /]',attrs:[{name:"slug",type:"lookup",source:"collections",label:"Collection"},{name:"display",type:"select",label:"Display",options:[["table","Table"],["cards","Cards"],["list","List"],["accordion","Accordion"],["timeline","Timeline"],["block","Block"]]},{name:"fields",type:"text",label:"Fields (comma-separated)"},{name:"limit",type:"number",label:"Limit"},{name:"sort",type:"lookup",source:"collection-fields",label:"Sort field"},{name:"order",type:"select",label:"Order",options:[["desc","Descending"],["asc","Ascending"]]},{name:"title-field",type:"lookup",source:"collection-fields",label:"Title field"},{name:"columns",type:"number",label:"Card columns"},{name:"search",type:"checkbox",label:"Search (table)",default:!0},{name:"sortable",type:"checkbox",label:"Sortable (table)",default:!0},{name:"exportable",type:"checkbox",label:"CSV export"},{name:"page-size",type:"number",label:"Page size"},{name:"empty",type:"text",label:"Empty message"},{name:"scope",type:"select",label:"Scope",options:[["","All"],["mine","Current user"]]},{name:"cta",type:"lookup",source:"actions",label:"CTA action"},{name:"cta-label",type:"text",label:"CTA label"},{name:"cta-icon",type:"icon",label:"CTA icon"},{name:"cta-style",type:"select",label:"CTA style",options:[["primary","Primary"],["secondary","Secondary"],["success","Success"],["danger","Danger"],["warning","Warning"],["info","Info"]]},{name:"cta-confirm",type:"text",label:"CTA confirm text"},{name:"class",type:"text",label:"CSS class"},{name:"id",type:"text",label:"ID"}]},{name:"view",label:"View",icon:"eye",category:"Data",selfClosing:!0,container:!1,wrappable:!1,template:'[view slug="products" display="table" limit="50" /]',attrs:[{name:"slug",type:"lookup",source:"views",label:"View"},{name:"display",type:"select",label:"Display",options:[["table","Table"],["cards","Cards"],["list","List"],["accordion","Accordion"],["timeline","Timeline"],["block","Block"]]},{name:"limit",type:"number",label:"Limit"},{name:"fields",type:"text",label:"Fields (comma-separated)"},{name:"title-field",type:"text",label:"Title field"},{name:"columns",type:"number",label:"Card columns"},{name:"empty",type:"text",label:"Empty message"}]},{name:"block",label:"Block",icon:"layout",category:"Data",selfClosing:!0,container:!1,wrappable:!1,template:'[block template="post-card" title="My Post" /]',attrs:[{name:"template",type:"lookup",source:"blocks",label:"Template"}]},{name:"form",label:"Form",icon:"file-text",category:"Data",selfClosing:!0,container:!1,wrappable:!1,template:'[form name="contact" /]',attrs:[{name:"name",type:"lookup",source:"forms",label:"Form"},{name:"class",type:"text",label:"CSS class"},{name:"id",type:"text",label:"ID"}]},{name:"cta",label:"CTA",icon:"mouse-pointer",category:"Data",selfClosing:!0,container:!1,wrappable:!1,template:'[cta action="send-welcome" entry="abc123" style="primary" label="Send" /]',attrs:[{name:"action",type:"lookup",source:"actions",label:"Action"},{name:"entry",type:"text",label:"Entry ID"},{name:"style",type:"select",label:"Style",options:[["primary","Primary"],["secondary","Secondary"],["success","Success"],["danger","Danger"],["warning","Warning"],["info","Info"]]},{name:"icon",type:"icon",label:"Icon"},{name:"size",type:"select",label:"Size",options:[["","Default"],["sm","Small"],["lg","Large"]]},{name:"confirm",type:"text",label:"Confirm text"},{name:"label",type:"text",label:"Label"}]},{name:"menu",label:"Menu",icon:"menu",category:"Data",selfClosing:!0,container:!1,wrappable:!1,template:'[menu slug="legal" /]',attrs:[{name:"slug",type:"lookup",source:"menus",label:"Menu"},{name:"location",type:"lookup",source:"menu-locations",label:"Location slot"},{name:"variant",type:"text",label:"Variant"},{name:"class",type:"text",label:"CSS class"},{name:"depth",type:"number",label:"Max depth"},{name:"orientation",type:"select",label:"Orientation",options:[["","Menu default"],["horizontal","Horizontal"],["vertical","Vertical"]]},{name:"float",type:"select",label:"Floating",options:[["","Menu default"],["no","Render inline here"]]}]},{name:"icon",label:"Icon",icon:"icon-pick",category:"Media",selfClosing:!0,container:!1,wrappable:!1,template:'[icon name="arrow-right" /]',attrs:[{name:"name",type:"icon",label:"Icon name"},{name:"size",type:"number",label:"Size (px)"},{name:"color",type:"colour",label:"Colour"},{name:"class",type:"text",label:"CSS class"}]},{name:"embed",label:"Embed",icon:"video",category:"Media",selfClosing:!0,container:!1,wrappable:!1,template:'[embed url="https://youtu.be/dQw4w9WgXcQ" /]',custom:"embed",attrs:[{name:"url",type:"url",label:"URL"},{name:"ratio",type:"select",label:"Aspect ratio",options:[["16:9","16:9"],["4:3","4:3"],["1:1","1:1"],["21:9","21:9"],["9:16","9:16"],["3:4","3:4"]]},{name:"title",type:"text",label:"Title / caption"},{name:"width",type:"text",label:"Width"},{name:"align",type:"select",label:"Alignment",options:[["left","Left"],["center","Centre"],["right","Right"]]},{name:"autoplay",type:"checkbox",label:"Autoplay"},{name:"loop",type:"checkbox",label:"Loop"},{name:"muted",type:"checkbox",label:"Muted"}]}],EDITOR_TOOLS=[{name:"image",label:"Image",icon:"image-add",category:"Media",custom:"image"},{name:"colourpick",label:"Colour picker",icon:"colour-picker",category:"Media",custom:"colourpick"},{name:"component",label:"Component",icon:"component",category:"Components",custom:"component"},{name:"scribe",label:"Scribe",icon:"scribe-insert",category:"Content",custom:"scribe"}];
47
+ [/listgroup]`,children:{name:"item",min:1,max:20},attrs:[{name:"variant",type:"select",label:"Variant",options:[["","Default"],["flush","Flush"],["numbered","Numbered"],["horizontal","Horizontal"]]},{name:"size",type:"select",label:"Size",options:[["","Default"],["sm","Small"],["lg","Large"]]},{name:"itemvariant",type:"select",label:"Item variant",options:[["","Default"],["primary","Primary"],["secondary","Secondary"],["success","Success"],["danger","Danger"],["warning","Warning"],["info","Info"]]},{name:"action",type:"checkbox",label:"Actionable items"}]},{name:"collection",label:"Collection",icon:"database",category:"Data",selfClosing:!0,container:!1,wrappable:!1,template:'[collection slug="enquiries" display="table" /]',attrs:[{name:"slug",type:"lookup",source:"collections",label:"Collection"},{name:"display",type:"select",label:"Display",options:[["table","Table"],["cards","Cards"],["list","List"],["accordion","Accordion"],["timeline","Timeline"],["block","Block"]]},{name:"fields",type:"text",label:"Fields (comma-separated)"},{name:"limit",type:"number",label:"Limit"},{name:"sort",type:"lookup",source:"collection-fields",label:"Sort field"},{name:"order",type:"select",label:"Order",options:[["desc","Descending"],["asc","Ascending"]]},{name:"title-field",type:"lookup",source:"collection-fields",label:"Title field"},{name:"columns",type:"number",label:"Card columns"},{name:"search",type:"checkbox",label:"Search (table)",default:!0},{name:"sortable",type:"checkbox",label:"Sortable (table)",default:!0},{name:"exportable",type:"checkbox",label:"CSV export"},{name:"page-size",type:"number",label:"Page size"},{name:"empty",type:"text",label:"Empty message"},{name:"scope",type:"select",label:"Scope",options:[["","All"],["mine","Current user"]]},{name:"cta",type:"lookup",source:"actions",label:"CTA action"},{name:"cta-label",type:"text",label:"CTA label"},{name:"cta-icon",type:"icon",label:"CTA icon"},{name:"cta-style",type:"select",label:"CTA style",options:[["primary","Primary"],["secondary","Secondary"],["success","Success"],["danger","Danger"],["warning","Warning"],["info","Info"]]},{name:"cta-confirm",type:"text",label:"CTA confirm text"},{name:"class",type:"text",label:"CSS class"},{name:"id",type:"text",label:"ID"}]},{name:"view",label:"View",icon:"eye",category:"Data",selfClosing:!0,container:!1,wrappable:!1,template:'[view slug="products" display="table" limit="50" /]',attrs:[{name:"slug",type:"lookup",source:"views",label:"View"},{name:"display",type:"select",label:"Display",options:[["table","Table"],["cards","Cards"],["list","List"],["accordion","Accordion"],["timeline","Timeline"],["block","Block"]]},{name:"limit",type:"number",label:"Limit"},{name:"fields",type:"text",label:"Fields (comma-separated)"},{name:"title-field",type:"text",label:"Title field"},{name:"columns",type:"number",label:"Card columns"},{name:"empty",type:"text",label:"Empty message"}]},{name:"block",label:"Block",icon:"layout",category:"Data",selfClosing:!0,container:!1,wrappable:!1,template:'[block template="post-card" title="My Post" /]',attrs:[{name:"template",type:"lookup",source:"blocks",label:"Template"}]},{name:"form",label:"Form",icon:"file-text",category:"Data",selfClosing:!0,container:!1,wrappable:!1,template:'[form name="contact" /]',attrs:[{name:"name",type:"lookup",source:"forms",label:"Form"},{name:"class",type:"text",label:"CSS class"},{name:"id",type:"text",label:"ID"}]},{name:"cta",label:"CTA",icon:"mouse-pointer",category:"Data",selfClosing:!0,container:!1,wrappable:!1,template:'[cta action="send-welcome" entry="abc123" style="primary" label="Send" /]',attrs:[{name:"action",type:"lookup",source:"actions",label:"Action"},{name:"entry",type:"text",label:"Entry ID"},{name:"style",type:"select",label:"Style",options:[["primary","Primary"],["secondary","Secondary"],["success","Success"],["danger","Danger"],["warning","Warning"],["info","Info"]]},{name:"icon",type:"icon",label:"Icon"},{name:"size",type:"select",label:"Size",options:[["","Default"],["sm","Small"],["lg","Large"]]},{name:"confirm",type:"text",label:"Confirm text"},{name:"label",type:"text",label:"Label"}]},{name:"menu",label:"Menu",icon:"menu",category:"Data",selfClosing:!0,container:!1,wrappable:!1,template:'[menu slug="legal" /]',attrs:[{name:"slug",type:"lookup",source:"menus",label:"Menu"},{name:"location",type:"lookup",source:"menu-locations",label:"Location slot"},{name:"variant",type:"text",label:"Variant"},{name:"class",type:"text",label:"CSS class"},{name:"depth",type:"number",label:"Max depth"},{name:"orientation",type:"select",label:"Orientation",options:[["","Menu default"],["horizontal","Horizontal"],["vertical","Vertical"]]},{name:"float",type:"select",label:"Floating",options:[["","Menu default"],["no","Render inline here"]]}]},{name:"theme-switcher",label:"Theme switcher",icon:"palette",category:"Components",selfClosing:!0,container:!1,wrappable:!1,template:"[theme-switcher /]",attrs:[{name:"position",type:"select",label:"Position",options:[["","Inline - where the tag is"],["top-left","Top left"],["top-right","Top right"],["left-center","Left, centred"],["right-center","Right, centred"],["bottom-left","Bottom left"],["bottom-right","Bottom right"]]},{name:"offset-x",type:"number",label:"Offset from the side (px, pinned only)"},{name:"offset-y",type:"number",label:"Offset from the top/bottom (px, pinned only)"},{name:"themes",type:"text",label:"Themes (comma list - blank for the site's)"}]},{name:"icon",label:"Icon",icon:"icon-pick",category:"Media",selfClosing:!0,container:!1,wrappable:!1,template:'[icon name="arrow-right" /]',attrs:[{name:"name",type:"icon",label:"Icon name"},{name:"size",type:"number",label:"Size (px)"},{name:"color",type:"colour",label:"Colour"},{name:"class",type:"text",label:"CSS class"}]},{name:"embed",label:"Embed",icon:"video",category:"Media",selfClosing:!0,container:!1,wrappable:!1,template:'[embed url="https://youtu.be/dQw4w9WgXcQ" /]',custom:"embed",attrs:[{name:"url",type:"url",label:"URL"},{name:"ratio",type:"select",label:"Aspect ratio",options:[["16:9","16:9"],["4:3","4:3"],["1:1","1:1"],["21:9","21:9"],["9:16","9:16"],["3:4","3:4"]]},{name:"title",type:"text",label:"Title / caption"},{name:"width",type:"text",label:"Width"},{name:"align",type:"select",label:"Alignment",options:[["left","Left"],["center","Centre"],["right","Right"]]},{name:"autoplay",type:"checkbox",label:"Autoplay"},{name:"loop",type:"checkbox",label:"Loop"},{name:"muted",type:"checkbox",label:"Muted"}]}],EDITOR_TOOLS=[{name:"image",label:"Image",icon:"image-add",category:"Media",custom:"image"},{name:"colourpick",label:"Colour picker",icon:"colour-picker",category:"Media",custom:"colourpick"},{name:"component",label:"Component",icon:"component",category:"Components",custom:"component"},{name:"scribe",label:"Scribe",icon:"scribe-insert",category:"Content",custom:"scribe"}];
@@ -1 +1 @@
1
- import{isPathAllowed as F}from"./admin-scope.js";export const TOOLS_FOLDER_TEXT="Tools",MANAGE_PLUGINS_URL="#/plugins",MANAGE_PLUGINS_ITEM={text:"Marketplace",url:MANAGE_PLUGINS_URL,icon:"store",permission:"plugins"};export function stripItemByUrl(e,t){const n=[];for(const r of e||[]){if(!r||r.url===t)continue;const s={...r};Array.isArray(r.items)&&r.items.length&&(s.items=stripItemByUrl(r.items,t)),n.push(s)}return n}export function managePluginsItem(){return{...MANAGE_PLUGINS_ITEM}}export const TOOLS_FOLDER_KEY="tools";const M=["countUrl","core","permission"],c=e=>String(e??"").toLowerCase();export function slugify(e){return c(e).trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}export function groupKeyFor(e,t){return`${c(e)}.${slugify(t)}`}export const GROUP_ICON="folder";const P=e=>e&&(e.type==="separator"||e.type==="spacer"),A=e=>e&&!P(e)&&!e.url;function O(e){return(e||[]).filter(Boolean).map(t=>Array.isArray(t.items)?{...t,items:O(t.items)}:{...t})}function E(e,t,n=[]){for(const r of e||[]){if(!r)continue;if(t(r))return[...n,r];const s=E(r.items,t,[...n,r]);if(s)return s}return null}function I(e,t,n=[],r=[]){for(const s of e||[])s&&(t(s)&&r.push([...n,s]),I(s.items,t,[...n,s],r));return r}const U=e=>!e.some(t=>t.hidden||t.restricted);function $(e,t){for(const n of e||[]){if(t(n))return n;const r=$(n?.items,t);if(r)return r}return null}export function findFolder(e,t){if(!t)return null;const n=c(t);return $(e,r=>A(r)&&c(r.key)===n)||$(e,r=>A(r)&&!r.key&&c(r.text)===n)}function b(e,t){const n={...t,...e};for(const r of M)t[r]!==void 0&&(n[r]=t[r]);return!(Array.isArray(e.items)&&e.items.length)&&Array.isArray(t.items)&&(n.items=t.items),e.text||(n.text=t.text),!e.icon&&t.icon&&(n.icon=t.icon),delete n.ref,delete n.folder,delete n.group,n}function _(e,{persisted:t,orphan:n=!1,node:r=null}){const s=e?.item||{};return{persisted:t,orphan:n,source:s.source||(s.core?"Built in":""),folder:e?.folder||TOOLS_FOLDER_KEY,defaults:n?{text:null,icon:null}:{text:s.text||"",icon:s.icon||""},...n&&r&&{label:r.text||r.url},...(e?.group||e?.item?.group)&&{group:e.group||e.item.group}}}export function placeTools(e,t,{forEditor:n=!1}={}){const r=O(e),s=(t||[]).filter(o=>o&&o.item&&typeof o.item.text=="string"),d=new Map;for(const o of s)o.item.url&&!d.has(o.item.url)&&d.set(o.item.url,o);const y=new Set,m=o=>{const i=[];for(const u of o){const h=u.url?d.get(u.url):null;if(h&&!y.has(u.url)){y.add(u.url);const g=b(u,h.item);Array.isArray(u.items)&&u.items.length?g.items=m(u.items):n&&(g.items=[]),n&&(g.$tool=_(h,{persisted:!0})),i.push(g);continue}if(u.ref){if(!n)continue;const{ref:g,...f}=u;i.push({...f,items:m(u.items||[]),$tool:_(null,{persisted:!0,orphan:!0,node:u})});continue}Array.isArray(u.items)&&(u.items=m(u.items)),i.push(u)}return i},a=m(r),T=new Set,L=o=>{for(const i of o||[])i.hidden&&i.text&&!i.url&&!(Array.isArray(i.items)&&i.items.length)&&T.add(c(i.text)),L(i.items)};L(a);let x=null;const S=()=>{const o=findFolder(a,TOOLS_FOLDER_KEY);if(o)return o;if(!x){x={text:TOOLS_FOLDER_TEXT,key:TOOLS_FOLDER_KEY,icon:"tool",items:[],synthesised:!0};const i=a.findIndex(u=>["system","documentation"].includes(c(u?.key||u?.text)));a.splice(i===-1?a.length:i,0,x)}return x},l=new Set,k=(o,i)=>{const u=groupKeyFor(o.key||slugify(o.text),i),h=$(a,p=>A(p)&&c(p.key)===u)||(o.items||[]).find(p=>A(p)&&(!p.key||c(p.key)===u)&&c(p.text)===c(i));if(h)return h;const g={text:String(i),key:u,icon:GROUP_ICON,items:[],synthesised:!0},f=o.items.findIndex(p=>l.has(p));return o.items.splice(f===-1?o.items.length:f,0,g),g};for(const o of s){const{item:i}=o;if(i.url&&y.has(i.url))continue;i.url&&y.add(i.url);const u=T.has(c(i.text));if(u&&!n)continue;const h=b({},i);n&&(h.items=[],h.$tool=_(o,{persisted:!1}),u&&(h.hidden=!0));let f=(o.parent?findFolder(a,o.parent)||$(a,w=>!P(w)&&c(w.text)===c(o.parent)):null)||findFolder(a,o.folder||i.folder)||S();f.items=Array.isArray(f.items)?f.items:[];const p=typeof(o.group||i.group)=="string"?(o.group||i.group).trim():"";p&&slugify(p)&&(f=k(f,p),f.items=Array.isArray(f.items)?f.items:[]),f.items.push(h),l.add(h)}return a}export const LOCKED_TOOLS=[{folder:"overview",item:{text:"Dashboard",url:"#/",icon:"home"}},{folder:"content",item:{text:"Menus",url:"#/menus",icon:"menu",permission:"menus"}},{folder:"system",item:{text:"Users",url:"#/users",icon:"users",permission:"users"}},{folder:"system",item:{text:"My Profile",url:"#/my-profile",icon:"user"}}];export function isLockedUrl(e){return LOCKED_TOOLS.some(t=>t.item.url===e)}export function ensureLockedTools(e,{leaveInHiddenFolders:t=!1}={}){const n=O(e);for(const{folder:r,item:s}of LOCKED_TOOLS){const d=I(n,l=>l?.url===s.url),y=d.find(l=>!l.some(k=>k.restricted));if(!y&&d.length)continue;const m=y?.at(-1);if(m){if(m.hidden&&delete m.hidden,!y.slice(0,-1).some(o=>o.hidden)||t)continue;const k=y.at(-2);k.items=k.items.filter(o=>o!==m)}const a=c(r),T=I(n,l=>A(l)&&c(l.key)===a).filter(U),L=I(n,l=>A(l)&&!l.key&&c(l.text)===a).filter(U),x=(T[0]||L[0])?.at(-1)||null,S=m||{...s};x?(x.items=Array.isArray(x.items)?x.items:[],x.items.push(S)):n.push(S)}return n}export function insertFoldersBeforeSystem(e,t){const n=(t||[]).filter(Boolean);if(!n.length)return e;const r=["system","documentation"],s=e.findIndex(d=>r.includes(c(d?.key||d?.text)));return s===-1?[...e,...n]:[...e.slice(0,s),...n,...e.slice(s)]}export function canAny(e,t){return t?!e||!e.length?!1:e.includes(t)?!0:["read","create","update","delete"].some(n=>e.includes(`${t}.${n}`)):!0}export function filterByPermission(e,t,n=null){const r=[];for(const s of e){if(s.hidden||s.permission&&!canAny(t,s.permission))continue;const d=typeof s.url=="string"?s.url:"";if(n&&d.startsWith("#/")&&!F(n,d.slice(1)))continue;const y=Array.isArray(s.items)&&s.items.length>0,m=y?filterByPermission(s.items,t,n):[];y&&!m.length&&!d||r.push({...s,items:m})}return r}export function sidebarStateKey(e,t=null){const n=String(e?.text||"").replace(/\s*\(\d+\)\s*$/,"").toLowerCase().replace(/\s+/g,"-"),r=String(e?.url||"").match(/^#\/projects\/([^/]+)/);let s="";if(r)try{s=`project-${decodeURIComponent(r[1])}.`}catch{s=`project-${r[1]}.`}return t?e?.key?`sidebar.expanded.key.${c(e.key)}`:`${t}/${s}${n}`:`sidebar.expanded.${s}${n}`}export function pruneEmptyToolsFolder(e){return(e||[]).filter(t=>A(t)?!(c(t.key)===TOOLS_FOLDER_KEY||!t.key&&t.text===TOOLS_FOLDER_TEXT)||Array.isArray(t.items)&&t.items.length>0:!0)}
1
+ import{isPathAllowed as F}from"./admin-scope.js";export const TOOLS_FOLDER_TEXT="Tools",MANAGE_PLUGINS_URL="#/plugins",MANAGE_PLUGINS_ITEM={text:"Marketplace",url:MANAGE_PLUGINS_URL,icon:"store",permission:"plugins"};export function stripItemByUrl(e,t){const n=[];for(const r of e||[]){if(!r||r.url===t)continue;const s={...r};Array.isArray(r.items)&&r.items.length&&(s.items=stripItemByUrl(r.items,t)),n.push(s)}return n}export function managePluginsItem(){return{...MANAGE_PLUGINS_ITEM}}export const TOOLS_FOLDER_KEY="tools";const M=["countUrl","core","permission"],c=e=>String(e??"").toLowerCase();export function slugify(e){return c(e).trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}export function groupKeyFor(e,t){return`${c(e)}.${slugify(t)}`}export const GROUP_ICON="folder";const P=e=>e&&(e.type==="separator"||e.type==="spacer"||e.type==="theme-switcher"),A=e=>e&&!P(e)&&!e.url;function w(e){return(e||[]).filter(Boolean).map(t=>Array.isArray(t.items)?{...t,items:w(t.items)}:{...t})}function E(e,t,n=[]){for(const r of e||[]){if(!r)continue;if(t(r))return[...n,r];const s=E(r.items,t,[...n,r]);if(s)return s}return null}function I(e,t,n=[],r=[]){for(const s of e||[])s&&(t(s)&&r.push([...n,s]),I(s.items,t,[...n,s],r));return r}const U=e=>!e.some(t=>t.hidden||t.restricted);function $(e,t){for(const n of e||[]){if(t(n))return n;const r=$(n?.items,t);if(r)return r}return null}export function findFolder(e,t){if(!t)return null;const n=c(t);return $(e,r=>A(r)&&c(r.key)===n)||$(e,r=>A(r)&&!r.key&&c(r.text)===n)}function b(e,t){const n={...t,...e};for(const r of M)t[r]!==void 0&&(n[r]=t[r]);return!(Array.isArray(e.items)&&e.items.length)&&Array.isArray(t.items)&&(n.items=t.items),e.text||(n.text=t.text),!e.icon&&t.icon&&(n.icon=t.icon),delete n.ref,delete n.folder,delete n.group,n}function O(e,{persisted:t,orphan:n=!1,node:r=null}){const s=e?.item||{};return{persisted:t,orphan:n,source:s.source||(s.core?"Built in":""),folder:e?.folder||TOOLS_FOLDER_KEY,defaults:n?{text:null,icon:null}:{text:s.text||"",icon:s.icon||""},...n&&r&&{label:r.text||r.url},...(e?.group||e?.item?.group)&&{group:e.group||e.item.group}}}export function placeTools(e,t,{forEditor:n=!1}={}){const r=w(e),s=(t||[]).filter(o=>o&&o.item&&typeof o.item.text=="string"),m=new Map;for(const o of s)o.item.url&&!m.has(o.item.url)&&m.set(o.item.url,o);const y=new Set,d=o=>{const i=[];for(const u of o){const h=u.url?m.get(u.url):null;if(h&&!y.has(u.url)){y.add(u.url);const g=b(u,h.item);Array.isArray(u.items)&&u.items.length?g.items=d(u.items):n&&(g.items=[]),n&&(g.$tool=O(h,{persisted:!0})),i.push(g);continue}if(u.ref){if(!n)continue;const{ref:g,...f}=u;i.push({...f,items:d(u.items||[]),$tool:O(null,{persisted:!0,orphan:!0,node:u})});continue}Array.isArray(u.items)&&(u.items=d(u.items)),i.push(u)}return i},a=d(r),T=new Set,L=o=>{for(const i of o||[])i.hidden&&i.text&&!i.url&&!(Array.isArray(i.items)&&i.items.length)&&T.add(c(i.text)),L(i.items)};L(a);let x=null;const S=()=>{const o=findFolder(a,TOOLS_FOLDER_KEY);if(o)return o;if(!x){x={text:TOOLS_FOLDER_TEXT,key:TOOLS_FOLDER_KEY,icon:"tool",items:[],synthesised:!0};const i=a.findIndex(u=>["system","documentation"].includes(c(u?.key||u?.text)));a.splice(i===-1?a.length:i,0,x)}return x},l=new Set,k=(o,i)=>{const u=groupKeyFor(o.key||slugify(o.text),i),h=$(a,p=>A(p)&&c(p.key)===u)||(o.items||[]).find(p=>A(p)&&(!p.key||c(p.key)===u)&&c(p.text)===c(i));if(h)return h;const g={text:String(i),key:u,icon:GROUP_ICON,items:[],synthesised:!0},f=o.items.findIndex(p=>l.has(p));return o.items.splice(f===-1?o.items.length:f,0,g),g};for(const o of s){const{item:i}=o;if(i.url&&y.has(i.url))continue;i.url&&y.add(i.url);const u=T.has(c(i.text));if(u&&!n)continue;const h=b({},i);n&&(h.items=[],h.$tool=O(o,{persisted:!1}),u&&(h.hidden=!0));let f=(o.parent?findFolder(a,o.parent)||$(a,_=>!P(_)&&c(_.text)===c(o.parent)):null)||findFolder(a,o.folder||i.folder)||S();f.items=Array.isArray(f.items)?f.items:[];const p=typeof(o.group||i.group)=="string"?(o.group||i.group).trim():"";p&&slugify(p)&&(f=k(f,p),f.items=Array.isArray(f.items)?f.items:[]),f.items.push(h),l.add(h)}return a}export const LOCKED_TOOLS=[{folder:"overview",item:{text:"Dashboard",url:"#/",icon:"home"}},{folder:"content",item:{text:"Menus",url:"#/menus",icon:"menu",permission:"menus"}},{folder:"system",item:{text:"Users",url:"#/users",icon:"users",permission:"users"}},{folder:"system",item:{text:"My Profile",url:"#/my-profile",icon:"user"}}];export function isLockedUrl(e){return LOCKED_TOOLS.some(t=>t.item.url===e)}export function ensureLockedTools(e,{leaveInHiddenFolders:t=!1}={}){const n=w(e);for(const{folder:r,item:s}of LOCKED_TOOLS){const m=I(n,l=>l?.url===s.url),y=m.find(l=>!l.some(k=>k.restricted));if(!y&&m.length)continue;const d=y?.at(-1);if(d){if(d.hidden&&delete d.hidden,!y.slice(0,-1).some(o=>o.hidden)||t)continue;const k=y.at(-2);k.items=k.items.filter(o=>o!==d)}const a=c(r),T=I(n,l=>A(l)&&c(l.key)===a).filter(U),L=I(n,l=>A(l)&&!l.key&&c(l.text)===a).filter(U),x=(T[0]||L[0])?.at(-1)||null,S=d||{...s};x?(x.items=Array.isArray(x.items)?x.items:[],x.items.push(S)):n.push(S)}return n}export function insertFoldersBeforeSystem(e,t){const n=(t||[]).filter(Boolean);if(!n.length)return e;const r=["system","documentation"],s=e.findIndex(m=>r.includes(c(m?.key||m?.text)));return s===-1?[...e,...n]:[...e.slice(0,s),...n,...e.slice(s)]}export function canAny(e,t){return t?!e||!e.length?!1:e.includes(t)?!0:["read","create","update","delete"].some(n=>e.includes(`${t}.${n}`)):!0}export function filterByPermission(e,t,n=null){const r=[];for(const s of e){if(s.hidden||s.permission&&!canAny(t,s.permission))continue;const m=typeof s.url=="string"?s.url:"";if(n&&m.startsWith("#/")&&!F(n,m.slice(1)))continue;const y=Array.isArray(s.items)&&s.items.length>0,d=y?filterByPermission(s.items,t,n):[];y&&!d.length&&!m||r.push({...s,items:d})}return r}export function sidebarStateKey(e,t=null){const n=String(e?.text||"").replace(/\s*\(\d+\)\s*$/,"").toLowerCase().replace(/\s+/g,"-"),r=String(e?.url||"").match(/^#\/projects\/([^/]+)/);let s="";if(r)try{s=`project-${decodeURIComponent(r[1])}.`}catch{s=`project-${r[1]}.`}return t?e?.key?`sidebar.expanded.key.${c(e.key)}`:`${t}/${s}${n}`:`sidebar.expanded.${s}${n}`}export function pruneEmptyToolsFolder(e){return(e||[]).filter(t=>A(t)?!(c(t.key)===TOOLS_FOLDER_KEY||!t.key&&t.text===TOOLS_FOLDER_TEXT)||Array.isArray(t.items)&&t.items.length>0:!0)}
@@ -1,4 +1,4 @@
1
- import{api as g,getUser as L}from"../api.js";import{isPathAllowed as ue}from"./admin-scope.js";import{colourToCss as V,applyFloat as fe,applyHighlight as me,itemHighlightAttrs as pe}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{placeTools as he,ensureLockedTools as ge,managePluginsItem as be,stripItemByUrl as ye,insertFoldersBeforeSystem as ve,pruneEmptyToolsFolder as xe,sidebarStateKey as X,canAny as O,filterByPermission as $e,MANAGE_PLUGINS_URL as Z}from"./sidebar-grouping.js";import{arrangeButtonMarkup as we,canArrange as ke,startArrange as Se}from"./sidebar-arrange.js";import{collapseButtonMarkup as qe,wireSidebarSize as Ce,COLLAPSED_WIDTH as Ae}from"./sidebar-size.js";import{FREQUENT_MAX as Y,addFrequent as Fe,frequentFolder as Le,moveFrequent as J,readFrequent as z,readFrequentHidden as Ee,removeFrequent as Te,saveFrequent as Me,syncFrequent as He,writeFrequent as Ne,writeFrequentHidden as Q}from"./sidebar-frequent.js";import{hotkeyFor as ee,hotkeyTitle as je,setHotkeyTree as Be}from"./hotkeys-ui.js";import{hotkeyLabel as Pe}from"./hotkeys.js";function m(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const Re=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Menus",url:"#/menus",icon:"menu",permission:"menus"}];async function _e(e){if(!O(e,"projects"))return[];let n=[];try{n=await g.projects.listWithCounts()}catch{return[]}if(!Array.isArray(n))return[];const s=[{key:"pages",text:"Pages",icon:"file-text",path:"/pages"},{key:"collections",text:"Collections",icon:"database",path:"/collections"},{key:"forms",text:"Forms",icon:"layout",path:"/forms"},{key:"actions",text:"Actions",icon:"zap",path:"/actions"},{key:"menus",text:"Menus",icon:"menu",path:"/menus"},{key:"blocks",text:"Blocks",icon:"box",path:"/blocks"},{key:"components",text:"Components",icon:"component",path:"/components"},{key:"views",text:"Views",icon:"eye",path:"/views"},{key:"roles",text:"Roles",icon:"shield",path:"/roles"},{key:"users",text:"Users",icon:"users",path:"/users"},{key:"apis",text:"APIs",icon:"code",path:"/apis"}];return n.map(t=>{const o="#/projects/"+encodeURIComponent(t.slug),a=t.counts||{},d=[{text:"Overview",url:o,icon:t.icon||"folder"}];for(const c of s){const r=Number(a[c.key])||0;r<=0||d.push({text:`${c.text} (${r})`,url:o+c.path,icon:c.icon})}d.push({text:"Settings",url:o+"/settings",icon:"settings"});const u=t.enabled===!1;return{text:t.name||t.slug,key:`project.${t.slug}`,icon:t.icon||"folder",items:d,...u&&{colour:"neutral",badge:{text:"off"}}}})}function Ie(e,n){for(const s of e)if(!s.hidden&&(s.key==="projects"||!s.key&&(s.text||"").toLowerCase()==="projects")){s.items=Array.isArray(s.items)?s.items.slice():[],s.items.push(...n);return}}function te(e,n){const s=se.includes(n.tone)?n.tone:"info",t=n.label||String(n.count);n.details?j.set(e,n.details):j.delete(e);const i=!n.inventory&&A.some(o=>o.url===e&&!o.inventory)?" data-live":"";return`<span class="sidebar-badge sidebar-badge--${s}" data-count-for="${m(e)}"${i} data-badge-label="${m(t)}" aria-label="${m(t)}">${m(String(n.count))}</span>`}function ne(e){for(const n of e.querySelectorAll("details")){const s=n.querySelector(":scope > summary");if(!s)continue;let t=0,i=!1;const o=[];for(const r of n.querySelectorAll(":scope > .sidebar-children .sidebar-badge[data-live]")){const f=Number(r.textContent)||0;f&&(t+=f,i||(i=r.classList.contains("sidebar-badge--danger")),o.push({text:r.closest("a")?.querySelector(".sidebar-text")?.textContent.trim()??"",meta:r.dataset.badgeLabel,tone:r.classList.contains("sidebar-badge--danger")?"danger":void 0}))}const a=`folder:${n.dataset.stateKey||s.textContent.trim()}`;let d=s.querySelector(":scope > .sidebar-badge--sum");if(!t){d?.remove(),j.delete(a);continue}d||(d=document.createElement("span"),s.querySelector(".sidebar-text")?.after(d));const u=s.querySelector(".sidebar-text")?.textContent.trim()||"This folder",c=o.map(r=>`${r.text}: ${r.meta}`).join(" \xB7 ");d.className=`sidebar-badge sidebar-badge--sum sidebar-badge--${i?"danger":"info"}`,d.dataset.countFor=a,d.dataset.badgeLabel=c,d.setAttribute("aria-label",c),d.textContent=String(t),j.set(a,{title:u,items:o})}}const j=new Map;function Oe(e,n){if(!e.url||!n)return"";const s=n[e.url];return!s||!(s.count>0)&&!s.showZero?"":te(e.url,s)}const se=["info","attention","danger","live"],Ue={"#/pages":["page","pages"],"#/media":["file in the media library","files in the media library"],"#/menus":["menu","menus"],"#/context-menus":["context menu","context menus"],"#/theme":["element override in the theme","element overrides in the theme"],"#/collections":["collection","collections"],"#/forms":["form","forms"],"#/views":["view","views"],"#/actions":["action","actions"],"#/blocks":["block","blocks"],"#/components":["component","components"],"#/users":["user","users"],"#/plugins":["plugin installed","plugins installed"],"#/system/notifications":["unread notification","unread notifications"]};function W(e,{fallbackLabel:n="",tone:s="info"}={}){const t=Number(typeof e=="object"&&e!==null?e.count:e);if(!Number.isFinite(t)||t<0)return null;const i=typeof e?.label=="string"&&e.label||(n?`${t} - ${n}`:String(t)),o={count:Math.round(t),label:i,tone:se.includes(e?.tone)?e.tone:s};return e?.details&&typeof e.details=="object"&&(o.details=e.details),e?.inventory===!0&&(o.inventory=!0),e?.showZero===!0&&(o.showZero=!0),o}function oe(e,n,s=null,{inFrequent:t=!1}={}){if(e&&e.type==="separator")return'<hr class="sidebar-divider">';if(e&&e.type==="spacer"){const y=["xs","sm","md","lg","xl","flex"].includes(e.size)?e.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${m(y)}" aria-hidden="true"></div>`}const i=String(e.text||"?").trim().charAt(0).toUpperCase()||"?",o=e.icon?`<span data-icon="${m(e.icon)}"></span>`:`<span class="sidebar-initial" aria-hidden="true">${m(i)}</span>`,a=m(e.text||""),d=e.url?m(e.url):"",u=Array.isArray(e.items)&&e.items.length>0,c=e.colour?V(e.colour):"",r=pe(e),f=[r.hover&&` data-menu-item-hover="${m(r.hover)}"`,r.active&&` data-menu-item-active="${m(r.active)}"`].filter(Boolean).join(""),x=[c&&`color:${m(c)}`,r.accent&&`--dm-menu-highlight:${m(r.accent)}`].filter(Boolean).join(";"),w=x?` style="${x}"`:"",k=e.url?je(e.url,e.text||"",{inFrequent:t}):"",q=k?` title="${m(k)}"`:"";let F="";if(e.badge&&e.badge.text!=null&&e.badge.text!==""){const y=e.badge.variant?V(e.badge.variant):"";F=`<span class="dm-menu-badge"${y?` style="background:${m(y)};color:var(--dm-text-inverse,#fff)"`:""}>${m(String(e.badge.text))}</span>`}if(e.frequent&&e.empty){const y=X(e,s),C=S.get(y)!==!1;return`<details data-state-key="${m(y)}"${C?" open":""} data-frequent data-frequent-empty>
1
+ import{api as g,getUser as L}from"../api.js";import{isPathAllowed as ue}from"./admin-scope.js";import{colourToCss as V,applyFloat as fe,applyHighlight as me,itemHighlightAttrs as pe}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{placeTools as he,ensureLockedTools as ge,managePluginsItem as be,stripItemByUrl as ye,insertFoldersBeforeSystem as ve,pruneEmptyToolsFolder as xe,sidebarStateKey as X,canAny as O,filterByPermission as $e,MANAGE_PLUGINS_URL as Z}from"./sidebar-grouping.js";import{arrangeButtonMarkup as we,canArrange as ke,startArrange as Se}from"./sidebar-arrange.js";import{collapseButtonMarkup as qe,wireSidebarSize as Ce,COLLAPSED_WIDTH as Ae}from"./sidebar-size.js";import{FREQUENT_MAX as Y,addFrequent as Fe,frequentFolder as Le,moveFrequent as J,readFrequent as z,readFrequentHidden as Ee,removeFrequent as Te,saveFrequent as Me,syncFrequent as He,writeFrequent as Ne,writeFrequentHidden as Q}from"./sidebar-frequent.js";import{hotkeyFor as ee,hotkeyTitle as je,setHotkeyTree as Be}from"./hotkeys-ui.js";import{hotkeyLabel as Pe}from"./hotkeys.js";function m(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const Re=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Menus",url:"#/menus",icon:"menu",permission:"menus"}];async function _e(e){if(!O(e,"projects"))return[];let n=[];try{n=await g.projects.listWithCounts()}catch{return[]}if(!Array.isArray(n))return[];const s=[{key:"pages",text:"Pages",icon:"file-text",path:"/pages"},{key:"collections",text:"Collections",icon:"database",path:"/collections"},{key:"forms",text:"Forms",icon:"layout",path:"/forms"},{key:"actions",text:"Actions",icon:"zap",path:"/actions"},{key:"menus",text:"Menus",icon:"menu",path:"/menus"},{key:"blocks",text:"Blocks",icon:"box",path:"/blocks"},{key:"components",text:"Components",icon:"component",path:"/components"},{key:"views",text:"Views",icon:"eye",path:"/views"},{key:"roles",text:"Roles",icon:"shield",path:"/roles"},{key:"users",text:"Users",icon:"users",path:"/users"},{key:"apis",text:"APIs",icon:"code",path:"/apis"}];return n.map(t=>{const o="#/projects/"+encodeURIComponent(t.slug),a=t.counts||{},d=[{text:"Overview",url:o,icon:t.icon||"folder"}];for(const c of s){const r=Number(a[c.key])||0;r<=0||d.push({text:`${c.text} (${r})`,url:o+c.path,icon:c.icon})}d.push({text:"Settings",url:o+"/settings",icon:"settings"});const u=t.enabled===!1;return{text:t.name||t.slug,key:`project.${t.slug}`,icon:t.icon||"folder",items:d,...u&&{colour:"neutral",badge:{text:"off"}}}})}function Ie(e,n){for(const s of e)if(!s.hidden&&(s.key==="projects"||!s.key&&(s.text||"").toLowerCase()==="projects")){s.items=Array.isArray(s.items)?s.items.slice():[],s.items.push(...n);return}}function te(e,n){const s=se.includes(n.tone)?n.tone:"info",t=n.label||String(n.count);n.details?j.set(e,n.details):j.delete(e);const i=!n.inventory&&A.some(o=>o.url===e&&!o.inventory)?" data-live":"";return`<span class="sidebar-badge sidebar-badge--${s}" data-count-for="${m(e)}"${i} data-badge-label="${m(t)}" aria-label="${m(t)}">${m(String(n.count))}</span>`}function ne(e){for(const n of e.querySelectorAll("details")){const s=n.querySelector(":scope > summary");if(!s)continue;let t=0,i=!1;const o=[];for(const r of n.querySelectorAll(":scope > .sidebar-children .sidebar-badge[data-live]")){const f=Number(r.textContent)||0;f&&(t+=f,i||(i=r.classList.contains("sidebar-badge--danger")),o.push({text:r.closest("a")?.querySelector(".sidebar-text")?.textContent.trim()??"",meta:r.dataset.badgeLabel,tone:r.classList.contains("sidebar-badge--danger")?"danger":void 0}))}const a=`folder:${n.dataset.stateKey||s.textContent.trim()}`;let d=s.querySelector(":scope > .sidebar-badge--sum");if(!t){d?.remove(),j.delete(a);continue}d||(d=document.createElement("span"),s.querySelector(".sidebar-text")?.after(d));const u=s.querySelector(".sidebar-text")?.textContent.trim()||"This folder",c=o.map(r=>`${r.text}: ${r.meta}`).join(" \xB7 ");d.className=`sidebar-badge sidebar-badge--sum sidebar-badge--${i?"danger":"info"}`,d.dataset.countFor=a,d.dataset.badgeLabel=c,d.setAttribute("aria-label",c),d.textContent=String(t),j.set(a,{title:u,items:o})}}const j=new Map;function Oe(e,n){if(!e.url||!n)return"";const s=n[e.url];return!s||!(s.count>0)&&!s.showZero?"":te(e.url,s)}const se=["info","attention","danger","live"],Ue={"#/pages":["page","pages"],"#/media":["file in the media library","files in the media library"],"#/menus":["menu","menus"],"#/context-menus":["context menu","context menus"],"#/theme":["element override in the theme","element overrides in the theme"],"#/collections":["collection","collections"],"#/forms":["form","forms"],"#/views":["view","views"],"#/actions":["action","actions"],"#/blocks":["block","blocks"],"#/components":["component","components"],"#/users":["user","users"],"#/plugins":["plugin installed","plugins installed"],"#/system/notifications":["unread notification","unread notifications"]};function W(e,{fallbackLabel:n="",tone:s="info"}={}){const t=Number(typeof e=="object"&&e!==null?e.count:e);if(!Number.isFinite(t)||t<0)return null;const i=typeof e?.label=="string"&&e.label||(n?`${t} - ${n}`:String(t)),o={count:Math.round(t),label:i,tone:se.includes(e?.tone)?e.tone:s};return e?.details&&typeof e.details=="object"&&(o.details=e.details),e?.inventory===!0&&(o.inventory=!0),e?.showZero===!0&&(o.showZero=!0),o}function oe(e,n,s=null,{inFrequent:t=!1}={}){if(e&&e.type==="separator")return'<hr class="sidebar-divider">';if(e&&e.type==="theme-switcher")return"";if(e&&e.type==="spacer"){const y=["xs","sm","md","lg","xl","flex"].includes(e.size)?e.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${m(y)}" aria-hidden="true"></div>`}const i=String(e.text||"?").trim().charAt(0).toUpperCase()||"?",o=e.icon?`<span data-icon="${m(e.icon)}"></span>`:`<span class="sidebar-initial" aria-hidden="true">${m(i)}</span>`,a=m(e.text||""),d=e.url?m(e.url):"",u=Array.isArray(e.items)&&e.items.length>0,c=e.colour?V(e.colour):"",r=pe(e),f=[r.hover&&` data-menu-item-hover="${m(r.hover)}"`,r.active&&` data-menu-item-active="${m(r.active)}"`].filter(Boolean).join(""),x=[c&&`color:${m(c)}`,r.accent&&`--dm-menu-highlight:${m(r.accent)}`].filter(Boolean).join(";"),w=x?` style="${x}"`:"",k=e.url?je(e.url,e.text||"",{inFrequent:t}):"",q=k?` title="${m(k)}"`:"";let F="";if(e.badge&&e.badge.text!=null&&e.badge.text!==""){const y=e.badge.variant?V(e.badge.variant):"";F=`<span class="dm-menu-badge"${y?` style="background:${m(y)};color:var(--dm-text-inverse,#fff)"`:""}>${m(String(e.badge.text))}</span>`}if(e.frequent&&e.empty){const y=X(e,s),C=S.get(y)!==!1;return`<details data-state-key="${m(y)}"${C?" open":""} data-frequent data-frequent-empty>
2
2
  <summary${w}>${o} <span class="sidebar-text">${a}</span></summary>
3
3
  <div class="sidebar-children"><p class="sidebar-frequent-empty">Right-click any Tool and choose <strong>Add to Frequent</strong> to keep it here.</p></div>
4
4
  </details>`}if(u){const y=X(e,s),C=S.get(y)!==!1,B=e.items.map(T=>oe(T,n,y,{inFrequent:!!e.frequent})).join(""),P=e.frequent?' data-frequent title="Your Frequent Tools - right-click any Tool to add or remove it"':"";return`<details data-state-key="${m(y)}"${C?" open":""}${P}>
@@ -50,7 +50,7 @@
50
50
  round a row means it will go inside that row (the navbar shows nested items as a dropdown,
51
51
  the admin sidebar as a folder inside a folder). Press Esc while dragging to cancel. On a
52
52
  touch screen, drag by the grip too. Right-click an item for add, move, indent, duplicate,
53
- hide and remove. Separators and spacers are items too.</li>
53
+ hide and remove. Separators, spacers and the theme switcher (the site's theme dots, among the links) are items too.</li>
54
54
  <li><strong>Behaviour</strong> - colour scheme, sticky, fixed, static or floating, horizontal
55
55
  or vertical, alignment, and the hover and current-page highlight. A single item can
56
56
  override the highlight in its own editor.</li>
@@ -81,6 +81,10 @@
81
81
  <td><code>[spacer size="16" /]</code></td>
82
82
  <td>Vertical whitespace block (px height)</td>
83
83
  </tr>
84
+ <tr>
85
+ <td><code>[theme-switcher /]</code></td>
86
+ <td>The site's theme dots, where the tag stands - or pinned with <code>position</code> (<code>top-left</code> … <code>bottom-right</code>), <code>offset-x</code> / <code>offset-y</code>; <code>themes</code> narrows the list. Replaces the floating switcher on its page</td>
87
+ </tr>
84
88
  <tr>
85
89
  <td><code>[timeline]...[/timeline]</code></td>
86
90
  <td>Progression timeline; use <code>[event title="..." date="..."
@@ -95,7 +95,8 @@
95
95
  .lyx-shape-nav, .lyx-shape-foot { height: 5px; background: color-mix(in srgb, var(--dm-text-muted) 55%, transparent); }
96
96
  .lyx-shape-body { flex: 1; display: flex; justify-content: center; padding: 3px 0; gap: 2px; }
97
97
  .lyx-shape-col { background: color-mix(in srgb, var(--dm-primary) 45%, transparent); border-radius: 1px; }
98
- .lyx-shape-side { width: 18%; background: color-mix(in srgb, var(--dm-text-muted) 35%, transparent); border-radius: 1px; }
98
+ .lyx-shape-side { order: -1; width: 18%; background: color-mix(in srgb, var(--dm-text-muted) 35%, transparent); border-radius: 1px; }
99
+ .lyx-shape-side.is-right { order: 1; }
99
100
  .lyx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
100
101
  .lyx-main strong, .lyx-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
101
102
  .lyx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
@@ -26,6 +26,7 @@
26
26
  <button type="button" class="btn btn-sm btn-secondary" id="me-add-item" data-on-click="addItem"><span data-icon="plus"></span> Add item</button>
27
27
  <button type="button" class="btn btn-sm btn-ghost" id="me-add-separator" data-on-click="addSeparator"><span data-icon="minus"></span> Add separator</button>
28
28
  <button type="button" class="btn btn-sm btn-ghost" id="me-add-spacer" data-on-click="addSpacer"><span data-icon="move"></span> Add spacer</button>
29
+ <button type="button" class="btn btn-sm btn-ghost" id="me-add-switcher" data-on-click="addSwitcher" title="The site's theme dots, among the links"><span data-icon="palette"></span> Add theme switcher</button>
29
30
  <span class="mex-spacer"></span>
30
31
  <small class="mex-note"><span data-bind-text="itemCount.value"></span> items</small>
31
32
  <span data-help-title="Items" data-help="Click an item to edit its label, link, icon, who sees it, badge and look. Right-click it (or ⋮, or Shift+F10) to add an item or a sub-item below it, move, indent, duplicate, hide or remove it. Drag the grip to move it, or drop it onto another item to nest it. With an item focused: Enter edits, Alt+Up / Down moves, Alt+Right / Left indents, Delete removes. Changes are kept only when you Save."></span>
@@ -1,7 +1,7 @@
1
1
  import{api as L,apiRequest as Qe}from"../api.js";import{addBannerAction as Xe}from"../lib/plugin-chrome.js";import{helpPopovers as Me}from"../lib/help-popover.js";import{onceClosed as $e,openMenuAt as Oe,sidebarCount as Ze}from"../lib/tool-kit.js";import{FILTERS as et,WIDTHS as tt,WIDTH_HINTS as at,WIDTH_LABELS as st,WIDTH_SHARE as De,arrangeLayouts as lt,checkLayout as nt,checkOptions as ot,formFromPreset as it}from"../lib/layouts-arrange.js";const ae=/^\/layouts\/?(\?.*)?$/,Te="#/layouts",se="layouts.draft",N="layouts.options.draft",le="layouts.filter",Le=(s="")=>`
2
2
  <span class="lyx-shape" aria-hidden="true" data-bind-style-background-color="bg${s}">
3
3
  <span class="lyx-shape-nav" data-bind-hidden="!hasNav${s}"></span>
4
- <span class="lyx-shape-body"><span class="lyx-shape-col" data-bind-style-width="share${s}"></span><span class="lyx-shape-side" data-bind-hidden="!hasSide${s}"></span></span>
4
+ <span class="lyx-shape-body"><span class="lyx-shape-col" data-bind-style-width="share${s}"></span><span class="lyx-shape-side" data-bind-hidden="!hasSide${s}" data-bind-class="sideRight${s} && 'is-right'"></span></span>
5
5
  <span class="lyx-shape-foot" data-bind-hidden="!hasFoot${s}"></span>
6
6
  </span>`,rt=`
7
7
  <div class="lyx-row" data-key="{{key}}" tabindex="0">
@@ -13,11 +13,11 @@ import{api as L,apiRequest as Qe}from"../api.js";import{addBannerAction as Xe}fr
13
13
  <span class="lyx-chip" data-bind-class="usedTone" title="Pages that use this layout">{{usedText}}</span>
14
14
  </span>
15
15
  <button type="button" class="lyx-kebab" aria-label="Actions for {{label}}" title="Actions">\u22EE</button>
16
- </div>`,W=s=>String(s??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");function Be(s){const r=document.createElement("template");return r.innerHTML=s.trim(),r.content.firstElementChild}const je=(s,r,i=`${r}s`)=>`${s} ${s===1?r:i}`,ct=s=>`#/pages/edit${s}`;let f=null,ze=!1;function ut(){ze||!window.R?.use||(ze=!0,R.use((s,r,i)=>!(ae.test(r?.path||"")&&!ae.test(s?.path||""))||!f?.dirty()?i():E.confirm("You have unsaved layout changes. Leave anyway? They are kept as a draft for your next visit.").then(d=>{d?i():history.replaceState(null,"",Te)})),M.subscribe("router:afterChange",({to:s}={})=>{if(f&&!ae.test(s?.path||"")){const r=f;f=null,r.dispose()}}))}export const layoutsView={templateUrl:"/admin/js/templates/layouts.html",async onMount(s){if(ut(),f){const e=f;f=null,e.dispose()}const r=[],i=e=>(r.push(e),e);let Y=!1;const d=s.get(0).querySelector(".lyx")||s.get(0),p=()=>!Y&&d.isConnected,y=M.observable({}),A=M.observable({byKey:{},missing:[]}),ne=M.observable(!0),U=M.observable("");let oe="all";try{oe=et.includes(S.get(le))?S.get(le):"all"}catch{}const k=M.observable(oe),ie=M.observable("");i(M.effect(()=>{const e=k.value;try{S.set(le,e)}catch{}}));const re=M.observable([]);i(M.effect(()=>{re.value=lt(y.value,A.value,{filter:k.value,query:ie.value})}));const Pe=M.computed(()=>{const e=Object.values(y.value),t=e.filter(n=>n.builtin).length;return{all:e.length,builtin:t,custom:e.length-t}}),V=M.computed(()=>A.value.missing||[]),Ne=M.computed(()=>{const e=V.value.length;return`${je(e,"page")} name${e===1?"s":""} a layout that no longer exists and show${e===1?"s":""} as Default.`}),H=M.observable({spacerSize:"8",spacerClass:""}),m=M.observable("8"),g=M.observable(""),G=M.observable(!1),ce=()=>({spacerSize:String(m.value??""),spacerClass:String(g.value??"")}),K=M.computed(()=>ot(ce())),x=M.computed(()=>{const e=H.value;return String(m.value??"")!==e.spacerSize||String(g.value??"")!==e.spacerClass}),ue=M.computed(()=>G.value||!x.value||Object.keys(K.value.errors).length>0),Ae=M.computed(()=>x.value?"Unsaved changes - Ctrl+S saves.":"Saved."),B=e=>({...y.peek()[e]||{},key:e}),J=e=>(A.peek().byKey||{})[e]||[];async function Q(){try{const[e,t]=await Promise.all([L.layouts.get(),Qe("/layouts/_usage",{method:"GET"}).catch(()=>({byKey:{},missing:[]}))]);if(!p())return;y.value=e&&typeof e=="object"?e:{},A.value=t&&typeof t=="object"?t:{byKey:{},missing:[]},U.value=""}catch(e){U.value=`Could not load the layouts: ${e.message||e}`}finally{ne.value=!1}}async function He(){const e=await L.layouts.getOptions().catch(()=>({spacerSize:8,spacerClass:""}));if(!p())return;const t={spacerSize:String(e?.spacerSize??8),spacerClass:String(e?.spacerClass??"")};H.value=t,m.value=t.spacerSize,g.value=t.spacerClass}async function de(){if(ue.peek())return;G.value=!0;const{payload:e}=K.peek();try{await L.layouts.saveOptions(e),H.value={spacerSize:String(e.spacerSize),spacerClass:e.spacerClass},m.value=String(e.spacerSize),g.value=e.spacerClass;try{S.remove?S.remove(N):S.set(N,null)}catch{}E.toast("Layout options saved.",{type:"success"})}catch(t){E.toast(`Not saved: ${t.message||t}`,{type:"error"})}finally{G.value=!1}}function Ie(){const e=H.peek();m.value=e.spacerSize,g.value=e.spacerClass}const pe=()=>Ze(Te);async function Ke(e){const t=B(e);if(t.builtin)return;const n=J(e).length,c=n?` ${je(n,"page")} use${n===1?"s":""} it and will show as Default.`:" No page uses it.";if(await E.confirm(`Delete the layout "${W(t.label||e)}"?${c}`))try{await L.layouts.remove(e),E.toast(`Deleted "${t.label||e}".`,{type:"success"}),pe(),p()&&await Q()}catch(o){E.toast(`Not deleted: ${o.message||o}`,{type:"error"})}}async function Re(e){const t=`layout: ${e}`;try{await navigator.clipboard.writeText(t),E.toast(`Copied "${t}" - paste it into a page's frontmatter.`,{type:"success"})}catch{E.toast(`Copy this into a page's frontmatter: ${t}`,{type:"info",duration:7e3})}}const C=new Set;let b=null,ye=!1;function be(e){const t=e===null,n=t?V.peek():J(e),c=t?null:B(e),o=E.slideover({title:t?"Pages on a missing layout":`Pages using ${W(c.label||e)}`,size:"sm",position:"right"}),j=Be(`
16
+ </div>`,W=s=>String(s??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");function Be(s){const r=document.createElement("template");return r.innerHTML=s.trim(),r.content.firstElementChild}const je=(s,r,i=`${r}s`)=>`${s} ${s===1?r:i}`,ct=s=>`#/pages/edit${s}`;let f=null,ze=!1;function dt(){ze||!window.R?.use||(ze=!0,R.use((s,r,i)=>!(ae.test(r?.path||"")&&!ae.test(s?.path||""))||!f?.dirty()?i():E.confirm("You have unsaved layout changes. Leave anyway? They are kept as a draft for your next visit.").then(u=>{u?i():history.replaceState(null,"",Te)})),M.subscribe("router:afterChange",({to:s}={})=>{if(f&&!ae.test(s?.path||"")){const r=f;f=null,r.dispose()}}))}export const layoutsView={templateUrl:"/admin/js/templates/layouts.html",async onMount(s){if(dt(),f){const e=f;f=null,e.dispose()}const r=[],i=e=>(r.push(e),e);let Y=!1;const u=s.get(0).querySelector(".lyx")||s.get(0),p=()=>!Y&&u.isConnected,y=M.observable({}),A=M.observable({byKey:{},missing:[]}),ne=M.observable(!0),U=M.observable("");let oe="all";try{oe=et.includes(S.get(le))?S.get(le):"all"}catch{}const k=M.observable(oe),ie=M.observable("");i(M.effect(()=>{const e=k.value;try{S.set(le,e)}catch{}}));const re=M.observable([]);i(M.effect(()=>{re.value=lt(y.value,A.value,{filter:k.value,query:ie.value})}));const Pe=M.computed(()=>{const e=Object.values(y.value),t=e.filter(n=>n.builtin).length;return{all:e.length,builtin:t,custom:e.length-t}}),V=M.computed(()=>A.value.missing||[]),Ne=M.computed(()=>{const e=V.value.length;return`${je(e,"page")} name${e===1?"s":""} a layout that no longer exists and show${e===1?"s":""} as Default.`}),H=M.observable({spacerSize:"8",spacerClass:""}),m=M.observable("8"),g=M.observable(""),G=M.observable(!1),ce=()=>({spacerSize:String(m.value??""),spacerClass:String(g.value??"")}),K=M.computed(()=>ot(ce())),x=M.computed(()=>{const e=H.value;return String(m.value??"")!==e.spacerSize||String(g.value??"")!==e.spacerClass}),de=M.computed(()=>G.value||!x.value||Object.keys(K.value.errors).length>0),Ae=M.computed(()=>x.value?"Unsaved changes - Ctrl+S saves.":"Saved."),B=e=>({...y.peek()[e]||{},key:e}),J=e=>(A.peek().byKey||{})[e]||[];async function Q(){try{const[e,t]=await Promise.all([L.layouts.get(),Qe("/layouts/_usage",{method:"GET"}).catch(()=>({byKey:{},missing:[]}))]);if(!p())return;y.value=e&&typeof e=="object"?e:{},A.value=t&&typeof t=="object"?t:{byKey:{},missing:[]},U.value=""}catch(e){U.value=`Could not load the layouts: ${e.message||e}`}finally{ne.value=!1}}async function He(){const e=await L.layouts.getOptions().catch(()=>({spacerSize:8,spacerClass:""}));if(!p())return;const t={spacerSize:String(e?.spacerSize??8),spacerClass:String(e?.spacerClass??"")};H.value=t,m.value=t.spacerSize,g.value=t.spacerClass}async function ue(){if(de.peek())return;G.value=!0;const{payload:e}=K.peek();try{await L.layouts.saveOptions(e),H.value={spacerSize:String(e.spacerSize),spacerClass:e.spacerClass},m.value=String(e.spacerSize),g.value=e.spacerClass;try{S.remove?S.remove(N):S.set(N,null)}catch{}E.toast("Layout options saved.",{type:"success"})}catch(t){E.toast(`Not saved: ${t.message||t}`,{type:"error"})}finally{G.value=!1}}function Ie(){const e=H.peek();m.value=e.spacerSize,g.value=e.spacerClass}const pe=()=>Ze(Te);async function Ke(e){const t=B(e);if(t.builtin)return;const n=J(e).length,c=n?` ${je(n,"page")} use${n===1?"s":""} it and will show as Default.`:" No page uses it.";if(await E.confirm(`Delete the layout "${W(t.label||e)}"?${c}`))try{await L.layouts.remove(e),E.toast(`Deleted "${t.label||e}".`,{type:"success"}),pe(),p()&&await Q()}catch(o){E.toast(`Not deleted: ${o.message||o}`,{type:"error"})}}async function Re(e){const t=`layout: ${e}`;try{await navigator.clipboard.writeText(t),E.toast(`Copied "${t}" - paste it into a page's frontmatter.`,{type:"success"})}catch{E.toast(`Copy this into a page's frontmatter: ${t}`,{type:"info",duration:7e3})}}const C=new Set;let b=null,ye=!1;function be(e){const t=e===null,n=t?V.peek():J(e),c=t?null:B(e),o=E.slideover({title:t?"Pages on a missing layout":`Pages using ${W(c.label||e)}`,size:"sm",position:"right"}),j=Be(`
17
17
  <div class="lyx-form">
18
18
  <p class="lyx-note"></p>
19
19
  <div class="lyx-pages"></div>
20
- </div>`);j.querySelector(".lyx-note").textContent=t?"These pages name a layout that no longer exists, so they show as Default. Open one and choose a layout for it.":n.length?"Click a page to open it in the editor.":"No page uses this layout.";const F=j.querySelector(".lyx-pages");for(const l of n){const w=document.createElement("a");w.href=ct(l.urlPath);const z=document.createElement("span");z.textContent=l.title||l.urlPath;const q=document.createElement("small");q.textContent=[l.urlPath,t?`"${l.layout}"`:"",l.status==="draft"?"draft":""].filter(Boolean).join(" \xB7 "),w.append(z,q),w.addEventListener("click",()=>o.close()),F.appendChild(w)}o.element.appendChild(j),C.add(o),$e(o,()=>C.delete(o)),o.open()}function $(e=null,{from:t=null,draft:n=null}={}){const c=e?B(e):null;if(e&&!y.peek()[e]){E.toast(`There is no layout "${e}".`,{type:"warning"});return}const o=!c,j=c||(t?{...B(t),label:`${B(t).label||t} copy`,builtin:!1}:{}),F={...it(j),...n||{}},l=Object.fromEntries(Object.entries(F).map(([a,h])=>[a,M.observable(h)])),w=()=>Object.fromEntries(Object.keys(l).map(a=>[a,l[a].peek()])),z=a=>JSON.stringify(Object.fromEntries(Object.entries(a).map(([h,te])=>[h,typeof te=="boolean"?te:String(te??"")]))),q=z(F),Se=()=>z(w())!==q||!!n||!!(t&&o),_=M.observable(!1),P=M.computed(()=>nt(Object.fromEntries(Object.keys(l).map(a=>[a,l[a].value])),{isNew:o,taken:Object.keys(y.value)})),X=a=>M.computed(()=>P.value.errors[a]||""),We={hasNav:M.computed(()=>l.navbar.value!==!1),hasFoot:M.computed(()=>l.footer.value!==!1),hasSide:M.computed(()=>l.sidebar.value===!0),share:M.computed(()=>De[l.width.value]||De.normal),bg:M.computed(()=>l.useBg.value?l.bgColor.value:"")},Ee={...l,...We,saving:_,labelErr:X("label"),bgErr:X("bgColor"),classErr:X("cssClass"),keyHint:M.computed(()=>o?P.value.key?`Pages will name it "layout: ${P.value.key}".`:"":`Pages name it "layout: ${c.key}" - renaming does not change that.`),widthHint:M.computed(()=>at[l.width.value]||""),builtinNote:!o&&c.builtin===!0,noBg:M.computed(()=>!l.useBg.value),cannot:M.computed(()=>_.value||Object.keys(P.value.errors).length>0),saveText:o?"Create layout":"Save"},Ye=o?t?"Duplicate layout":"New layout":`Edit ${W(c.label||c.key)}`,u=E.slideover({title:Ye,size:"md",position:"right"}),T=Be(`
20
+ </div>`);j.querySelector(".lyx-note").textContent=t?"These pages name a layout that no longer exists, so they show as Default. Open one and choose a layout for it.":n.length?"Click a page to open it in the editor.":"No page uses this layout.";const F=j.querySelector(".lyx-pages");for(const l of n){const w=document.createElement("a");w.href=ct(l.urlPath);const z=document.createElement("span");z.textContent=l.title||l.urlPath;const q=document.createElement("small");q.textContent=[l.urlPath,t?`"${l.layout}"`:"",l.status==="draft"?"draft":""].filter(Boolean).join(" \xB7 "),w.append(z,q),w.addEventListener("click",()=>o.close()),F.appendChild(w)}o.element.appendChild(j),C.add(o),$e(o,()=>C.delete(o)),o.open()}function $(e=null,{from:t=null,draft:n=null}={}){const c=e?B(e):null;if(e&&!y.peek()[e]){E.toast(`There is no layout "${e}".`,{type:"warning"});return}const o=!c,j=c||(t?{...B(t),label:`${B(t).label||t} copy`,builtin:!1}:{}),F={...it(j),...n||{}},l=Object.fromEntries(Object.entries(F).map(([a,h])=>[a,M.observable(h)])),w=()=>Object.fromEntries(Object.keys(l).map(a=>[a,l[a].peek()])),z=a=>JSON.stringify(Object.fromEntries(Object.entries(a).map(([h,te])=>[h,typeof te=="boolean"?te:String(te??"")]))),q=z(F),Se=()=>z(w())!==q||!!n||!!(t&&o),_=M.observable(!1),P=M.computed(()=>nt(Object.fromEntries(Object.keys(l).map(a=>[a,l[a].value])),{isNew:o,taken:Object.keys(y.value)})),X=a=>M.computed(()=>P.value.errors[a]||""),We={hasNav:M.computed(()=>l.navbar.value!==!1),hasFoot:M.computed(()=>l.footer.value!==!1),hasSide:M.computed(()=>l.sidebar.value===!0),sideRight:M.computed(()=>l.sidebar.value===!0&&l.sidebarSide.value==="right"),share:M.computed(()=>De[l.width.value]||De.normal),bg:M.computed(()=>l.useBg.value?l.bgColor.value:"")},Ee={...l,...We,saving:_,labelErr:X("label"),bgErr:X("bgColor"),classErr:X("cssClass"),keyHint:M.computed(()=>o?P.value.key?`Pages will name it "layout: ${P.value.key}".`:"":`Pages name it "layout: ${c.key}" - renaming does not change that.`),widthHint:M.computed(()=>at[l.width.value]||""),builtinNote:!o&&c.builtin===!0,noBg:M.computed(()=>!l.useBg.value),cannot:M.computed(()=>_.value||Object.keys(P.value.errors).length>0),saveText:o?"Create layout":"Save"},Ye=o?t?"Duplicate layout":"New layout":`Edit ${W(c.label||c.key)}`,d=E.slideover({title:Ye,size:"md",position:"right"}),T=Be(`
21
21
  <div class="lyx-form">
22
22
  <div class="lyx-preview">${Le(".value")}
23
23
  <small class="lyx-hint" data-bind-text="keyHint.value"></small></div>
@@ -48,6 +48,10 @@ import{api as L,apiRequest as Qe}from"../api.js";import{addBannerAction as Xe}fr
48
48
  <label><input type="checkbox" class="form-switch" data-model="footer.value"> Footer</label>
49
49
  <label><input type="checkbox" class="form-switch" data-model="sidebar.value"> Sidebar</label>
50
50
  </div>
51
+ <div class="lyx-switches" data-if="sidebar.value">
52
+ <label><input type="radio" name="lyx-side" value="left" data-model="sidebarSide.value"> Sidebar on the left</label>
53
+ <label><input type="radio" name="lyx-side" value="right" data-model="sidebarSide.value"> on the right</label>
54
+ </div>
51
55
  </div>
52
56
  <div>
53
57
  <span class="form-label">Background colour
@@ -78,4 +82,4 @@ import{api as L,apiRequest as Qe}from"../api.js";import{addBannerAction as Xe}fr
78
82
  <button type="button" class="btn btn-sm btn-primary" data-on-click="save" data-bind-disabled="cannot.value">
79
83
  <span data-icon="check" data-icon-size="13"></span> <span data-bind-text="saveText"></span></button>
80
84
  </div>
81
- </div>`);async function ke(){if(Ee.cannot.peek())return;_.value=!0;const{payload:a}=P.peek();try{o?await L.layouts.create(a):await L.layouts.update(c.key,a),E.toast(o?`Created "${a.label}".`:"Saved.",{type:"success"}),Z=!0,u.close(),pe(),p()&&await Q()}catch(h){E.toast(`Not saved: ${h.message||h}`,{type:"error"})}finally{_.value=!1}}u.element.appendChild(T);const Ue=Me(T),Ve=M.applyBindings(Ee,T,{methods:{save:ke,cancel:()=>u.close()}}),Ge=a=>{(a.ctrlKey||a.metaKey)&&a.key==="Enter"&&(a.preventDefault(),ke())};T.addEventListener("keydown",Ge);const Ce={key:c?.key??null,dirty:Se,values:w};b=Ce,C.add(u),$e(u,()=>{Ve.dispose(),Ue(),C.delete(u),b===Ce&&(b=null)});const Je=u.close.bind(u);let Z=!1,ee=!1;u.close=async a=>{if(!Z&&!ye&&a!==!0&&Se()){if(ee)return;ee=!0;const h=await E.confirm("Discard the changes you have not saved?");if(ee=!1,!h)return}return Z=!0,Je()},I.scan(T),u.open(),T.querySelector("#lyx-f-label")?.focus()}function Fe(e){const t=y.peek()[e];if(!t)return[];const n=J(e).length;return[{header:t.label||e},{label:"Edit\u2026",icon:"edit",action:()=>$(e)},{label:n?`Pages using it (${n})\u2026`:"Pages using it",icon:"file-text",disabled:!n,action:()=>be(e)},{label:"Duplicate\u2026",icon:"copy",action:()=>$(null,{from:e})},{label:"Copy frontmatter line",icon:"clipboard",action:()=>Re(e)},...t.builtin?[]:[{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:()=>Ke(e)}]]}const O=d.querySelector(".lyx-list");O.innerHTML=rt;const ve=E.tabs(d.querySelector(".lyx-tabs"),{tabSelector:".tab-item",panelSelector:".tab-panel"}),qe=M.applyBindings({loading:ne,loadError:U,filter:k,query:ie,rows:re,counts:Pe,missing:V,missingText:Ne,spacerSize:m,spacerClass:g,optDirty:x,optCannot:ue,optStatus:Ae,optErrSize:M.computed(()=>K.value.errors.spacerSize||""),optErrClass:M.computed(()=>K.value.errors.spacerClass||"")},d,{methods:{showAll:()=>{k.value="all"},showBuiltin:()=>{k.value="builtin"},showCustom:()=>{k.value="custom"},showMissing:()=>be(null),saveOptions:()=>de(),resetOptions:()=>Ie()}});i(()=>qe.dispose()),i(Me(d,{position:"bottom"}));const _e=E.contextMenu(O,{cursor:"glyph",match:".lyx-row",inherit:!1,density:"compact",items:e=>Fe(e.dataset.key)});i(()=>_e?.destroy?.());const he=e=>{const t=e.target.closest(".lyx-row");if(!t)return;const n=e.target.closest(".lyx-kebab");if(n){Oe(n);return}$(t.dataset.key)},fe=e=>{const t=e.target.closest(".lyx-row");t&&(e.key==="F10"&&e.shiftKey?(e.preventDefault(),Oe(t)):e.key==="Enter"&&e.target===t&&$(t.dataset.key))};O.addEventListener("click",he),O.addEventListener("keydown",fe),i(()=>{O.removeEventListener("click",he),O.removeEventListener("keydown",fe)});const me=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||!p()||C.size||(e.preventDefault(),x.peek()&&de())};document.addEventListener("keydown",me,!0),i(()=>document.removeEventListener("keydown",me,!0)),Xe(s,{id:"layouts-new",icon:"plus",title:"New layout",onClick:()=>{ve.activate?.(0),$()}});const ge=e=>{(x.peek()||b?.dirty())&&(xe(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",ge),i(()=>window.removeEventListener("beforeunload",ge));function xe(){try{b?.dirty()&&S.set(se,{key:b.key,values:b.values(),at:new Date().toISOString()}),x.peek()&&S.set(N,{...ce(),at:new Date().toISOString()})}catch{}}if(f={dirty:()=>x.peek()||!!b?.dirty(),dispose(){Y=!0,xe(),ye=!0,[...C].forEach(e=>{try{e.close(!0)}catch{}}),r.splice(0).forEach(e=>{try{e()}catch{}})}},I.scan(d),await Promise.all([Q(),He()]),!p())return;I.scan(d);let D=null,v=null;try{D=S.get(N),v=S.get(se)}catch{}const we=e=>{try{S.remove?S.remove(e):S.set(e,null)}catch{}};if(D&&typeof D=="object"&&"spacerSize"in D&&(we(N),await E.confirm("You left unsaved spacer options. Put them back?")&&p()&&(m.value=String(D.spacerSize??""),g.value=String(D.spacerClass??""),ve.activate?.(1))),v?.values&&p()){we(se);const e=v.key&&y.peek()[v.key],t=e?`your changes to "${W(y.peek()[v.key].label||v.key)}"`:"the new layout you started";await E.confirm(`You left ${t} unsaved. Pick up where you left off?`)&&p()&&$(e?v.key:null,{draft:v.values})}}};
85
+ </div>`);async function ke(){if(Ee.cannot.peek())return;_.value=!0;const{payload:a}=P.peek();try{o?await L.layouts.create(a):await L.layouts.update(c.key,a),E.toast(o?`Created "${a.label}".`:"Saved.",{type:"success"}),Z=!0,d.close(),pe(),p()&&await Q()}catch(h){E.toast(`Not saved: ${h.message||h}`,{type:"error"})}finally{_.value=!1}}d.element.appendChild(T);const Ue=Me(T),Ve=M.applyBindings(Ee,T,{methods:{save:ke,cancel:()=>d.close()}}),Ge=a=>{(a.ctrlKey||a.metaKey)&&a.key==="Enter"&&(a.preventDefault(),ke())};T.addEventListener("keydown",Ge);const Ce={key:c?.key??null,dirty:Se,values:w};b=Ce,C.add(d),$e(d,()=>{Ve.dispose(),Ue(),C.delete(d),b===Ce&&(b=null)});const Je=d.close.bind(d);let Z=!1,ee=!1;d.close=async a=>{if(!Z&&!ye&&a!==!0&&Se()){if(ee)return;ee=!0;const h=await E.confirm("Discard the changes you have not saved?");if(ee=!1,!h)return}return Z=!0,Je()},I.scan(T),d.open(),T.querySelector("#lyx-f-label")?.focus()}function Fe(e){const t=y.peek()[e];if(!t)return[];const n=J(e).length;return[{header:t.label||e},{label:"Edit\u2026",icon:"edit",action:()=>$(e)},{label:n?`Pages using it (${n})\u2026`:"Pages using it",icon:"file-text",disabled:!n,action:()=>be(e)},{label:"Duplicate\u2026",icon:"copy",action:()=>$(null,{from:e})},{label:"Copy frontmatter line",icon:"clipboard",action:()=>Re(e)},...t.builtin?[]:[{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:()=>Ke(e)}]]}const O=u.querySelector(".lyx-list");O.innerHTML=rt;const ve=E.tabs(u.querySelector(".lyx-tabs"),{tabSelector:".tab-item",panelSelector:".tab-panel"}),qe=M.applyBindings({loading:ne,loadError:U,filter:k,query:ie,rows:re,counts:Pe,missing:V,missingText:Ne,spacerSize:m,spacerClass:g,optDirty:x,optCannot:de,optStatus:Ae,optErrSize:M.computed(()=>K.value.errors.spacerSize||""),optErrClass:M.computed(()=>K.value.errors.spacerClass||"")},u,{methods:{showAll:()=>{k.value="all"},showBuiltin:()=>{k.value="builtin"},showCustom:()=>{k.value="custom"},showMissing:()=>be(null),saveOptions:()=>ue(),resetOptions:()=>Ie()}});i(()=>qe.dispose()),i(Me(u,{position:"bottom"}));const _e=E.contextMenu(O,{cursor:"glyph",match:".lyx-row",inherit:!1,density:"compact",items:e=>Fe(e.dataset.key)});i(()=>_e?.destroy?.());const he=e=>{const t=e.target.closest(".lyx-row");if(!t)return;const n=e.target.closest(".lyx-kebab");if(n){Oe(n);return}$(t.dataset.key)},fe=e=>{const t=e.target.closest(".lyx-row");t&&(e.key==="F10"&&e.shiftKey?(e.preventDefault(),Oe(t)):e.key==="Enter"&&e.target===t&&$(t.dataset.key))};O.addEventListener("click",he),O.addEventListener("keydown",fe),i(()=>{O.removeEventListener("click",he),O.removeEventListener("keydown",fe)});const me=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||!p()||C.size||(e.preventDefault(),x.peek()&&ue())};document.addEventListener("keydown",me,!0),i(()=>document.removeEventListener("keydown",me,!0)),Xe(s,{id:"layouts-new",icon:"plus",title:"New layout",onClick:()=>{ve.activate?.(0),$()}});const ge=e=>{(x.peek()||b?.dirty())&&(xe(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",ge),i(()=>window.removeEventListener("beforeunload",ge));function xe(){try{b?.dirty()&&S.set(se,{key:b.key,values:b.values(),at:new Date().toISOString()}),x.peek()&&S.set(N,{...ce(),at:new Date().toISOString()})}catch{}}if(f={dirty:()=>x.peek()||!!b?.dirty(),dispose(){Y=!0,xe(),ye=!0,[...C].forEach(e=>{try{e.close(!0)}catch{}}),r.splice(0).forEach(e=>{try{e()}catch{}})}},I.scan(u),await Promise.all([Q(),He()]),!p())return;I.scan(u);let D=null,v=null;try{D=S.get(N),v=S.get(se)}catch{}const we=e=>{try{S.remove?S.remove(e):S.set(e,null)}catch{}};if(D&&typeof D=="object"&&"spacerSize"in D&&(we(N),await E.confirm("You left unsaved spacer options. Put them back?")&&p()&&(m.value=String(D.spacerSize??""),g.value=String(D.spacerClass??""),ve.activate?.(1))),v?.values&&p()){we(se);const e=v.key&&y.peek()[v.key],t=e?`your changes to "${W(y.peek()[v.key].label||v.key)}"`:"the new layout you started";await E.confirm(`You left ${t} unsaved. Pick up where you left off?`)&&p()&&$(e?v.key:null,{draft:v.values})}}};