domma-cms 0.71.0 → 0.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import{api as f}from"../api.js";import{isPathAllowed as se}from"./admin-scope.js";import{colourToCss as G,applyFloat as ie,applyHighlight as oe,itemHighlightAttrs as ae}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{groupPluginItems as re,stripItemByUrl as le,insertFoldersBeforeSystem as ce,pruneEmptySynthesisedFolders as de,MANAGE_PLUGINS_URL as ue}from"./sidebar-grouping.js";function g(t){return String(t??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const me=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Menus",url:"#/menus",icon:"menu",permission:"menus"}];function z(t,i){return i?!t||!t.length?!1:t.includes(i)?!0:["read","create","update","delete"].some(n=>t.includes(`${i}.${n}`)):!0}function J(t,i,n=null){const e=[];for(const s of t){if(s.hidden||s.permission&&!z(i,s.permission))continue;const a=typeof s.url=="string"?s.url:"";if(n&&a.startsWith("#/")&&!se(n,a.slice(1)))continue;const r=Array.isArray(s.items)&&s.items.length>0,o=r?J(s.items,i,n):[];r&&!o.length&&!a||e.push({...s,items:o})}return e}function fe(t,i){if(!i||!i.length)return t;const n=(e,s)=>{for(const a of e){if((a.text||"").toLowerCase()===s.toLowerCase())return a;if(Array.isArray(a.items)){const r=n(a.items,s);if(r)return r}}return null};for(const e of i)if(e.parent){const s=n(t,e.parent);s?(s.items=Array.isArray(s.items)?s.items:[],s.items.push(e.item)):t.push(e.item)}else t.push(e.item);return t}async function ge(t){if(!z(t,"projects"))return[];let i=[];try{i=await f.projects.list()}catch{return[]}const n={};await Promise.all(i.map(async s=>{try{const a=await f.projects.artefacts(s.slug);n[s.slug]=a}catch{n[s.slug]={}}}));const e=[{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 i.map(s=>{const r="#/projects/"+encodeURIComponent(s.slug),o=n[s.slug]||{},c=[{text:"Overview",url:r,icon:s.icon||"folder"}];for(const d of e){const y=o[d.key],b=Array.isArray(y)?y.length:0;b<=0||c.push({text:`${d.text} (${b})`,url:r+d.path,icon:d.icon})}c.push({text:"Settings",url:r+"/settings",icon:"settings"});const m=s.enabled===!1;return{text:s.name||s.slug,icon:s.icon||"folder",items:c,...m&&{colour:"neutral",badge:{text:"off"}}}})}function pe(t,i){for(const n of t)if((n.text||"").toLowerCase()==="projects"){n.items=Array.isArray(n.items)?n.items.slice():[],n.items.push(...i);return}}function Y(t,i){const n=Z.includes(i.tone)?i.tone:"info",e=i.label||String(i.count);i.details?j.set(t,i.details):j.delete(t);const s=C.some(a=>a.url===t)?" data-live":"";return`<span class="sidebar-badge sidebar-badge--${n}" data-count-for="${g(t)}"${s} data-badge-label="${g(e)}" aria-label="${g(e)}">${g(String(i.count))}</span>`}function Q(t){for(const i of t.querySelectorAll("details")){const n=i.querySelector(":scope > summary");if(!n)continue;let e=0,s=!1;const a=[];for(const d of i.querySelectorAll(":scope > .sidebar-children .sidebar-badge[data-live]")){const y=Number(d.textContent)||0;y&&(e+=y,s||(s=d.classList.contains("sidebar-badge--danger")),a.push({text:d.closest("a")?.querySelector(".sidebar-text")?.textContent.trim()??"",meta:d.dataset.badgeLabel,tone:d.classList.contains("sidebar-badge--danger")?"danger":void 0}))}const r=`folder:${i.dataset.stateKey||n.textContent.trim()}`;let o=n.querySelector(":scope > .sidebar-badge--sum");if(!e){o?.remove(),j.delete(r);continue}o||(o=document.createElement("span"),n.querySelector(".sidebar-text")?.after(o));const c=n.querySelector(".sidebar-text")?.textContent.trim()||"This folder",m=a.map(d=>`${d.text}: ${d.meta}`).join(" \xB7 ");o.className=`sidebar-badge sidebar-badge--sum sidebar-badge--${s?"danger":"info"}`,o.dataset.countFor=r,o.dataset.badgeLabel=m,o.setAttribute("aria-label",m),o.textContent=String(e),j.set(r,{title:c,items:a})}}const j=new Map;function he(t,i){if(!t.url||!i)return"";const n=i[t.url];return!n||!(n.count>0)?"":Y(t.url,n)}const Z=["info","attention","danger","live"],be={"#/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 _(t,{fallbackLabel:i="",tone:n="info"}={}){const e=Number(typeof t=="object"&&t!==null?t.count:t);if(!Number.isFinite(e)||e<0)return null;const s=typeof t?.label=="string"&&t.label||(i?`${e} - ${i}`:String(e)),a={count:Math.round(e),label:s,tone:Z.includes(t?.tone)?t.tone:n};return t?.details&&typeof t.details=="object"&&(a.details=t.details),a}function ye(t){const i=(t.text||"").replace(/\s*\(\d+\)\s*$/,"").toLowerCase().replace(/\s+/g,"-"),n=(t.url||"").match(/^#\/projects\/([^/]+)/);return`sidebar.expanded.${n?`project-${decodeURIComponent(n[1])}.`:""}${i}`}function ee(t,i){if(t&&t.type==="separator")return'<hr class="sidebar-divider">';if(t&&t.type==="spacer"){const b=["xs","sm","md","lg","xl","flex"].includes(t.size)?t.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${g(b)}" aria-hidden="true"></div>`}const n=t.icon?`<span data-icon="${g(t.icon)}"></span>`:"",e=g(t.text||""),s=t.url?g(t.url):"",a=Array.isArray(t.items)&&t.items.length>0,r=t.colour?G(t.colour):"",o=ae(t),c=[o.hover&&` data-menu-item-hover="${g(o.hover)}"`,o.active&&` data-menu-item-active="${g(o.active)}"`].filter(Boolean).join(""),m=[r&&`color:${g(r)}`,o.accent&&`--dm-menu-highlight:${g(o.accent)}`].filter(Boolean).join(";"),d=m?` style="${m}"`:"";let y="";if(t.badge&&t.badge.text!=null&&t.badge.text!==""){const b=t.badge.variant?G(t.badge.variant):"";y=`<span class="dm-menu-badge"${b?` style="background:${g(b)};color:var(--dm-text-inverse,#fff)"`:""}>${g(String(t.badge.text))}</span>`}if(a){const b=ye(t),w=S.get(b)!==!1,A=t.items.map(L=>ee(L,i)).join("");return`<details data-state-key="${g(b)}"${w?" open":""}>
2
- <summary${d}>${n} <span class="sidebar-text">${e}</span>${y}</summary>
3
- <div class="sidebar-children">${A}</div>
4
- </details>`}return`<a href="${s}" class="sidebar-link" data-url="${s}"${d}${c}>${n} <span class="sidebar-text">${e}</span>${he(t,i)}${y}</a>`}async function xe(t=[],i=[]){const n=p=>p.then(x=>Array.isArray(x)?x.length:Array.isArray(x?.entries)?x.entries.length:0).catch(()=>0),e=(p,x)=>z(i,p)?x():Promise.resolve(0),[s,a,r,o,c,m,d,y,b,w,A,L,U,T]=await Promise.all([e("pages",()=>n(f.pages.list())),e("media",()=>n(f.media.list())),e("menus",()=>n(f.menus.list())),e("context-menus",()=>n(f.contextMenus.list())),e("theme",()=>f.theme.get().then(p=>(p?.overrides||[]).length).catch(()=>0)),e("collections",()=>n(f.collections.list())),e("forms",()=>n(f.forms.list())),e("views",()=>n(f.views.list())),e("actions",()=>n(f.actions.list())),e("blocks",()=>n(f.blocks.list())),e("components",()=>n(f.components.list())),e("users",()=>n(f.users.list())),e("plugins",()=>n(f.plugins.list())),e("notifications",()=>f.system?.notifications?.unreadCount?.().then(p=>p?.count??0).catch(()=>0)??Promise.resolve(0))]);C=Ce(t);const k={};await Promise.all(C.map(async p=>{const x=await te(p);x&&(k[p.url]=x)}));const F={"#/pages":s,"#/media":a,"#/menus":r,"#/context-menus":o,"#/theme":c,"#/collections":m,"#/forms":d,"#/views":y,"#/actions":b,"#/blocks":w,"#/components":A,"#/users":L,"#/plugins":U,"#/system/notifications":T},N={};for(const[p,x]of Object.entries(F)){const[B,O]=be[p]??["item","items"],v=_(x);v&&(N[p]={...v,label:`${v.count} ${v.count===1?B:O}`})}return{...N,...k}}let C=[];const ve="dm:sidebar-count",$e=6e4;function Ce(t=[]){const i=[],n=e=>{if(e){e.url&&typeof e.countUrl=="string"&&e.countUrl&&i.push({url:e.url,countUrl:e.countUrl,text:e.text||"",countLabel:e.countLabel||""});for(const s of[...e.children||[],...e.items||[]])n(s)}};for(const e of t??[])n(e?.item??e);return i}async function te(t){try{const i=new Date,n=`${i.getFullYear()}-${String(i.getMonth()+1).padStart(2,"0")}-${String(i.getDate()).padStart(2,"0")}`,e=t.countUrl+(t.countUrl.includes("?")?"&":"?")+`today=${n}`;return _(await H.get(e),{fallbackLabel:t.countLabel||t.text})}catch{return null}}function ne(t,i,n){const e=[...t.querySelectorAll("a.sidebar-link")].find(a=>a.dataset.url===i);if(!e)return;const s=e.querySelector(".sidebar-badge[data-count-for]");if(!n||!(n.count>0))s?.remove();else{const a=document.createRange();a.selectNodeContents(e);const r=a.createContextualFragment(Y(i,n)).firstElementChild;s?s.replaceWith(r):e.querySelector(".sidebar-text")?.after(r)}W.set(i,n),Q(t)}const W=new Map;async function D(t,i=null){const n=i?C.filter(e=>e.url===i):C;await Promise.all(n.map(async e=>{const s=await te(e),a=W.get(e.url);JSON.stringify(a??null)!==JSON.stringify(s)&&ne(t,e.url,s)}))}function we(t){t.dataset.liveCounts||(t.dataset.liveCounts="1",window.addEventListener(ve,i=>{const n=i.detail||{};if(n.url)if(n.count!==void 0){const e=C.find(s=>s.url===n.url);ne(t,n.url,_(n,{fallbackLabel:e?.countLabel||e?.text||""}))}else D(t,n.url)}),setInterval(()=>{!document.hidden&&document.body.contains(t)&&D(t)},$e),document.addEventListener("visibilitychange",()=>{document.hidden||D(t)}),Ae(t))}function Se(t,i,n){const e=(r,o,c)=>{const m=document.createElement(r);return o&&(m.className=o),c!=null&&(m.textContent=String(c)),m};if(!n){t.textContent=i;return}t.appendChild(e("strong","dm-badge-pop-title",n.title||i)),(n.rows?.length||!n.items?.length)&&t.appendChild(e("p","dm-badge-pop-sub",i));const s=Array.isArray(n.rows)?n.rows.slice(0,6):[];if(s.length){const r=e("dl","dm-badge-pop-rows");for(const o of s)r.appendChild(e("dt",null,o.label)),r.appendChild(e("dd",o.tone==="danger"?"is-danger":null,o.value));t.appendChild(r)}const a=Array.isArray(n.items)?n.items.slice(0,6):[];if(a.length){const r=e("ul","dm-badge-pop-items");for(const o of a){const c=e("li",o.tone==="danger"?"is-danger":null);c.appendChild(e("span","dm-badge-pop-text",o.text)),o.meta&&c.appendChild(e("span","dm-badge-pop-meta",o.meta)),r.appendChild(c)}t.appendChild(r)}else n.empty&&t.appendChild(e("p","dm-badge-pop-empty",n.empty))}function Ae(t){let i=null,n=null;const e=()=>{clearTimeout(n),i?.remove(),i=null},s=a=>{e();const r=a?.dataset.badgeLabel;r&&(n=setTimeout(()=>{i=document.createElement("div"),i.className="dm-help-pop dm-badge-pop",i.setAttribute("role","tooltip"),Se(i,r,j.get(a.dataset.countFor)),document.body.appendChild(i);const o=a.getBoundingClientRect(),c=i.getBoundingClientRect(),m=Math.min(o.right+10,innerWidth-c.width-8);i.style.left=`${Math.max(8,m)}px`,i.style.top=`${Math.max(8,Math.min(o.top+o.height/2-c.height/2,innerHeight-c.height-8))}px`},150))};t.addEventListener("mouseover",a=>{const r=a.target.closest?.(".sidebar-badge[data-badge-label]");r&&s(r)}),t.addEventListener("mouseout",a=>{a.target.closest?.(".sidebar-badge[data-badge-label]")&&e()}),t.addEventListener("focusin",a=>{const r=a.target.closest?.("a, summary")?.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");r?s(r):e()}),t.addEventListener("focusout",e),window.addEventListener("scroll",e,!0),window.addEventListener("hashchange",e)}async function Le(){try{const t=await f.settings.get();return t?.adminBrand?.title||t?.title||"Admin"}catch{return"Admin"}}export async function renderAdminSidebar({mount:t,permissions:i,adminScope:n=null}){const e=$(t).get(0);if(!e)return;let s,a=null,r=null,o=null,c=null,m=null;try{const l=await f.get("/sidebar/menu");s=Array.isArray(l?.items)?l.items:null,a=l?.variant||null,r=l?.position||null,o=l?.style||null,c=l?.float||null,m=l?.highlight||null}catch{s=null}(!s||!s.length)&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),s=me.slice());let d=[];try{d=await H.get("/api/sidebar/registered-items")||[]}catch{}s=le(s,ue);const{toolsFolder:y,managePluginsItem:b,parented:w}=re(d);s=fe(s,w),s=ce(s,[y,b]);const A=await ge(i);A.length&&pe(s,A),s=J(s,i,n),s=de(s);const[L,U]=await Promise.all([xe(d.filter(l=>z(i,l?.item?.permission)),i).catch(()=>({})),Le()]),T=a?` dm-admin-sidebar--${g(a)}`:"";function k(l,h="px"){if(l==null)return l;const u=String(l).trim();return/^\d+(\.\d+)?$/.test(u)?`${u}${h}`:u}let F="";if(o){const l=[];o.fontFamily&&l.push(`font-family: ${o.fontFamily}, sans-serif`),o.fontSize&&l.push(`font-size: ${k(o.fontSize)}`),o.fontWeight&&l.push(`font-weight: ${o.fontWeight}`),o.letterSpacing&&l.push(`letter-spacing: ${k(o.letterSpacing,"em")}`);const h=[];if(l.length&&h.push(`#admin-sidebar .dm-admin-sidebar, #admin-sidebar .dm-admin-sidebar .sidebar-link, #admin-sidebar .dm-admin-sidebar summary, #admin-sidebar .dm-admin-sidebar .sidebar-text { ${l.join("; ")} }`),o.iconSize){const u=k(o.iconSize);h.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${u} !important; height: ${u} !important; }`)}h.length&&(F=`<style data-admin-sidebar-style>${h.join(" ")}</style>`)}const N=`<div class="dm-admin-sidebar-header"><span data-icon="layout"></span> ${g(U)}</div>`,p=s.some(l=>l&&l.type==="spacer"&&l.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",x=`${F}<nav class="dm-admin-sidebar${T}${p}">${N}${s.map(l=>ee(l,L)).join("")}</nav>`,B=document.createRange();B.selectNodeContents(e);const O=B.createContextualFragment(x);e.replaceChildren(O),Domma.icons.scan(e);for(const l of C)W.set(l.url,L[l.url]??null);Q(e),we(e);const v=Number.parseInt(S.get("sidebar.width"),10);Number.isFinite(v)&&v>=180&&v<=480&&(e.style.width=v+"px"),e.removeAttribute("data-menu-hover"),e.removeAttribute("data-menu-active"),e.style.removeProperty("--dm-menu-highlight"),oe(e,{highlight:m});const E=document.querySelector(".dashboard-wrapper");if(["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(l=>{e.style[l]=""}),e.classList.remove("dm-menu-floating"),E&&(E.style.paddingLeft="",E.style.paddingRight=""),r==="floating"){ie(e,{position:"floating",float:c||{}});const h=c&&c.anchor||"TL",u=Number.isFinite(Number(c?.offsetX))?Number(c.offsetX):16,P=e.getBoundingClientRect().width||v||260;if(E){const I=Math.round(P+u*2)+"px";h==="R"||h==="TR"||h==="BR"?E.style.paddingRight=I:E.style.paddingLeft=I}}const R=document.createElement("div");R.className="dm-admin-sidebar-handle",R.setAttribute("aria-label","Resize sidebar"),e.appendChild(R),e.querySelectorAll("details").forEach(l=>{l.addEventListener("toggle",function(){const h=this.getAttribute("data-state-key");h&&S.set(h,this.open);const u=this.querySelector(":scope > .sidebar-children");if(u)if(this.open){const P=u.scrollHeight;u.style.maxHeight="0",requestAnimationFrame(()=>{u.style.maxHeight=P+"px",u.addEventListener("transitionend",function I(){u.style.maxHeight="none",u.removeEventListener("transitionend",I)})})}else{const P=u.scrollHeight;u.style.maxHeight=P+"px",requestAnimationFrame(()=>{u.style.maxHeight="0"})}})});let q=!1,X=0,K=0;R.addEventListener("mousedown",l=>{q=!0,X=l.clientX,K=e.getBoundingClientRect().width,document.body.style.cursor="col-resize",document.body.style.userSelect="none",l.preventDefault()}),document.addEventListener("mousemove",l=>{if(!q)return;const h=Math.max(180,Math.min(480,K+(l.clientX-X)));e.style.width=h+"px"}),document.addEventListener("mouseup",()=>{q&&(q=!1,document.body.style.cursor="",document.body.style.userSelect="",S.set("sidebar.width",parseInt(e.style.width,10)))});function V(){const l=location.hash||"#/";$(e).find(".sidebar-link").removeClass("active"),$(e).find(`.sidebar-link[data-url="${l}"]`).addClass("active")}V(),M.subscribe("router:afterChange",V)}
1
+ import{api as f}from"../api.js";import{isPathAllowed as ee}from"./admin-scope.js";import{colourToCss as X,applyFloat as te,applyHighlight as ne,itemHighlightAttrs as se}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{placeTools as ie,ensureLockedTools as oe,managePluginsItem as ae,stripItemByUrl as re,insertFoldersBeforeSystem as le,pruneEmptyToolsFolder as ce,MANAGE_PLUGINS_URL as de}from"./sidebar-grouping.js";function p(t){return String(t??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const ue=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Menus",url:"#/menus",icon:"menu",permission:"menus"}];function q(t,s){return s?!t||!t.length?!1:t.includes(s)?!0:["read","create","update","delete"].some(n=>t.includes(`${s}.${n}`)):!0}function K(t,s,n=null){const e=[];for(const i of t){if(i.hidden||i.permission&&!q(s,i.permission))continue;const r=typeof i.url=="string"?i.url:"";if(n&&r.startsWith("#/")&&!ee(n,r.slice(1)))continue;const l=Array.isArray(i.items)&&i.items.length>0,o=l?K(i.items,s,n):[];l&&!o.length&&!r||e.push({...i,items:o})}return e}async function me(t){if(!q(t,"projects"))return[];let s=[];try{s=await f.projects.list()}catch{return[]}const n={};await Promise.all(s.map(async i=>{try{const r=await f.projects.artefacts(i.slug);n[i.slug]=r}catch{n[i.slug]={}}}));const e=[{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 s.map(i=>{const l="#/projects/"+encodeURIComponent(i.slug),o=n[i.slug]||{},c=[{text:"Overview",url:l,icon:i.icon||"folder"}];for(const d of e){const x=o[d.key],h=Array.isArray(x)?x.length:0;h<=0||c.push({text:`${d.text} (${h})`,url:l+d.path,icon:d.icon})}c.push({text:"Settings",url:l+"/settings",icon:"settings"});const m=i.enabled===!1;return{text:i.name||i.slug,icon:i.icon||"folder",items:c,...m&&{colour:"neutral",badge:{text:"off"}}}})}function fe(t,s){for(const n of t)if(!n.hidden&&(n.key==="projects"||!n.key&&(n.text||"").toLowerCase()==="projects")){n.items=Array.isArray(n.items)?n.items.slice():[],n.items.push(...s);return}}function V(t,s){const n=J.includes(s.tone)?s.tone:"info",e=s.label||String(s.count);s.details?F.set(t,s.details):F.delete(t);const i=A.some(r=>r.url===t)?" data-live":"";return`<span class="sidebar-badge sidebar-badge--${n}" data-count-for="${p(t)}"${i} data-badge-label="${p(e)}" aria-label="${p(e)}">${p(String(s.count))}</span>`}function G(t){for(const s of t.querySelectorAll("details")){const n=s.querySelector(":scope > summary");if(!n)continue;let e=0,i=!1;const r=[];for(const d of s.querySelectorAll(":scope > .sidebar-children .sidebar-badge[data-live]")){const x=Number(d.textContent)||0;x&&(e+=x,i||(i=d.classList.contains("sidebar-badge--danger")),r.push({text:d.closest("a")?.querySelector(".sidebar-text")?.textContent.trim()??"",meta:d.dataset.badgeLabel,tone:d.classList.contains("sidebar-badge--danger")?"danger":void 0}))}const l=`folder:${s.dataset.stateKey||n.textContent.trim()}`;let o=n.querySelector(":scope > .sidebar-badge--sum");if(!e){o?.remove(),F.delete(l);continue}o||(o=document.createElement("span"),n.querySelector(".sidebar-text")?.after(o));const c=n.querySelector(".sidebar-text")?.textContent.trim()||"This folder",m=r.map(d=>`${d.text}: ${d.meta}`).join(" \xB7 ");o.className=`sidebar-badge sidebar-badge--sum sidebar-badge--${i?"danger":"info"}`,o.dataset.countFor=l,o.dataset.badgeLabel=m,o.setAttribute("aria-label",m),o.textContent=String(e),F.set(l,{title:c,items:r})}}const F=new Map;function ge(t,s){if(!t.url||!s)return"";const n=s[t.url];return!n||!(n.count>0)?"":V(t.url,n)}const J=["info","attention","danger","live"],pe={"#/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 I(t,{fallbackLabel:s="",tone:n="info"}={}){const e=Number(typeof t=="object"&&t!==null?t.count:t);if(!Number.isFinite(e)||e<0)return null;const i=typeof t?.label=="string"&&t.label||(s?`${e} - ${s}`:String(e)),r={count:Math.round(e),label:i,tone:J.includes(t?.tone)?t.tone:n};return t?.details&&typeof t.details=="object"&&(r.details=t.details),r}function he(t){const s=(t.text||"").replace(/\s*\(\d+\)\s*$/,"").toLowerCase().replace(/\s+/g,"-"),n=(t.url||"").match(/^#\/projects\/([^/]+)/);return`sidebar.expanded.${n?`project-${decodeURIComponent(n[1])}.`:""}${s}`}function Y(t,s){if(t&&t.type==="separator")return'<hr class="sidebar-divider">';if(t&&t.type==="spacer"){const h=["xs","sm","md","lg","xl","flex"].includes(t.size)?t.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${p(h)}" aria-hidden="true"></div>`}const n=t.icon?`<span data-icon="${p(t.icon)}"></span>`:"",e=p(t.text||""),i=t.url?p(t.url):"",r=Array.isArray(t.items)&&t.items.length>0,l=t.colour?X(t.colour):"",o=se(t),c=[o.hover&&` data-menu-item-hover="${p(o.hover)}"`,o.active&&` data-menu-item-active="${p(o.active)}"`].filter(Boolean).join(""),m=[l&&`color:${p(l)}`,o.accent&&`--dm-menu-highlight:${p(o.accent)}`].filter(Boolean).join(";"),d=m?` style="${m}"`:"";let x="";if(t.badge&&t.badge.text!=null&&t.badge.text!==""){const h=t.badge.variant?X(t.badge.variant):"";x=`<span class="dm-menu-badge"${h?` style="background:${p(h)};color:var(--dm-text-inverse,#fff)"`:""}>${p(String(t.badge.text))}</span>`}if(r){const h=he(t),L=S.get(h)!==!1,E=t.items.map(w=>Y(w,s)).join("");return`<details data-state-key="${p(h)}"${L?" open":""}>
2
+ <summary${d}>${n} <span class="sidebar-text">${e}</span>${x}</summary>
3
+ <div class="sidebar-children">${E}</div>
4
+ </details>`}return`<a href="${i}" class="sidebar-link" data-url="${i}"${d}${c}>${n} <span class="sidebar-text">${e}</span>${ge(t,s)}${x}</a>`}async function be(t=[],s=[]){const n=b=>b.then(g=>Array.isArray(g)?g.length:Array.isArray(g?.entries)?g.entries.length:0).catch(()=>0),e=(b,g)=>q(s,b)?g():Promise.resolve(0),[i,r,l,o,c,m,d,x,h,L,E,w,N,T]=await Promise.all([e("pages",()=>n(f.pages.list())),e("media",()=>n(f.media.list())),e("menus",()=>n(f.menus.list())),e("context-menus",()=>n(f.contextMenus.list())),e("theme",()=>f.theme.get().then(b=>(b?.overrides||[]).length).catch(()=>0)),e("collections",()=>n(f.collections.list())),e("forms",()=>n(f.forms.list())),e("views",()=>n(f.views.list())),e("actions",()=>n(f.actions.list())),e("blocks",()=>n(f.blocks.list())),e("components",()=>n(f.components.list())),e("users",()=>n(f.users.list())),e("plugins",()=>n(f.plugins.list())),e("notifications",()=>f.system?.notifications?.unreadCount?.().then(b=>b?.count??0).catch(()=>0)??Promise.resolve(0))]);A=ve(t);const B={};await Promise.all(A.map(async b=>{const g=await Q(b);g&&(B[b.url]=g)}));const z={"#/pages":i,"#/media":r,"#/menus":l,"#/context-menus":o,"#/theme":c,"#/collections":m,"#/forms":d,"#/views":x,"#/actions":h,"#/blocks":L,"#/components":E,"#/users":w,"#/plugins":N,"#/system/notifications":T},j={};for(const[b,g]of Object.entries(z)){const[C,k]=pe[b]??["item","items"],v=I(g);v&&(j[b]={...v,label:`${v.count} ${v.count===1?C:k}`})}return{...j,...B}}let A=[];const ye="dm:sidebar-count",xe=6e4;function ve(t=[]){const s=[],n=e=>{if(e){e.url&&typeof e.countUrl=="string"&&e.countUrl&&s.push({url:e.url,countUrl:e.countUrl,text:e.text||"",countLabel:e.countLabel||""});for(const i of[...e.children||[],...e.items||[]])n(i)}};for(const e of t??[])n(e?.item??e);return s}async function Q(t){try{const s=new Date,n=`${s.getFullYear()}-${String(s.getMonth()+1).padStart(2,"0")}-${String(s.getDate()).padStart(2,"0")}`,e=t.countUrl+(t.countUrl.includes("?")?"&":"?")+`today=${n}`;return I(await H.get(e),{fallbackLabel:t.countLabel||t.text})}catch{return null}}function Z(t,s,n){const e=[...t.querySelectorAll("a.sidebar-link")].find(r=>r.dataset.url===s);if(!e)return;const i=e.querySelector(".sidebar-badge[data-count-for]");if(!n||!(n.count>0))i?.remove();else{const r=document.createRange();r.selectNodeContents(e);const l=r.createContextualFragment(V(s,n)).firstElementChild;i?i.replaceWith(l):e.querySelector(".sidebar-text")?.after(l)}U.set(s,n),G(t)}const U=new Map;async function O(t,s=null){const n=s?A.filter(e=>e.url===s):A;await Promise.all(n.map(async e=>{const i=await Q(e),r=U.get(e.url);JSON.stringify(r??null)!==JSON.stringify(i)&&Z(t,e.url,i)}))}function $e(t){t.dataset.liveCounts||(t.dataset.liveCounts="1",window.addEventListener(ye,s=>{const n=s.detail||{};if(n.url)if(n.count!==void 0){const e=A.find(i=>i.url===n.url);Z(t,n.url,I(n,{fallbackLabel:e?.countLabel||e?.text||""}))}else O(t,n.url)}),setInterval(()=>{!document.hidden&&document.body.contains(t)&&O(t)},xe),document.addEventListener("visibilitychange",()=>{document.hidden||O(t)}),Se(t))}function Ce(t,s,n){const e=(l,o,c)=>{const m=document.createElement(l);return o&&(m.className=o),c!=null&&(m.textContent=String(c)),m};if(!n){t.textContent=s;return}t.appendChild(e("strong","dm-badge-pop-title",n.title||s)),(n.rows?.length||!n.items?.length)&&t.appendChild(e("p","dm-badge-pop-sub",s));const i=Array.isArray(n.rows)?n.rows.slice(0,6):[];if(i.length){const l=e("dl","dm-badge-pop-rows");for(const o of i)l.appendChild(e("dt",null,o.label)),l.appendChild(e("dd",o.tone==="danger"?"is-danger":null,o.value));t.appendChild(l)}const r=Array.isArray(n.items)?n.items.slice(0,6):[];if(r.length){const l=e("ul","dm-badge-pop-items");for(const o of r){const c=e("li",o.tone==="danger"?"is-danger":null);c.appendChild(e("span","dm-badge-pop-text",o.text)),o.meta&&c.appendChild(e("span","dm-badge-pop-meta",o.meta)),l.appendChild(c)}t.appendChild(l)}else n.empty&&t.appendChild(e("p","dm-badge-pop-empty",n.empty))}function Se(t){let s=null,n=null;const e=()=>{clearTimeout(n),s?.remove(),s=null},i=r=>{e();const l=r?.dataset.badgeLabel;l&&(n=setTimeout(()=>{s=document.createElement("div"),s.className="dm-help-pop dm-badge-pop",s.setAttribute("role","tooltip"),Ce(s,l,F.get(r.dataset.countFor)),document.body.appendChild(s);const o=r.getBoundingClientRect(),c=s.getBoundingClientRect(),m=Math.min(o.right+10,innerWidth-c.width-8);s.style.left=`${Math.max(8,m)}px`,s.style.top=`${Math.max(8,Math.min(o.top+o.height/2-c.height/2,innerHeight-c.height-8))}px`},150))};t.addEventListener("mouseover",r=>{const l=r.target.closest?.(".sidebar-badge[data-badge-label]");l&&i(l)}),t.addEventListener("mouseout",r=>{r.target.closest?.(".sidebar-badge[data-badge-label]")&&e()}),t.addEventListener("focusin",r=>{const l=r.target.closest?.("a, summary")?.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");l?i(l):e()}),t.addEventListener("focusout",e),window.addEventListener("scroll",e,!0),window.addEventListener("hashchange",e)}async function we(){try{const t=await f.settings.get();return t?.adminBrand?.title||t?.title||"Admin"}catch{return"Admin"}}export async function renderAdminSidebar({mount:t,permissions:s,adminScope:n=null}){const e=$(t).get(0);if(!e)return;let i,r=null,l=null,o=null,c=null,m=null;try{const a=await f.get("/sidebar/menu");i=Array.isArray(a?.items)?a.items:null,r=a?.variant||null,l=a?.position||null,o=a?.style||null,c=a?.float||null,m=a?.highlight||null}catch{i=null}(!i||!i.some(a=>a&&(!a.hidden||a.restricted)))&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),i=ue.slice());let d=[];try{d=await H.get("/api/sidebar/registered-items")||[]}catch{}i=re(i,de),i=ie(i,d),i=oe(i),i=le(i,[ae()]);const x=await me(s);x.length&&fe(i,x),i=K(i,s,n),i=ce(i);const[h,L]=await Promise.all([be(d.filter(a=>q(s,a?.item?.permission)),s).catch(()=>({})),we()]),E=r?` dm-admin-sidebar--${p(r)}`:"";function w(a,y="px"){if(a==null)return a;const u=String(a).trim();return/^\d+(\.\d+)?$/.test(u)?`${u}${y}`:u}let N="";if(o){const a=[];o.fontFamily&&a.push(`font-family: ${o.fontFamily}, sans-serif`),o.fontSize&&a.push(`font-size: ${w(o.fontSize)}`),o.fontWeight&&a.push(`font-weight: ${o.fontWeight}`),o.letterSpacing&&a.push(`letter-spacing: ${w(o.letterSpacing,"em")}`);const y=[];if(a.length&&y.push(`#admin-sidebar .dm-admin-sidebar, #admin-sidebar .dm-admin-sidebar .sidebar-link, #admin-sidebar .dm-admin-sidebar summary, #admin-sidebar .dm-admin-sidebar .sidebar-text { ${a.join("; ")} }`),o.iconSize){const u=w(o.iconSize);y.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${u} !important; height: ${u} !important; }`)}y.length&&(N=`<style data-admin-sidebar-style>${y.join(" ")}</style>`)}const T=`<div class="dm-admin-sidebar-header"><span data-icon="layout"></span> ${p(L)}</div>`,B=i.some(a=>a&&a.type==="spacer"&&a.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",z=`${N}<nav class="dm-admin-sidebar${E}${B}">${T}${i.map(a=>Y(a,h)).join("")}</nav>`,j=document.createRange();j.selectNodeContents(e);const b=j.createContextualFragment(z);e.replaceChildren(b),Domma.icons.scan(e);for(const a of A)U.set(a.url,h[a.url]??null);G(e),$e(e);const g=Number.parseInt(S.get("sidebar.width"),10);Number.isFinite(g)&&g>=180&&g<=480&&(e.style.width=g+"px"),e.removeAttribute("data-menu-hover"),e.removeAttribute("data-menu-active"),e.style.removeProperty("--dm-menu-highlight"),ne(e,{highlight:m});const C=document.querySelector(".dashboard-wrapper");if(["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(a=>{e.style[a]=""}),e.classList.remove("dm-menu-floating"),C&&(C.style.paddingLeft="",C.style.paddingRight=""),l==="floating"){te(e,{position:"floating",float:c||{}});const y=c&&c.anchor||"TL",u=Number.isFinite(Number(c?.offsetX))?Number(c.offsetX):16,P=e.getBoundingClientRect().width||g||260;if(C){const R=Math.round(P+u*2)+"px";y==="R"||y==="TR"||y==="BR"?C.style.paddingRight=R:C.style.paddingLeft=R}}const k=document.createElement("div");k.className="dm-admin-sidebar-handle",k.setAttribute("aria-label","Resize sidebar"),e.appendChild(k),e.querySelectorAll("details").forEach(a=>{a.addEventListener("toggle",function(){const y=this.getAttribute("data-state-key");y&&S.set(y,this.open);const u=this.querySelector(":scope > .sidebar-children");if(u)if(this.open){const P=u.scrollHeight;u.style.maxHeight="0",requestAnimationFrame(()=>{u.style.maxHeight=P+"px",u.addEventListener("transitionend",function R(){u.style.maxHeight="none",u.removeEventListener("transitionend",R)})})}else{const P=u.scrollHeight;u.style.maxHeight=P+"px",requestAnimationFrame(()=>{u.style.maxHeight="0"})}})});let v=!1,_=0,W=0;k.addEventListener("mousedown",a=>{v=!0,_=a.clientX,W=e.getBoundingClientRect().width,document.body.style.cursor="col-resize",document.body.style.userSelect="none",a.preventDefault()}),document.addEventListener("mousemove",a=>{if(!v)return;const y=Math.max(180,Math.min(480,W+(a.clientX-_)));e.style.width=y+"px"}),document.addEventListener("mouseup",()=>{v&&(v=!1,document.body.style.cursor="",document.body.style.userSelect="",S.set("sidebar.width",parseInt(e.style.width,10)))});function D(){const a=location.hash||"#/";$(e).find(".sidebar-link").removeClass("active"),$(e).find(`.sidebar-link[data-url="${a}"]`).addClass("active")}D(),M.subscribe("router:afterChange",D)}
@@ -46,6 +46,14 @@
46
46
  </div>
47
47
  </div>
48
48
  </div>
49
+ <div class="row mb-3">
50
+ <div class="col-6">
51
+ <label class="form-label" for="field-admin-home">Admin home</label>
52
+ <select id="field-admin-home" class="form-select"></select>
53
+ <small class="form-hint">The screen the admin opens on, and where Dashboard goes. Anyone without the
54
+ permission for it gets the built-in dashboard; a confined role keeps its own home (Roles &rsaquo; Admin area).</small>
55
+ </div>
56
+ </div>
49
57
  <div class="row mt-3">
50
58
  <div class="col-3">
51
59
  <label class="form-label">Default Spacer Size (px)</label>
@@ -44,9 +44,6 @@
44
44
  <input id="theme-auto-enabled" type="checkbox" class="form-check">
45
45
  Switch automatically between a day and a night theme
46
46
  </label>
47
- <p id="theme-auto-roller-hint" class="form-hint text-muted"
48
- style="display:none;margin-top:.35rem;font-size:.8rem;">
49
- Custom themes (Theme Roller) do not support auto-switching.</p>
50
47
  </div>
51
48
  </div>
52
49
  <div id="theme-auto-fields" class="row mt-3" style="display:none;">
@@ -1,5 +1,5 @@
1
- import{api as k}from"../api.js";import{colourToCss as xt,HIGHLIGHT_HOVER as rt,HIGHLIGHT_ACTIVE as pt,HIGHLIGHT_HOVER_LABELS as mt,HIGHLIGHT_ACTIVE_LABELS as ft}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{openIconPicker as At}from"../lib/card-builder.js";import{applyDrop as _t}from"../lib/menu-tree.js";let kt=1;const G=()=>"i_"+kt++,It=760;function ut(t,s=null,l=[]){for(const i of t||[]){const n=G();if(i&&i.type==="separator"){l.push({id:n,parentId:s,type:"separator"});continue}if(i&&i.type==="spacer"){l.push({id:n,parentId:s,type:"spacer",size:i.size||"md"});continue}l.push({id:n,parentId:s,text:i.text||"",url:i.url||"",icon:i.icon||"",visibility:i.visibility||"",permission:i.permission||"",hidden:!!i.hidden,bundled:!!i.bundled,badge:i.badge?{...i.badge}:null,pill:i.pill?{...i.pill}:null,colour:i.colour||"",highlight:i.highlight?{...i.highlight}:null}),Array.isArray(i.items)&&i.items.length&&ut(i.items,n,l)}return l}function Ht(t){const s=new Map;for(const i of t)s.has(i.parentId)||s.set(i.parentId,[]),s.get(i.parentId).push(i);function l(i){return(s.get(i)||[]).map(n=>{if(n.type==="separator")return{type:"separator"};if(n.type==="spacer")return{type:"spacer",...n.size&&n.size!=="md"&&{size:n.size}};const o=l(n.id);return{text:(n.text||"").trim(),url:(n.url||"").trim(),...n.icon&&{icon:n.icon.trim()},...n.visibility&&{visibility:n.visibility},...n.permission&&{permission:n.permission},...n.hidden&&{hidden:!0},...n.bundled&&{bundled:!0},...n.badge&&(n.badge.text||n.badge.countFrom)?{badge:{...n.badge.text&&{text:n.badge.text},...n.badge.countFrom&&{countFrom:n.badge.countFrom},...n.badge.variant&&{variant:n.badge.variant}}}:{},...n.pill&&n.pill.variant?{pill:{style:n.pill.style||"filled",variant:n.pill.variant}}:{},...n.colour&&{colour:n.colour},...n.highlight&&(n.highlight.hover||n.highlight.active||n.highlight.accent)?{highlight:{...n.highlight.hover&&{hover:n.highlight.hover},...n.highlight.active&&{active:n.highlight.active},...n.highlight.accent&&{accent:n.highlight.accent}}}:{},...o.length&&{items:o}}})}return l(null)}function h(t){return String(t||"").replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;")}function St(t,s){let l=0,i=s.find(n=>n.id===t);for(;i&&i.parentId;)l++,i=s.find(n=>n.id===i.parentId);return l}function ht(t,s){const{top:l,height:i}=t.getBoundingClientRect(),n=i?(s-l)/i:.5;return n<.25?"above":n>.75?"below":"into"}const gt=["me-drop-above","me-drop-below","me-drop-into"];function st(t){t.get(0).querySelectorAll(".menu-tree-row").forEach(s=>s.classList.remove(...gt))}function Ot(t,s,l){let i=null;t.get(0).querySelectorAll(".menu-tree-row").forEach(n=>{const o=n.querySelector(".menu-tree-grip");n.draggable=!1,o&&(o.addEventListener("mousedown",()=>{n.draggable=!0}),o.addEventListener("mouseup",()=>{n.draggable=!1})),n.addEventListener("dragstart",p=>{i=n.dataset.id,n.style.opacity="0.45",p.dataTransfer.effectAllowed="move";try{p.dataTransfer.setData("text/plain",i)}catch{}}),n.addEventListener("dragend",()=>{n.style.opacity="",n.draggable=!1,i=null,st(t)}),n.addEventListener("dragover",p=>{if(!i||i===n.dataset.id)return;p.preventDefault(),p.dataTransfer.dropEffect="move";const w=ht(n,p.clientY);st(t),n.classList.add(`me-drop-${w}`)}),n.addEventListener("dragleave",()=>n.classList.remove(...gt)),n.addEventListener("drop",p=>{p.preventDefault();const w=n.dataset.id,A=ht(n,p.clientY);if(st(t),!i)return;const y=_t(s(),i,w,A);i=null,y&&l()})})}function jt(t,s,l=()=>""){t.html("");function i(n){for(const o of s.filter(p=>p.parentId===n)){const p=St(o.id,s);if(o.type==="separator"){t.append(`
2
- <div class="menu-tree-row menu-tree-row--separator" data-id="${o.id}" style="margin-left:${p*28}px">
1
+ import{api as S}from"../api.js";import{colourToCss as It,HIGHLIGHT_HOVER as mt,HIGHLIGHT_ACTIVE as ut,HIGHLIGHT_HOVER_LABELS as ht,HIGHLIGHT_ACTIVE_LABELS as bt}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{openIconPicker as _t}from"../lib/card-builder.js";import{applyDrop as St}from"../lib/menu-tree.js";import{placeTools as Ht,ensureLockedTools as Ot,isLockedUrl as Lt,TOOLS_FOLDER_KEY as Tt}from"../lib/sidebar-grouping.js";import{flatten as jt,nest as Ct,uid as it}from"../lib/menu-rows.js";const Et=760,Rt="admin-sidebar";function g(t){return String(t||"").replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;")}function Nt(t,c){let r=0,i=c.find(a=>a.id===t);for(;i&&i.parentId;)r++,i=c.find(a=>a.id===i.parentId);return r}function gt(t,c){const{top:r,height:i}=t.getBoundingClientRect(),a=i?(c-r)/i:.5;return a<.25?"above":a>.75?"below":"into"}const vt=["me-drop-above","me-drop-below","me-drop-into"];function rt(t){t.get(0).querySelectorAll(".menu-tree-row").forEach(c=>c.classList.remove(...vt))}function Ft(t,c,r){let i=null;t.get(0).querySelectorAll(".menu-tree-row").forEach(a=>{const o=a.querySelector(".menu-tree-grip");a.draggable=!1,o&&(o.addEventListener("mousedown",()=>{a.draggable=!0}),o.addEventListener("mouseup",()=>{a.draggable=!1})),a.addEventListener("dragstart",m=>{i=a.dataset.id,a.style.opacity="0.45",m.dataTransfer.effectAllowed="move";try{m.dataTransfer.setData("text/plain",i)}catch{}}),a.addEventListener("dragend",()=>{a.style.opacity="",a.draggable=!1,i=null,rt(t)}),a.addEventListener("dragover",m=>{if(!i||i===a.dataset.id)return;m.preventDefault(),m.dataTransfer.dropEffect="move";const p=gt(a,m.clientY);rt(t),a.classList.add(`me-drop-${p}`)}),a.addEventListener("dragleave",()=>a.classList.remove(...vt)),a.addEventListener("drop",m=>{m.preventDefault();const p=a.dataset.id,x=gt(a,m.clientY);if(rt(t),!i)return;const k=St(c(),i,p,x);if(k){const v=c().find(L=>L.id===i);v&&(v.touched=!0)}i=null,k&&r()})})}function lt(t){const c=new Set,r=new Set,i=a=>{for(const o of t.filter(m=>m.parentId===a))!o.type&&Lt(o.url)&&!r.has(o.url)&&(r.add(o.url),c.add(o.id)),i(o.id)};return i(null),c}function zt(t,c,r=()=>"",i=!1){t.html("");const a=i?lt(c):new Set;function o(m){for(const p of c.filter(x=>x.parentId===m)){const x=Nt(p.id,c);if(p.type==="separator"){t.append(`
2
+ <div class="menu-tree-row menu-tree-row--separator" data-id="${p.id}" style="margin-left:${x*28}px">
3
3
  <span class="menu-tree-grip" data-icon="move"></span>
4
4
  <span class="me-sep-line"><span class="me-sep-label">Separator</span></span>
5
5
  <button class="btn btn-sm btn-ghost me-ins-item" data-tooltip="Add item below"><span data-icon="plus"></span></button>
@@ -10,12 +10,12 @@ import{api as k}from"../api.js";import{colourToCss as xt,HIGHLIGHT_HOVER as rt,H
10
10
  <button class="btn btn-sm btn-ghost me-down" data-tooltip="Move down"><span data-icon="arrow-down"></span></button>
11
11
  <button class="btn btn-sm btn-ghost me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
12
12
  </div>
13
- `);continue}if(o.type==="spacer"){const R=["xs","sm","md","lg","xl","flex"].map(L=>`<option value="${L}"${(o.size||"md")===L?" selected":""}>${L==="flex"?"flexible":L}</option>`).join("");t.append(`
14
- <div class="menu-tree-row menu-tree-row--spacer" data-id="${o.id}" style="margin-left:${p*28}px">
13
+ `);continue}if(p.type==="spacer"){const M=["xs","sm","md","lg","xl","flex"].map(s=>`<option value="${s}"${(p.size||"md")===s?" selected":""}>${s==="flex"?"flexible":s}</option>`).join("");t.append(`
14
+ <div class="menu-tree-row menu-tree-row--spacer" data-id="${p.id}" style="margin-left:${x*28}px">
15
15
  <span class="menu-tree-grip" data-icon="move"></span>
16
16
  <span class="me-spacer-line">
17
17
  <span class="me-spacer-label">Spacer</span>
18
- <select class="form-input me-spacer-size" data-tooltip="Spacer size - flexible fills the leftover room">${R}</select>
18
+ <select class="form-input me-spacer-size" data-tooltip="Spacer size - flexible fills the leftover room">${M}</select>
19
19
  </span>
20
20
  <button class="btn btn-sm btn-ghost me-ins-item" data-tooltip="Add item below"><span data-icon="plus"></span></button>
21
21
  <button class="btn btn-sm btn-ghost me-ins-sep" data-tooltip="Add separator below"><span data-icon="minus"></span></button>
@@ -25,23 +25,23 @@ import{api as k}from"../api.js";import{colourToCss as xt,HIGHLIGHT_HOVER as rt,H
25
25
  <button class="btn btn-sm btn-ghost me-down" data-tooltip="Move down"><span data-icon="arrow-down"></span></button>
26
26
  <button class="btn btn-sm btn-ghost me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
27
27
  </div>
28
- `);continue}const w=o.badge&&(o.badge.text||o.badge.countFrom)?`<span class="dm-menu-badge">${h(String(o.badge.text||"#"))}</span>`:"",A=xt(o.colour),y=A?` style="color:${h(A)}"`:"";t.append(`
29
- <div class="menu-tree-row menu-tree-row--slim${o.hidden?" menu-tree-row--hidden":""}" data-id="${o.id}" style="margin-left:${p*28}px">
28
+ `);continue}const k=p.badge&&(p.badge.text||p.badge.countFrom)?`<span class="dm-menu-badge">${g(String(p.badge.text||"#"))}</span>`:"",v=p.tool,L=v&&(v.persisted||p.touched),V=v?v.orphan?' <span class="badge badge-secondary me-tool-badge" data-tooltip="A placeholder for a Tool that is not enabled - it returns here when it is">Not enabled</span>':` <span class="badge badge-secondary me-tool-badge" data-tooltip="${L?"A Tool, placed here by this menu":"A Tool in its default place - move or edit it to place it here"}">${g(v.source||"Tool")}${L?"":" \xB7 default"}</span>`:"",q=a.has(p.id),Z=q?'<button class="btn btn-sm btn-ghost" disabled data-tooltip="Always in the sidebar - it can be moved, not removed"><span data-icon="lock"></span></button>':v&&!v.orphan?L?'<button class="btn btn-sm btn-ghost me-reset" data-tooltip="Back to its default place"><span data-icon="rotate-ccw"></span></button>':'<button class="btn btn-sm btn-ghost" disabled data-tooltip="A Tool cannot be removed - hide it instead"><span data-icon="trash"></span></button>':'<button class="btn btn-sm btn-ghost me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>',W=It(p.colour),C=W?` style="color:${g(W)}"`:"";t.append(`
29
+ <div class="menu-tree-row menu-tree-row--slim${p.hidden?" menu-tree-row--hidden":""}${v?" menu-tree-row--tool":""}${v&&!L?" menu-tree-row--default":""}" data-id="${p.id}" style="margin-left:${x*28}px">
30
30
  <span class="menu-tree-grip" data-icon="move"></span>
31
- <span class="me-row-label"${y}>${h(o.text||"(untitled)")}${w}</span>
32
- <span class="me-row-url">${h(o.url||"")}</span>
31
+ <span class="me-row-label"${C}>${g(p.text||v?.label||"(untitled)")}${k}${V}</span>
32
+ <span class="me-row-url">${g(p.url||"")}</span>
33
33
  <button class="btn btn-sm btn-ghost me-ins-item" data-tooltip="Add item below"><span data-icon="plus"></span></button>
34
34
  <button class="btn btn-sm btn-ghost me-ins-sep" data-tooltip="Add separator below"><span data-icon="minus"></span></button>
35
35
  <button class="btn btn-sm btn-ghost me-outdent" data-tooltip="Outdent"><span data-icon="chevron-left"></span></button>
36
36
  <button class="btn btn-sm btn-ghost me-indent" data-tooltip="Indent"><span data-icon="chevron-right"></span></button>
37
37
  <button class="btn btn-sm btn-ghost me-up" data-tooltip="Move up"><span data-icon="arrow-up"></span></button>
38
38
  <button class="btn btn-sm btn-ghost me-down" data-tooltip="Move down"><span data-icon="arrow-down"></span></button>
39
- <button class="btn btn-sm btn-ghost me-hidden ${o.hidden?"active":""}" data-tooltip="${o.hidden?"Show":"Hide"}"><span data-icon="eye-off"></span></button>
39
+ ${q?'<button class="btn btn-sm btn-ghost" disabled data-tooltip="Always shown"><span data-icon="eye-off"></span></button>':`<button class="btn btn-sm btn-ghost me-hidden ${p.hidden?"active":""}" data-tooltip="${p.hidden?"Show":"Hide"}"><span data-icon="eye-off"></span></button>`}
40
40
  <button class="btn btn-sm btn-secondary me-edit" data-tooltip="Edit"><span data-icon="edit"></span></button>
41
- <button class="btn btn-sm btn-ghost me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
41
+ ${Z}
42
42
  </div>
43
- `),i(o.id)}}i(null),Domma.icons.scan(t.get(0)),t.get(0).querySelectorAll("[data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})}function j(t,s){t.find(".menu-tree-row").each(function(){const l=$(this).data("id"),i=s.find(y=>y.id===l);if(!i||i.type==="separator")return;if(i.type==="spacer"){const y=$(this).find(".me-spacer-size");y.length&&(i.size=y.val());return}const n=$(this).find(".me-text");n.length&&(i.text=n.val());const o=$(this).find(".me-url");o.length&&(i.url=o.val());const p=$(this).find(".me-icon");p.length&&(i.icon=p.val());const w=$(this).find(".me-vis");w.length&&(i.visibility=w.val());const A=$(this).find(".me-perm");A.length&&(i.permission=A.val())})}function Rt(t,s){const l={};for(const i of s){const n=t.querySelector(`[name="${i}"]`);n&&(l[i]=n.type==="checkbox"?n.checked:n.value)}return l}function b(t,s={},...l){const i=document.createElement(t);Object.assign(i,s);for(const n of l)n!=null&&i.append(n);return i}function I(t,s,l){const i=b("label",{className:"form-field"},b("span",{className:"form-label",textContent:t}),s);return l&&i.append(b("small",{className:"form-hint",textContent:l,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),i}function Lt(t,s){const l=[["","- Everyone -"],["public","Public - anyone, incl. logged-out"],...(s||[]).map(n=>[n.name,`${n.label||n.name} and above`]),...(s||[]).filter(n=>n.level!==0).map(n=>[`=${n.name}`,`${n.label||n.name} only`]),["private","Private - super-admin only"]];t&&!l.some(([n])=>n===t)&&l.push([t,`${t} (custom)`]);const i=P(l,t||"");return i.name="visibility",i}function P(t,s){const l=b("select",{className:"form-select"});return t.forEach(([i,n])=>l.add(new Option(n,i))),l.value=s??"",l}function it(t,s){const l=typeof s=="string"&&s.startsWith("#"),i=P([["","- none -"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],l?"__custom__":s||""),n=b("input",{type:"color",className:"form-input me-colour-hex",value:l?s:"#000000"});n.style.display=l?"":"none",i.addEventListener("change",()=>{n.style.display=i.value==="__custom__"?"":"none"});const o=I(t,i);return o.append(n),o._read=()=>i.value==="__custom__"?n.value:i.value,o}function zt(t,s){const l=b("input",{className:"form-input me-icon",name:"icon",value:s||""}),i=b("span",{className:"me-icon-preview"});s&&i.setAttribute("data-icon",s);const n=b("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),o=p=>{p?i.setAttribute("data-icon",p):i.removeAttribute("data-icon"),Domma.icons.scan(i.parentNode||i)};return n.addEventListener("click",()=>At(p=>{l.value=p,o(p)})),l.addEventListener("input",()=>o(l.value.trim())),I(t,b("div",{className:"me-icon-field"},l,n,i))}function ot(...t){return b("div",{className:"me-field-grid"},...t)}async function Ct(t,s,l,i,n){const o=m=>b("h4",{className:"me-form-section",textContent:m}),p=I("Label",b("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),w=I("URL",b("input",{className:"form-input",name:"url",value:t.url||""}),"Where it links. Must start with /, #, https://, or mailto: . Leave blank for a parent that only groups sub-items."),A=zt("Icon",t.icon||""),y=I("Visibility",Lt(t.visibility||"",i),'Who can see this item. "Role and above" shows it to that role and anyone more senior; "Role only" shows it to holders of that role alone (and the super-admin). Blank = everyone.'),R=P([["","- no permission gate -"],...(s||[]).map(m=>[m.key,m.label||m.key])],t.permission||"");R.name="permission";const L=I("Permission",R,"Hide unless the viewer holds this specific permission. Independent of Visibility - both must pass."),lt=I("Badge text",b("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),J=P([["","- none -"],...(l||[]).map(m=>[m.slug,m.title||m.slug])],t.badge?.countFrom||"");J.name="badgeCount";const at=I("Badge count from",J,"Show a live count of this collection\u2019s entries as the badge."),Q=it("Badge colour",t.badge?.variant||""),d=P([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");d.name="renderAs";const x=I("Render as",d,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),v=I("Pill style",P([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));v.querySelector("select").name="pillStyle";const Y=it("Pill colour",t.pill?.variant||""),W=b("div",{className:"me-pill-wrap"},ot(v,Y));W.style.display=t.pill?"":"none",d.addEventListener("change",()=>{W.style.display=d.value==="pill"?"":"none"});const D=it("Text colour",t.colour||""),X=P([["","- use the menu default -"],...rt.map(m=>[m,mt[m]||m])],t.highlight?.hover||"");X.name="hlHover";const N=I("Hover effect",X,"Overrides the menu-wide hover effect for this item only."),M=P([["","- use the menu default -"],...pt.map(m=>[m,ft[m]||m])],t.highlight?.active||"");M.name="hlActive";const U=I("When selected",M,"How this item is marked when it points at the page being viewed."),tt=it("Highlight colour",t.highlight?.accent||""),B=b("button",{className:"btn btn-primary",textContent:"Apply"}),V=b("div",{className:"me-item-form"},o("Link"),p,w,A,o("Access"),ot(y,L),o("Badge"),ot(lt,Q),at,o("Appearance"),x,W,D,o("Highlighting"),ot(N,U),tt,B),S=E.slideover({title:"Edit menu item",size:"lg",position:"right"});S.element.appendChild(V),S.open();const K=S.element.closest(".dm-slideover")||S.element.querySelector(".dm-slideover")||S.element;K.style.setProperty("width",It+"px","important"),K.style.setProperty("max-width","95vw","important"),Domma.icons.scan(V);const g=m=>V.querySelector(`[name="${m}"]`)?.value||"";B.addEventListener("click",()=>{t.text=g("text"),t.url=g("url"),t.icon=g("icon"),t.visibility=g("visibility"),t.permission=g("permission");const m=g("badgeText"),q=g("badgeCount"),et=Q._read();if(t.badge=m||q?{...m&&{text:m},...q&&{countFrom:q},...et&&{variant:et}}:null,g("renderAs")==="pill"){const nt=Y._read();t.pill={style:g("pillStyle")||"filled",...nt&&{variant:nt}}}else t.pill=null;t.colour=D._read();const F=g("hlHover"),T=g("hlActive"),z=tt._read();t.highlight=F||T||z?{...F&&{hover:F},...T&&{active:T},...z&&{accent:z}}:null,S.close(),n()})}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const s=location.hash.match(/^#\/menus\/edit\/(.+)$/),l=s?decodeURIComponent(s[1]):null,i=!l,n=E.loader(t.get(0),{type:"dots"});let o,p,w,A,y,R,L;try{if(i)o={slug:"",name:"",description:"",items:[]};else try{o=await k.menus.get(l)}catch(e){E.toast(`Failed to load menu: ${e.message||e}`,{type:"error"}),location.hash="#/menus";return}p=await k.projects.list().catch(()=>[]),w=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),A=await k.collections.list().catch(()=>[]),y=await k.get("/collections/roles/entries?limit=100").catch(()=>null),R=await k.menuLocations.registry().catch(()=>[]),L=await k.pages.list().catch(()=>[])}finally{n.destroy()}const J=(Array.isArray(y)?y:y?.entries||[]).map(e=>({name:e.data?.name,label:e.data?.label,level:e.data?.level??99})).filter(e=>e.name).sort((e,a)=>a.level-e.level),at='<option value="">- no permission gate -</option>',Q=e=>at+(w.resources||[]).map(a=>{const c=(e||"")===a.key?" selected":"";return`<option value="${a.key}"${c}>${a.label||a.key}</option>`}).join("");t.find("#me-title").text(i?"New menu":`Edit "${o.slug}"`);let d=ut(o.items);const x=t.find("#me-items-list");function v(){jt(x,d,Q),Ot(x,()=>d,v)}v(),t.find("#me-add-item").on("click",()=>{j(x,d),d.push({id:G(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),v()}),t.find("#me-add-separator").on("click",()=>{j(x,d),d.push({id:G(),parentId:null,type:"separator"}),v()}),t.find("#me-add-spacer").on("click",()=>{j(x,d),d.push({id:G(),parentId:null,type:"spacer",size:"md"}),v()});function Y(e){return function(){j(x,d);const a=$(this).closest(".menu-tree-row").data("id"),c=d.findIndex(_=>_.id===a);if(c===-1)return;const u=d[c],r={separator:{type:"separator"},spacer:{type:"spacer",size:"md"}}[e],f=r?{id:G(),parentId:u.parentId,...r}:{id:G(),parentId:u.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};d.splice(c+1,0,f),v()}}t.off("click",".me-ins-item").on("click",".me-ins-item",Y("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",Y("separator")),t.off("click",".me-remove").on("click",".me-remove",function(){const e=$(this).closest(".menu-tree-row").data("id");d=d.filter(a=>a.id!==e&&a.parentId!==e),v()}),t.off("click",".me-indent").on("click",".me-indent",function(){j(x,d);const e=$(this).closest(".menu-tree-row").data("id"),a=d.find(r=>r.id===e);if(!a)return;const c=d.filter(r=>r.parentId===a.parentId),u=c.findIndex(r=>r.id===e);u<=0||["separator","spacer"].includes(c[u-1].type)||(a.parentId=c[u-1].id,v())}),t.off("click",".me-outdent").on("click",".me-outdent",function(){j(x,d);const e=$(this).closest(".menu-tree-row").data("id"),a=d.find(u=>u.id===e);if(!a||a.parentId==null)return;const c=d.find(u=>u.id===a.parentId);a.parentId=c?c.parentId:null,v()}),t.off("click",".me-up").on("click",".me-up",function(){j(x,d);const e=$(this).closest(".menu-tree-row").data("id"),a=d.filter(f=>f.parentId===d.find(_=>_.id===e)?.parentId),c=a.findIndex(f=>f.id===e);if(c<=0)return;const u=d.indexOf(a[c]),r=d.indexOf(a[c-1]);[d[u],d[r]]=[d[r],d[u]],v()}),t.off("click",".me-down").on("click",".me-down",function(){j(x,d);const e=$(this).closest(".menu-tree-row").data("id"),a=d.filter(f=>f.parentId===d.find(_=>_.id===e)?.parentId),c=a.findIndex(f=>f.id===e);if(c===-1||c>=a.length-1)return;const u=d.indexOf(a[c]),r=d.indexOf(a[c+1]);[d[u],d[r]]=[d[r],d[u]],v()}),t.off("click",".me-hidden").on("click",".me-hidden",function(){j(x,d);const e=$(this).closest(".menu-tree-row").data("id"),a=d.find(c=>c.id===e);a&&(a.hidden=!a.hidden),v()}),t.off("click",".me-edit").on("click",".me-edit",function(){const e=$(this).closest(".menu-tree-row").data("id"),a=d.find(c=>c.id===e);!a||["separator","spacer"].includes(a.type)||Ct(a,w.resources||[],A,J,()=>v())}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(o.variant||""),t.find("#me-position").val(o.position||"");const W=t.find("#me-appearOnHover").get(0);W&&(W.checked=o.appearOnHover===!0),t.find("#me-float-anchor").val(o.float?.anchor||"TL"),t.find("#me-float-offsetX").val(o.float?.offsetX??""),t.find("#me-float-offsetY").val(o.float?.offsetY??""),t.find("#me-float-width").val(o.float?.width??""),t.find("#me-float-radius").val(o.float?.radius??""),t.find("#me-float-shadow").val(o.float?.shadow||""),t.find("#me-float-opacity").val(o.float?.opacity??""),t.find("#me-float-title").val(o.float?.title||"");for(const[e,a]of[["#me-float-collapsible",o.float?.collapsible],["#me-float-collapsed",o.float?.collapsed],["#me-float-movable",o.float?.movable],["#me-float-resizable",o.float?.resizable]]){const c=t.find(e).get(0);c&&(c.checked=a===!0)}const D=o.float?.accent||"",X=D.startsWith("#"),N=t.find("#me-float-accent"),M=t.find("#me-float-accent-hex");N.val(X?"__custom__":D),X&&M.val(D);const U=()=>{M.get(0).style.display=N.val()==="__custom__"?"":"none"};U(),N.on("change",U);const tt=()=>N.val()==="__custom__"?M.val():N.val(),B=()=>{const e=t.find("#me-position").val()==="floating";t.find(".dm-float-field").each(function(){this.style.display=e?"":"none"}),e&&U();const a=t.find("#me-orientation").val()==="vertical";t.find(".dm-vertical-field").each(function(){this.style.display=a?"":"none"});const c=!!(t.find("#me-float-collapsible").get(0)||{}).checked;t.find(".dm-float-collapsed-field").each(function(){this.style.display=e&&c?"":"none"})};t.find("#me-orientation").val(o.orientation||""),t.find("#me-align").val(o.align||""),t.find("#me-side").val(o.side||"left"),B(),t.find("#me-position").on("change",B),t.find("#me-orientation").on("change",B),t.find("#me-float-collapsible").on("change",B);const V=(e,a,c,u)=>{const r=t.find(e).get(0);r&&(r.innerHTML="",r.add(new Option(u,"")),a.forEach(f=>r.add(new Option(c[f]||f,f))))};V("#me-highlight-hover",rt,mt,"- theme default -"),V("#me-highlight-active",pt,ft,"- theme default -"),t.find("#me-highlight-hover").val(o.highlight?.hover||""),t.find("#me-highlight-active").val(o.highlight?.active||"");const S=o.highlight?.accent||"",K=S.startsWith("#"),g=t.find("#me-highlight-accent"),m=t.find("#me-highlight-accent-hex");g.val(K?"__custom__":S),K&&m.val(S);const q=()=>{m.get(0).style.display=g.val()==="__custom__"?"":"none"};q(),g.on("change",q);const et=()=>g.val()==="__custom__"?m.val():g.val();t.find("#me-fontFamily").val(o.style?.fontFamily||""),t.find("#me-fontSize").val(o.style?.fontSize||""),t.find("#me-fontWeight").val(o.style?.fontWeight||""),t.find("#me-letterSpacing").val(o.style?.letterSpacing||""),t.find("#me-iconSize").val(o.style?.iconSize||"");const F=t.find("#me-bind-slot"),T=t.find("#me-bind-projects"),z=t.find("#me-bind-pages");F.html('<option value="">- not bound -</option>'+(R||[]).map(e=>`<option value="${h(e.slot)}">${h(e.label||e.slot)} (${h(e.slot)})</option>`).join("")),T.html(p.map(e=>`<option value="${h(e.slug)}">${h(e.name||e.slug)}</option>`).join("")),F.val(o.binding?.slot||"");const nt=new Set(o.binding?.projects||[]);Array.from(T.get(0).options).forEach(e=>{e.selected=nt.has(e.value)}),z.val((o.binding?.pages||[]).join(`
44
- `)),t.find("#me-bind-page-pick").html('<option value="">- pick a page -</option>'+L.map(e=>{const a=e.urlPath||e.url||"";return a?`<option value="${h(a)}">${h(e.title||a)} - ${h(a)}</option>`:""}).join("")),t.find("#me-bind-page-add").on("click",()=>{const e=t.find("#me-bind-page-pick").val();if(!e)return;const a=z.val().split(`
45
- `).map(c=>c.trim()).filter(Boolean);a.includes(e)||a.push(e),z.val(a.join(`
46
- `)),Z()});function dt(){const e=F.val();return e?{slot:e,projects:Array.from(T.get(0).selectedOptions).map(a=>a.value),pages:z.val().split(`
47
- `).map(a=>a.trim()).filter(Boolean)}:null}function Z(){const e=dt(),a=t.find("#me-bind-summary");if(!e){a.html('<span class="text-muted">Not bound - this menu renders only where it is mapped or placed by a shortcode.</span>');return}const c=[];e.projects.length&&c.push(`pages in ${e.projects.map(f=>`<code>${h(f)}</code>`).join(", ")}`),e.pages.length&&c.push(`pages matching ${e.pages.map(f=>`<code>${h(f)}</code>`).join(", ")}`);const u=e.pages.filter(f=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(f)),r=u.length?`<div class="text-danger" style="margin-top:.4rem">Not valid page patterns: ${u.map(f=>`<code>${h(f)}</code>`).join(", ")}</div>`:"";a.html(c.length?`Takes over the <code>${h(e.slot)}</code> slot on ${c.join(" and ")}.${r}`:`<span class="text-warning">Slot <code>${h(e.slot)}</code> selected but no project or page chosen - the binding will never match.</span>`)}F.on("change",Z),T.on("change",Z),z.on("input",Z),Z();const{getProjectFromHash:bt}=await import("../lib/project-context.js"),vt=i?bt():null,ct=t.find("#me-project");ct.html('<option value="">- none -</option>'+p.map(e=>`<option value="${h(e.slug)}">${h(e.name||e.slug)}</option>`).join("")),t.find("#me-slug").val(o.slug||""),t.find("#me-name").val(o.name||""),t.find("#me-description").val(o.description||""),ct.val(o.meta?.project||vt||""),Domma.icons.scan(t.get(0)),t.find("#me-save").on("click",async()=>{j(x,d);const e={variant:t.find("#me-variant").val(),position:t.find("#me-position").val(),appearOnHover:!!(t.find("#me-appearOnHover").get(0)||{}).checked,floatAnchor:t.find("#me-float-anchor").val(),floatOffsetX:t.find("#me-float-offsetX").val(),floatOffsetY:t.find("#me-float-offsetY").val(),floatWidth:t.find("#me-float-width").val(),floatRadius:t.find("#me-float-radius").val(),floatShadow:t.find("#me-float-shadow").val(),floatAccent:tt(),floatOpacity:t.find("#me-float-opacity").val(),floatTitle:t.find("#me-float-title").val().trim(),floatCollapsible:!!(t.find("#me-float-collapsible").get(0)||{}).checked,floatCollapsed:!!(t.find("#me-float-collapsed").get(0)||{}).checked,floatMovable:!!(t.find("#me-float-movable").get(0)||{}).checked,floatResizable:!!(t.find("#me-float-resizable").get(0)||{}).checked,highlightHover:t.find("#me-highlight-hover").val(),highlightActive:t.find("#me-highlight-active").val(),highlightAccent:et(),orientation:t.find("#me-orientation").val(),align:t.find("#me-align").val(),side:t.find("#me-side").val(),fontFamily:t.find("#me-fontFamily").val(),fontSize:t.find("#me-fontSize").val(),fontWeight:t.find("#me-fontWeight").val(),letterSpacing:t.find("#me-letterSpacing").val(),iconSize:t.find("#me-iconSize").val()},a={slug:t.find("#me-slug").val().trim(),name:t.find("#me-name").val().trim(),description:t.find("#me-description").val().trim(),project:t.find("#me-project").val()},c=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"],u=Object.fromEntries(Object.entries(e).filter(([O,C])=>c.includes(O)&&C)),r={};if(e.position==="floating"){e.floatAnchor&&(r.anchor=e.floatAnchor);for(const[O,C]of[["offsetX",e.floatOffsetX],["offsetY",e.floatOffsetY],["width",e.floatWidth],["radius",e.floatRadius],["opacity",e.floatOpacity]])C!==""&&C!=null&&Number.isFinite(Number(C))&&(r[O]=Number(C));e.floatShadow&&(r.shadow=e.floatShadow),e.floatAccent&&(r.accent=e.floatAccent),e.floatTitle&&(r.title=e.floatTitle),e.floatCollapsible&&(r.collapsible=!0),e.floatCollapsible&&e.floatCollapsed&&(r.collapsed=!0),e.floatMovable&&(r.movable=!0),e.floatResizable&&(r.resizable=!0)}const f={};e.highlightHover&&(f.hover=e.highlightHover),e.highlightActive&&(f.active=e.highlightActive),e.highlightAccent&&(f.accent=e.highlightAccent);const _={slug:a.slug,name:a.name,description:a.description,...e.variant&&{variant:e.variant},...e.position&&{position:e.position},...e.orientation&&{orientation:e.orientation},...e.orientation==="vertical"&&e.side&&{side:e.side},...e.orientation!=="vertical"&&e.align&&{align:e.align},...Object.keys(r).length&&{float:r},...Object.keys(f).length&&{highlight:f},...e.appearOnHover&&{appearOnHover:!0},...Object.keys(u).length&&{style:u},binding:dt(),items:Ht(d),meta:{...o.meta||{},project:a.project||null}};try{if(i)await k.menus.create(_);else if(_.slug!==o.slug){await k.menus.create(_);const O=await k.menuLocations.get();let C=!1;for(const[yt,wt]of Object.entries(O))wt===o.slug&&(O[yt]=_.slug,C=!0);C&&await k.menuLocations.save(O),await k.menus.remove(o.slug)}else await k.menus.update(o.slug,_);E.toast("Saved.",{type:"success"}),location.hash="#/menus/edit/"+encodeURIComponent(_.slug)}catch(O){E.toast(`Save failed: ${O.message||O}`,{type:"error"})}})}};
43
+ `),o(p.id)}}o(null),Domma.icons.scan(t.get(0)),t.get(0).querySelectorAll("[data-tooltip]").forEach(m=>{E.tooltip(m,{content:m.getAttribute("data-tooltip"),position:"top"})})}function j(t,c){t.find(".menu-tree-row").each(function(){const r=$(this).data("id"),i=c.find(k=>k.id===r);if(!i||i.type==="separator")return;if(i.type==="spacer"){const k=$(this).find(".me-spacer-size");k.length&&(i.size=k.val());return}const a=$(this).find(".me-text");a.length&&(i.text=a.val());const o=$(this).find(".me-url");o.length&&(i.url=o.val());const m=$(this).find(".me-icon");m.length&&(i.icon=m.val());const p=$(this).find(".me-vis");p.length&&(i.visibility=p.val());const x=$(this).find(".me-perm");x.length&&(i.permission=x.val())})}function Ut(t,c){const r={};for(const i of c){const a=t.querySelector(`[name="${i}"]`);a&&(r[i]=a.type==="checkbox"?a.checked:a.value)}return r}function w(t,c={},...r){const i=document.createElement(t);Object.assign(i,c);for(const a of r)a!=null&&i.append(a);return i}function O(t,c,r){const i=w("label",{className:"form-field"},w("span",{className:"form-label",textContent:t}),c);return r&&i.append(w("small",{className:"form-hint",textContent:r,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),i}function Pt(t,c){const r=[["","- Everyone -"],["public","Public - anyone, incl. logged-out"],...(c||[]).map(a=>[a.name,`${a.label||a.name} and above`]),...(c||[]).filter(a=>a.level!==0).map(a=>[`=${a.name}`,`${a.label||a.name} only`]),["private","Private - super-admin only"]];t&&!r.some(([a])=>a===t)&&r.push([t,`${t} (custom)`]);const i=D(r,t||"");return i.name="visibility",i}function D(t,c){const r=w("select",{className:"form-select"});return t.forEach(([i,a])=>r.add(new Option(a,i))),r.value=c??"",r}function dt(t,c){const r=typeof c=="string"&&c.startsWith("#"),i=D([["","- none -"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],r?"__custom__":c||""),a=w("input",{type:"color",className:"form-input me-colour-hex",value:r?c:"#000000"});a.style.display=r?"":"none",i.addEventListener("change",()=>{a.style.display=i.value==="__custom__"?"":"none"});const o=O(t,i);return o.append(a),o._read=()=>i.value==="__custom__"?a.value:i.value,o}function Bt(t,c){const r=w("input",{className:"form-input me-icon",name:"icon",value:c||""}),i=w("span",{className:"me-icon-preview"});c&&i.setAttribute("data-icon",c);const a=w("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),o=m=>{m?i.setAttribute("data-icon",m):i.removeAttribute("data-icon"),Domma.icons.scan(i.parentNode||i)};return a.addEventListener("click",()=>_t(m=>{r.value=m,o(m)})),r.addEventListener("input",()=>o(r.value.trim())),O(t,w("div",{className:"me-icon-field"},r,a,i))}function ct(...t){return w("div",{className:"me-field-grid"},...t)}async function Dt(t,c,r,i,a){const o=b=>w("h4",{className:"me-form-section",textContent:b}),m=O("Label",w("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),p=w("input",{className:"form-input",name:"url",value:t.url||""}),x=O("URL",p,t.tool||t.locked?"A Tool's address is its identity, so it cannot be changed here.":"Where it links. Must start with /, #, https://, or mailto: . Leave blank for a parent that only groups sub-items.");(t.tool||t.locked)&&(p.readOnly=!0);const k=Bt("Icon",t.icon||""),v=Pt(t.visibility||"",i),L=O("Visibility",v,t.locked?"Always in the sidebar for everyone who may use it, so it cannot be restricted here.":'Who can see this item. "Role and above" shows it to that role and anyone more senior; "Role only" shows it to holders of that role alone (and the super-admin). Blank = everyone.');t.locked&&(v.disabled=!0);const V=D([["","- no permission gate -"],...(c||[]).map(b=>[b.key,b.label||b.key])],t.permission||"");V.name="permission";const q=O("Permission",V,t.tool||t.locked?"A Tool is guarded by its own permission, which always applies.":"Hide unless the viewer holds this specific permission. Independent of Visibility - both must pass.");(t.tool||t.locked)&&(V.disabled=!0);const Z=O("Badge text",w("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),W=D([["","- none -"],...(r||[]).map(b=>[b.slug,b.title||b.slug])],t.badge?.countFrom||"");W.name="badgeCount";const C=O("Badge count from",W,"Show a live count of this collection\u2019s entries as the badge."),M=dt("Badge colour",t.badge?.variant||""),s=D([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");s.name="renderAs";const A=O("Render as",s,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),y=O("Pill style",D([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));y.querySelector("select").name="pillStyle";const J=dt("Pill colour",t.pill?.variant||""),Y=w("div",{className:"me-pill-wrap"},ct(y,J));Y.style.display=t.pill?"":"none",s.addEventListener("change",()=>{Y.style.display=s.value==="pill"?"":"none"});const Q=dt("Text colour",t.colour||""),U=D([["","- use the menu default -"],...mt.map(b=>[b,ht[b]||b])],t.highlight?.hover||"");U.name="hlHover";const at=O("Hover effect",U,"Overrides the menu-wide hover effect for this item only."),N=D([["","- use the menu default -"],...ut.map(b=>[b,bt[b]||b])],t.highlight?.active||"");N.name="hlActive";const tt=O("When selected",N,"How this item is marked when it points at the page being viewed."),X=dt("Highlight colour",t.highlight?.accent||""),st=w("button",{className:"btn btn-primary",textContent:"Apply"}),F=w("div",{className:"me-item-form"},o("Link"),m,x,k,o("Access"),ct(L,q),o("Badge"),ct(Z,M),C,o("Appearance"),A,Y,Q,o("Highlighting"),ct(at,tt),X,st),R=E.slideover({title:"Edit menu item",size:"lg",position:"right"});R.element.appendChild(F),R.open();const K=R.element.closest(".dm-slideover")||R.element.querySelector(".dm-slideover")||R.element;K.style.setProperty("width",Et+"px","important"),K.style.setProperty("max-width","95vw","important"),Domma.icons.scan(F);const _=b=>F.querySelector(`[name="${b}"]`)?.value||"";st.addEventListener("click",()=>{t.text=_("text"),t.icon=_("icon"),t.locked||(t.visibility=_("visibility")),!t.tool&&!t.locked?(t.url=_("url"),t.permission=_("permission")):t.touched=!0;const b=_("badgeText"),G=_("badgeCount"),et=M._read();if(t.badge=b||G?{...b&&{text:b},...G&&{countFrom:G},...et&&{variant:et}}:null,_("renderAs")==="pill"){const B=J._read();t.pill={style:_("pillStyle")||"filled",...B&&{variant:B}}}else t.pill=null;t.colour=Q._read();const nt=_("hlHover"),z=_("hlActive"),P=X._read();t.highlight=nt||z||P?{...nt&&{hover:nt},...z&&{active:z},...P&&{accent:P}}:null,R.close(),a()})}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const c=location.hash.match(/^#\/menus\/edit\/(.+)$/),r=c?decodeURIComponent(c[1]):null,i=!r,a=E.loader(t.get(0),{type:"dots"});let o,m,p,x,k,v,L;try{if(i)o={slug:"",name:"",description:"",items:[]};else try{o=await S.menus.get(r)}catch(e){E.toast(`Failed to load menu: ${e.message||e}`,{type:"error"}),location.hash="#/menus";return}m=await S.projects.list().catch(()=>[]),p=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),x=await S.collections.list().catch(()=>[]),k=await S.get("/collections/roles/entries?limit=100").catch(()=>null),v=await S.menuLocations.registry().catch(()=>[]),L=await S.pages.list().catch(()=>[])}finally{a.destroy()}const q=(Array.isArray(k)?k:k?.entries||[]).map(e=>({name:e.data?.name,label:e.data?.label,level:e.data?.level??99})).filter(e=>e.name).sort((e,n)=>n.level-e.level),Z='<option value="">- no permission gate -</option>',W=e=>Z+(p.resources||[]).map(n=>{const l=(e||"")===n.key?" selected":"";return`<option value="${n.key}"${l}>${n.label||n.key}</option>`}).join("");t.find("#me-title").text(i?"New menu":`Edit "${o.slug}"`);const C=!i&&r===Rt;let M=[];C&&(M=await H.get("/api/sidebar/registered-items").catch(()=>(E.toast("Could not load the Tools - they are not shown, and saving keeps their places as they are.",{type:"warning",duration:8e3}),[])));let s=jt(C?Ot(Ht(o.items||[],M,{forEditor:!0}),{leaveInHiddenFolders:!0}):o.items);const A=t.find("#me-items-list");function y(){zt(A,s,W,C),Ft(A,()=>s,y)}y(),t.find("#me-add-item").on("click",()=>{j(A,s),s.push({id:it(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),y()}),t.find("#me-add-separator").on("click",()=>{j(A,s),s.push({id:it(),parentId:null,type:"separator"}),y()}),t.find("#me-add-spacer").on("click",()=>{j(A,s),s.push({id:it(),parentId:null,type:"spacer",size:"md"}),y()});function J(e){return function(){j(A,s);const n=$(this).closest(".menu-tree-row").data("id"),l=s.findIndex(f=>f.id===n);if(l===-1)return;const u=s[l],d={separator:{type:"separator"},spacer:{type:"spacer",size:"md"}}[e],h=d?{id:it(),parentId:u.parentId,...d}:{id:it(),parentId:u.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};s.splice(l+1,0,h),y()}}t.off("click",".me-ins-item").on("click",".me-ins-item",J("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",J("separator"));function Y(e){const n=u=>s.find(d=>!d.type&&!d.url&&!d.tool&&(d.key||"").toLowerCase()===u)||s.find(d=>!d.type&&!d.url&&!d.tool&&!d.key&&(d.text||"").toLowerCase()===u),l=n(String(e.tool.folder||"").toLowerCase())||n(Tt);e.parentId=l?l.id:null,s.splice(s.indexOf(e),1),s.push(e),Object.assign(e,{text:e.tool.defaults.text,icon:e.tool.defaults.icon,visibility:"",hidden:!1,badge:null,pill:null,colour:"",highlight:null,touched:!1}),e.tool={...e.tool,persisted:!1}}t.off("click",".me-remove").on("click",".me-remove",function(){const e=$(this).closest(".menu-tree-row").data("id"),n=s.find(f=>f.id===e),l=f=>{let I=f;for(;I;){if(I.id===e)return!0;I=s.find(T=>T.id===I.parentId)}return!1},u=C?lt(s):new Set;if(u.has(e))return;const d=s.filter(f=>f.id!==e&&f.tool&&l(f)),h=s.filter(f=>f.id!==e&&!f.tool&&u.has(f.id)&&l(f));s=s.filter(f=>!l(f)||d.includes(f)||h.includes(f));for(const f of h)f.parentId=n?.parentId??null;for(const f of d)f.tool.orphan?f.parentId=n?.parentId??null:Y(f);y()}),t.off("click",".me-indent").on("click",".me-indent",function(){j(A,s);const e=$(this).closest(".menu-tree-row").data("id"),n=s.find(d=>d.id===e);if(!n)return;const l=s.filter(d=>d.parentId===n.parentId),u=l.findIndex(d=>d.id===e);u<=0||["separator","spacer"].includes(l[u-1].type)||(n.parentId=l[u-1].id,n.touched=!0,y())}),t.off("click",".me-outdent").on("click",".me-outdent",function(){j(A,s);const e=$(this).closest(".menu-tree-row").data("id"),n=s.find(u=>u.id===e);if(!n||n.parentId==null)return;const l=s.find(u=>u.id===n.parentId);n.parentId=l?l.parentId:null,n.touched=!0,y()}),t.off("click",".me-up").on("click",".me-up",function(){j(A,s);const e=$(this).closest(".menu-tree-row").data("id"),n=s.filter(h=>h.parentId===s.find(f=>f.id===e)?.parentId),l=n.findIndex(h=>h.id===e);if(l<=0)return;n[l].touched=!0;const u=s.indexOf(n[l]),d=s.indexOf(n[l-1]);[s[u],s[d]]=[s[d],s[u]],y()}),t.off("click",".me-down").on("click",".me-down",function(){j(A,s);const e=$(this).closest(".menu-tree-row").data("id"),n=s.filter(h=>h.parentId===s.find(f=>f.id===e)?.parentId),l=n.findIndex(h=>h.id===e);if(l===-1||l>=n.length-1)return;n[l].touched=!0;const u=s.indexOf(n[l]),d=s.indexOf(n[l+1]);[s[u],s[d]]=[s[d],s[u]],y()}),t.off("click",".me-hidden").on("click",".me-hidden",function(){j(A,s);const e=$(this).closest(".menu-tree-row").data("id"),n=s.find(l=>l.id===e);n&&!(C&&lt(s).has(n.id))&&(n.hidden=!n.hidden,n.touched=!0),y()}),t.off("click",".me-reset").on("click",".me-reset",function(){j(A,s);const e=$(this).closest(".menu-tree-row").data("id"),n=s.find(l=>l.id===e);!n?.tool||n.tool.orphan||(Y(n),y())}),t.off("click",".me-edit").on("click",".me-edit",function(){const e=$(this).closest(".menu-tree-row").data("id"),n=s.find(l=>l.id===e);!n||["separator","spacer"].includes(n.type)||(n.locked=C&&lt(s).has(n.id),Dt(n,p.resources||[],x,q,()=>y()))}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(o.variant||""),t.find("#me-position").val(o.position||"");const Q=t.find("#me-appearOnHover").get(0);Q&&(Q.checked=o.appearOnHover===!0),t.find("#me-float-anchor").val(o.float?.anchor||"TL"),t.find("#me-float-offsetX").val(o.float?.offsetX??""),t.find("#me-float-offsetY").val(o.float?.offsetY??""),t.find("#me-float-width").val(o.float?.width??""),t.find("#me-float-radius").val(o.float?.radius??""),t.find("#me-float-shadow").val(o.float?.shadow||""),t.find("#me-float-opacity").val(o.float?.opacity??""),t.find("#me-float-title").val(o.float?.title||"");for(const[e,n]of[["#me-float-collapsible",o.float?.collapsible],["#me-float-collapsed",o.float?.collapsed],["#me-float-movable",o.float?.movable],["#me-float-resizable",o.float?.resizable]]){const l=t.find(e).get(0);l&&(l.checked=n===!0)}const U=o.float?.accent||"",at=U.startsWith("#"),N=t.find("#me-float-accent"),tt=t.find("#me-float-accent-hex");N.val(at?"__custom__":U),at&&tt.val(U);const X=()=>{tt.get(0).style.display=N.val()==="__custom__"?"":"none"};X(),N.on("change",X);const st=()=>N.val()==="__custom__"?tt.val():N.val(),F=()=>{const e=t.find("#me-position").val()==="floating";t.find(".dm-float-field").each(function(){this.style.display=e?"":"none"}),e&&X();const n=t.find("#me-orientation").val()==="vertical";t.find(".dm-vertical-field").each(function(){this.style.display=n?"":"none"});const l=!!(t.find("#me-float-collapsible").get(0)||{}).checked;t.find(".dm-float-collapsed-field").each(function(){this.style.display=e&&l?"":"none"})};t.find("#me-orientation").val(o.orientation||""),t.find("#me-align").val(o.align||""),t.find("#me-side").val(o.side||"left"),F(),t.find("#me-position").on("change",F),t.find("#me-orientation").on("change",F),t.find("#me-float-collapsible").on("change",F);const R=(e,n,l,u)=>{const d=t.find(e).get(0);d&&(d.innerHTML="",d.add(new Option(u,"")),n.forEach(h=>d.add(new Option(l[h]||h,h))))};R("#me-highlight-hover",mt,ht,"- theme default -"),R("#me-highlight-active",ut,bt,"- theme default -"),t.find("#me-highlight-hover").val(o.highlight?.hover||""),t.find("#me-highlight-active").val(o.highlight?.active||"");const K=o.highlight?.accent||"",_=K.startsWith("#"),b=t.find("#me-highlight-accent"),G=t.find("#me-highlight-accent-hex");b.val(_?"__custom__":K),_&&G.val(K);const et=()=>{G.get(0).style.display=b.val()==="__custom__"?"":"none"};et(),b.on("change",et);const nt=()=>b.val()==="__custom__"?G.val():b.val();t.find("#me-fontFamily").val(o.style?.fontFamily||""),t.find("#me-fontSize").val(o.style?.fontSize||""),t.find("#me-fontWeight").val(o.style?.fontWeight||""),t.find("#me-letterSpacing").val(o.style?.letterSpacing||""),t.find("#me-iconSize").val(o.style?.iconSize||"");const z=t.find("#me-bind-slot"),P=t.find("#me-bind-projects"),B=t.find("#me-bind-pages");z.html('<option value="">- not bound -</option>'+(v||[]).map(e=>`<option value="${g(e.slot)}">${g(e.label||e.slot)} (${g(e.slot)})</option>`).join("")),P.html(m.map(e=>`<option value="${g(e.slug)}">${g(e.name||e.slug)}</option>`).join("")),z.val(o.binding?.slot||"");const yt=new Set(o.binding?.projects||[]);Array.from(P.get(0).options).forEach(e=>{e.selected=yt.has(e.value)}),B.val((o.binding?.pages||[]).join(`
44
+ `)),t.find("#me-bind-page-pick").html('<option value="">- pick a page -</option>'+L.map(e=>{const n=e.urlPath||e.url||"";return n?`<option value="${g(n)}">${g(e.title||n)} - ${g(n)}</option>`:""}).join("")),t.find("#me-bind-page-add").on("click",()=>{const e=t.find("#me-bind-page-pick").val();if(!e)return;const n=B.val().split(`
45
+ `).map(l=>l.trim()).filter(Boolean);n.includes(e)||n.push(e),B.val(n.join(`
46
+ `)),ot()});function pt(){const e=z.val();return e?{slot:e,projects:Array.from(P.get(0).selectedOptions).map(n=>n.value),pages:B.val().split(`
47
+ `).map(n=>n.trim()).filter(Boolean)}:null}function ot(){const e=pt(),n=t.find("#me-bind-summary");if(!e){n.html('<span class="text-muted">Not bound - this menu renders only where it is mapped or placed by a shortcode.</span>');return}const l=[];e.projects.length&&l.push(`pages in ${e.projects.map(h=>`<code>${g(h)}</code>`).join(", ")}`),e.pages.length&&l.push(`pages matching ${e.pages.map(h=>`<code>${g(h)}</code>`).join(", ")}`);const u=e.pages.filter(h=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(h)),d=u.length?`<div class="text-danger" style="margin-top:.4rem">Not valid page patterns: ${u.map(h=>`<code>${g(h)}</code>`).join(", ")}</div>`:"";n.html(l.length?`Takes over the <code>${g(e.slot)}</code> slot on ${l.join(" and ")}.${d}`:`<span class="text-warning">Slot <code>${g(e.slot)}</code> selected but no project or page chosen - the binding will never match.</span>`)}z.on("change",ot),P.on("change",ot),B.on("input",ot),ot();const{getProjectFromHash:wt}=await import("../lib/project-context.js"),kt=i?wt():null,ft=t.find("#me-project");ft.html('<option value="">- none -</option>'+m.map(e=>`<option value="${g(e.slug)}">${g(e.name||e.slug)}</option>`).join("")),t.find("#me-slug").val(o.slug||""),t.find("#me-name").val(o.name||""),t.find("#me-description").val(o.description||""),ft.val(o.meta?.project||kt||""),Domma.icons.scan(t.get(0)),t.find("#me-save").on("click",async()=>{j(A,s);const e={variant:t.find("#me-variant").val(),position:t.find("#me-position").val(),appearOnHover:!!(t.find("#me-appearOnHover").get(0)||{}).checked,floatAnchor:t.find("#me-float-anchor").val(),floatOffsetX:t.find("#me-float-offsetX").val(),floatOffsetY:t.find("#me-float-offsetY").val(),floatWidth:t.find("#me-float-width").val(),floatRadius:t.find("#me-float-radius").val(),floatShadow:t.find("#me-float-shadow").val(),floatAccent:st(),floatOpacity:t.find("#me-float-opacity").val(),floatTitle:t.find("#me-float-title").val().trim(),floatCollapsible:!!(t.find("#me-float-collapsible").get(0)||{}).checked,floatCollapsed:!!(t.find("#me-float-collapsed").get(0)||{}).checked,floatMovable:!!(t.find("#me-float-movable").get(0)||{}).checked,floatResizable:!!(t.find("#me-float-resizable").get(0)||{}).checked,highlightHover:t.find("#me-highlight-hover").val(),highlightActive:t.find("#me-highlight-active").val(),highlightAccent:nt(),orientation:t.find("#me-orientation").val(),align:t.find("#me-align").val(),side:t.find("#me-side").val(),fontFamily:t.find("#me-fontFamily").val(),fontSize:t.find("#me-fontSize").val(),fontWeight:t.find("#me-fontWeight").val(),letterSpacing:t.find("#me-letterSpacing").val(),iconSize:t.find("#me-iconSize").val()},n={slug:t.find("#me-slug").val().trim(),name:t.find("#me-name").val().trim(),description:t.find("#me-description").val().trim(),project:t.find("#me-project").val()},l=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"],u=Object.fromEntries(Object.entries(e).filter(([I,T])=>l.includes(I)&&T)),d={};if(e.position==="floating"){e.floatAnchor&&(d.anchor=e.floatAnchor);for(const[I,T]of[["offsetX",e.floatOffsetX],["offsetY",e.floatOffsetY],["width",e.floatWidth],["radius",e.floatRadius],["opacity",e.floatOpacity]])T!==""&&T!=null&&Number.isFinite(Number(T))&&(d[I]=Number(T));e.floatShadow&&(d.shadow=e.floatShadow),e.floatAccent&&(d.accent=e.floatAccent),e.floatTitle&&(d.title=e.floatTitle),e.floatCollapsible&&(d.collapsible=!0),e.floatCollapsible&&e.floatCollapsed&&(d.collapsed=!0),e.floatMovable&&(d.movable=!0),e.floatResizable&&(d.resizable=!0)}const h={};e.highlightHover&&(h.hover=e.highlightHover),e.highlightActive&&(h.active=e.highlightActive),e.highlightAccent&&(h.accent=e.highlightAccent);const f={slug:n.slug,name:n.name,description:n.description,...e.variant&&{variant:e.variant},...e.position&&{position:e.position},...e.orientation&&{orientation:e.orientation},...e.orientation==="vertical"&&e.side&&{side:e.side},...e.orientation!=="vertical"&&e.align&&{align:e.align},...Object.keys(d).length&&{float:d},...Object.keys(h).length&&{highlight:h},...e.appearOnHover&&{appearOnHover:!0},...Object.keys(u).length&&{style:u},binding:pt(),items:Ct(s),meta:{...o.meta||{},project:n.project||null}};try{if(i)await S.menus.create(f);else if(f.slug!==o.slug){await S.menus.create(f);const I=await S.menuLocations.get();let T=!1;for(const[xt,At]of Object.entries(I))At===o.slug&&(I[xt]=f.slug,T=!0);T&&await S.menuLocations.save(I),await S.menus.remove(o.slug)}else await S.menus.update(o.slug,f);E.toast("Saved.",{type:"success"}),location.hash="#/menus/edit/"+encodeURIComponent(f.slug)}catch(I){E.toast(`Save failed: ${I.message||I}`,{type:"error"})}})}};
@@ -1,3 +1,3 @@
1
- import{api as v,apiRequest as b}from"../api.js";export const settingsView={templateUrl:"/admin/js/templates/settings.html",async onMount(e){E.tabs(e.find("#settings-tabs").get(0));const p=E.loader(e.get(0),{type:"dots"}),l=await v.settings.get().catch(()=>({}));p.destroy(),e.find("#field-site-title").val(l.title||""),e.find("#field-tagline").val(l.tagline||""),e.find("#field-spacer-size").val(l.layoutOptions?.spacerSize??40),e.find("#field-seo-title").val(l.seo?.defaultTitle||""),e.find("#field-seo-separator").val(l.seo?.titleSeparator||" | "),e.find("#field-seo-desc").val(l.seo?.defaultDescription||""),e.find("#field-footer-copy").val(l.footer?.copyright||""),e.find("#field-footer-motion-toggle").prop("checked",l.footer?.showMotionToggle!==!1),e.find("#field-social-twitter").val(l.social?.twitter||""),e.find("#field-social-facebook").val(l.social?.facebook||""),e.find("#field-social-instagram").val(l.social?.instagram||""),e.find("#field-social-linkedin").val(l.social?.linkedin||""),e.find("#field-social-github").val(l.social?.github||""),e.find("#field-social-youtube").val(l.social?.youtube||""),e.find("#field-smtp-host").val(l.smtp?.host||""),e.find("#field-smtp-port").val(l.smtp?.port||587),e.find("#field-smtp-user").val(l.smtp?.user||""),e.find("#field-smtp-pass").val(l.smtp?.pass||""),e.find("#field-smtp-secure").prop("checked",l.smtp?.secure||!1),e.find("#field-smtp-from-address").val(l.smtp?.fromAddress||""),e.find("#field-smtp-from-name").val(l.smtp?.fromName||""),y(l.smtp?.host||""),e.find("#field-smtp-host").on("input",function(){y(this.value)});const c=l.backToTop||{};e.find("#field-btt-enabled").prop("checked",c.enabled!==!1),e.find("#field-btt-threshold").val(c.scrollThreshold??300),e.find("#field-btt-position").val(c.position||"bottom-right"),e.find("#field-btt-offset").val(c.offset??32),e.find("#field-btt-bottom-offset").val(c.bottomOffset??c.offset??32),e.find("#field-btt-label").val(c.label||""),e.find("#field-btt-smooth").prop("checked",c.smooth!==!1);const i=l.cookieConsent||{};e.find("#field-cc-enabled").prop("checked",i.enabled!==!1),e.find("#field-cc-message").val(i.message||""),e.find("#field-cc-accept-all").val(i.acceptAllText||"Accept All"),e.find("#field-cc-reject-all").val(i.rejectAllText||"Reject All"),e.find("#field-cc-customize").val(i.customizeText||"Customize"),e.find("#field-cc-save-prefs").val(i.savePreferencesText||"Save Preferences"),e.find("#field-cc-privacy-text").val(i.privacyPolicyText||"Privacy Policy"),e.find("#field-cc-privacy-url").val(i.privacyPolicyUrl||""),e.find("#field-cc-cookie-text").val(i.cookiePolicyText||"Cookie Policy"),e.find("#field-cc-cookie-url").val(i.cookiePolicyUrl||""),e.find("#field-cc-position").val(i.position||"bottom"),e.find("#field-cc-layout").val(i.layout||"bar"),e.find("#field-cc-theme").val(i.theme||"dark"),e.find("#field-cc-show-functional").prop("checked",i.showFunctional!==!1),e.find("#field-cc-show-analytics").prop("checked",i.showAnalytics!==!1),e.find("#field-cc-show-marketing").prop("checked",i.showMarketing!==!1),e.find("#field-cc-version").val(i.consentVersion||"1.0");const m=l.breadcrumbs||{};e.find("#field-breadcrumbs-enabled").prop("checked",m.enabled===!0),e.find("#field-breadcrumbs-home-label").val(m.homeLabel||"Home"),e.find("#field-breadcrumbs-position").val(m.position||"TL"),e.find("#field-bc-offset-x").val(m.offsetX??8),e.find("#field-bc-offset-y").val(m.offsetY??8);function u(t){e.find(".bc-pos-btn").each(function(){const s=this.dataset.pos===t;this.style.background=s?"var(--dm-primary, #5b8cff)":"",this.style.color=s?"var(--dm-primary-text)":""})}u(m.position||"TL"),e.find(".bc-pos-btn").on("click",function(){const t=this.dataset.pos;e.find("#field-breadcrumbs-position").val(t),u(t)});let n=null;try{const{css:t}=await b("/settings/custom-css");e.find("#field-custom-css").val(t||""),E.editor&&(n=E.editor(e.find("#field-custom-css").get(0),{mode:"code",language:"css",lineNumbers:!0,showToolbar:!1,minHeight:420,placeholder:"/* Add your custom CSS here */",characterCount:!0}))}catch{}n&&n._editorEl&&n._editorEl.addEventListener("keydown",t=>{if(!t.ctrlKey&&!t.metaKey)return;if(t.key==="s"){t.preventDefault(),e.find("#save-css-btn").get(0)?.click();return}const s=n._editorEl,a=s.selectionStart!==s.selectionEnd;if((t.key==="c"||t.key==="x")&&!a){t.preventDefault();const d=s.value,o=s.selectionStart,r=d.lastIndexOf(`
2
- `,o-1)+1,h=d.indexOf(`
3
- `,o),g=h===-1?d.slice(r):d.slice(r,h+1);if(navigator.clipboard.writeText(g),t.key==="x"){const k=d.slice(0,r),x=h===-1?"":d.slice(h+1);s.value=k+x,s.selectionStart=s.selectionEnd=r,s.dispatchEvent(new Event("input",{bubbles:!0})),E.toast("Line cut",{type:"info",duration:1500})}else E.toast("Line copied",{type:"info",duration:1500})}}),e.find("#save-settings-btn").on("click",async()=>{const t={title:e.find("#field-site-title").val().trim(),tagline:e.find("#field-tagline").val().trim(),layoutOptions:{spacerSize:parseInt(e.find("#field-spacer-size").val(),10)||40},seo:{defaultTitle:e.find("#field-seo-title").val().trim(),titleSeparator:e.find("#field-seo-separator").val()||" | ",defaultDescription:e.find("#field-seo-desc").val().trim()},footer:{copyright:e.find("#field-footer-copy").val().trim(),showMotionToggle:e.find("#field-footer-motion-toggle").prop("checked"),links:l.footer?.links||[]},social:{twitter:e.find("#field-social-twitter").val().trim(),facebook:e.find("#field-social-facebook").val().trim(),instagram:e.find("#field-social-instagram").val().trim(),linkedin:e.find("#field-social-linkedin").val().trim(),github:e.find("#field-social-github").val().trim(),youtube:e.find("#field-social-youtube").val().trim()},smtp:{host:e.find("#field-smtp-host").val().trim(),port:parseInt(e.find("#field-smtp-port").val(),10)||587,user:e.find("#field-smtp-user").val().trim(),pass:e.find("#field-smtp-pass").val(),secure:e.find("#field-smtp-secure").prop("checked"),fromAddress:e.find("#field-smtp-from-address").val().trim(),fromName:e.find("#field-smtp-from-name").val().trim()},backToTop:{enabled:e.find("#field-btt-enabled").prop("checked"),scrollThreshold:parseInt(e.find("#field-btt-threshold").val(),10)||300,position:e.find("#field-btt-position").val()||"bottom-right",offset:parseInt(e.find("#field-btt-offset").val(),10)||32,bottomOffset:parseInt(e.find("#field-btt-bottom-offset").val(),10)||32,label:e.find("#field-btt-label").val().trim(),smooth:e.find("#field-btt-smooth").prop("checked")},cookieConsent:{enabled:e.find("#field-cc-enabled").prop("checked"),message:e.find("#field-cc-message").val().trim(),acceptAllText:e.find("#field-cc-accept-all").val().trim(),rejectAllText:e.find("#field-cc-reject-all").val().trim(),customizeText:e.find("#field-cc-customize").val().trim(),savePreferencesText:e.find("#field-cc-save-prefs").val().trim(),privacyPolicyText:e.find("#field-cc-privacy-text").val().trim(),privacyPolicyUrl:e.find("#field-cc-privacy-url").val().trim(),cookiePolicyText:e.find("#field-cc-cookie-text").val().trim(),cookiePolicyUrl:e.find("#field-cc-cookie-url").val().trim(),position:e.find("#field-cc-position").val(),layout:e.find("#field-cc-layout").val(),theme:e.find("#field-cc-theme").val(),showFunctional:e.find("#field-cc-show-functional").prop("checked"),showAnalytics:e.find("#field-cc-show-analytics").prop("checked"),showMarketing:e.find("#field-cc-show-marketing").prop("checked"),consentVersion:e.find("#field-cc-version").val().trim()||"1.0"}},s=e.find("#save-settings-btn");s.prop("disabled",!0);try{await v.settings.save(t),E.toast("Settings saved.",{type:"success"})}catch{E.toast("Failed to save settings.",{type:"error"})}finally{s.prop("disabled",!1)}}),e.find("#send-test-email-btn").on("click",async()=>{const t=e.find("#field-test-email-to").val().trim(),s=e.find("#test-email-result").get(0),a=e.find("#send-test-email-btn");a.prop("disabled",!0),s&&(s.textContent="Sending\u2026",s.style.color="");try{const d=await b("/settings/test-email",{method:"POST",body:JSON.stringify({to:t||void 0})});s&&(s.textContent=d.message||"Test email sent.",s.style.color="var(--dm-success,#4ade80)")}catch(d){s&&(s.textContent=d.message||"Failed to send test email.",s.style.color="var(--dm-danger,#f87171)")}finally{a.prop("disabled",!1)}}),e.find("#save-breadcrumbs-btn").on("click",async()=>{const t=e.find("#save-breadcrumbs-btn");t.prop("disabled",!0);try{const s={enabled:e.find("#field-breadcrumbs-enabled").prop("checked"),homeLabel:e.find("#field-breadcrumbs-home-label").val().trim()||"Home",position:e.find("#field-breadcrumbs-position").val()||"TL",offsetX:parseInt(e.find("#field-bc-offset-x").val(),10)||8,offsetY:parseInt(e.find("#field-bc-offset-y").val(),10)||8};await v.settings.save({breadcrumbs:s}),l.breadcrumbs=s,E.toast("Breadcrumbs settings saved.",{type:"success"})}catch{E.toast("Failed to save breadcrumbs settings.",{type:"error"})}finally{t.prop("disabled",!1)}}),e.find("#save-css-btn").on("click",async()=>{const t=n?n.getValue():e.find("#field-custom-css").val(),s=e.find("#save-css-btn");s.prop("disabled",!0);try{await b("/settings/custom-css",{method:"PUT",body:JSON.stringify({css:t})}),E.toast("Custom CSS saved.",{type:"success"})}catch(a){E.toast(a.message||"Failed to save CSS.",{type:"error"})}finally{s.prop("disabled",!1)}});async function f(){try{const t=await b("/cache/status"),s=t.enabled?`enabled (${t.driver})`:"disabled",a=t.maxItems?` \xB7 ${t.size} / ${t.maxItems} entries`:"";e.find("#cache-status").text(s+a)}catch{e.find("#cache-status").text("unknown")}try{const{entries:t}=await b("/cache/keys");w(e.find("#cache-keys-table"),t),e.find("#cache-key-count").text(`(${t.length})`)}catch(t){e.find("#cache-keys-table").get(0).textContent="Failed to load keys: "+t.message}}await f(),e.find("#btn-clear-cache").on("click",async()=>{if(!await E.confirm("Clear the entire response cache?"))return;const t=e.find("#btn-clear-cache");t.prop("disabled",!0);try{await b("/cache/clear",{method:"POST"}),E.toast("Cache cleared.",{type:"success"}),await f()}catch(s){E.toast(s.message||"Failed to clear cache.",{type:"error"})}finally{t.prop("disabled",!1)}}),e.find("#btn-refresh-cache-keys").on("click",f)}};function y(e){const p=document.getElementById("smtp-unconfigured-notice");p&&(p.style.display=String(e||"").trim()?"none":"",p.style.display===""&&Domma.icons.scan(p))}function w(e,p){const l=e.get(0);if(!l)return;for(;l.firstChild;)l.removeChild(l.firstChild);if(!p.length){const f=document.createElement("p");f.className="text-muted",f.style.fontSize=".875rem",f.textContent="Cache is empty.",l.appendChild(f);return}const c=[...p].reverse(),i=document.createElement("table");i.className="table table-compact";const m=document.createElement("thead"),u=document.createElement("tr");for(const f of["Key","Tags","Expires"]){const t=document.createElement("th");t.textContent=f,u.appendChild(t)}m.appendChild(u),i.appendChild(m);const n=document.createElement("tbody");for(const f of c){const t=document.createElement("tr"),s=document.createElement("td");s.style.fontFamily="monospace",s.style.fontSize="12px",s.textContent=f.key,t.appendChild(s);const a=document.createElement("td");a.style.fontSize="12px";for(const o of f.tags){const r=document.createElement("span");r.className="badge",r.style.marginRight="4px",r.style.fontSize="11px",r.textContent=o,a.appendChild(r)}t.appendChild(a);const d=document.createElement("td");if(d.style.fontSize="12px",d.style.color="var(--dm-text-muted, #6b7280)",f.expiresAt===null)d.textContent="no expiry";else{const o=f.expiresAt-Date.now();o<=0?d.textContent="expired":o<6e4?d.textContent=`${Math.round(o/1e3)}s`:o<36e5?d.textContent=`${Math.round(o/6e4)}m`:d.textContent=`${Math.round(o/36e5)}h`}t.appendChild(d),n.appendChild(t)}i.appendChild(n),l.appendChild(i)}
1
+ import{api as v,apiRequest as b}from"../api.js";import{placeTools as w}from"../lib/sidebar-grouping.js";async function C(e,f){if(!e)return;const[l,c]=await Promise.all([H.get("/api/sidebar/menu").catch(()=>({items:[]})),H.get("/api/sidebar/registered-items").catch(()=>[])]),i=new Map([["/","Dashboard (built in)"]]),n=(a,d)=>{for(const t of a||[]){if(!t||t.hidden||t.restricted)continue;const s=typeof t.url=="string"?t.url:"";s.startsWith("#/")&&s.length>2&&!i.has(s.slice(1))&&i.set(s.slice(1),`${d?`${d} \u203A `:""}${t.text||s}`),n(t.items,t.url?d:t.text||d)}};n(w(l?.items||[],c||[]),"");const m=/^\/[A-Za-z0-9\-_/]*$/.test(f||"")&&f.replace(/\/+$/,"")||"/";i.has(m)||i.set(m,`${m} (not in the sidebar)`);for(const[a,d]of i){const t=document.createElement("option");t.value=a,t.textContent=d,t.selected=a===m,e.appendChild(t)}}export const settingsView={templateUrl:"/admin/js/templates/settings.html",async onMount(e){E.tabs(e.find("#settings-tabs").get(0));const f=E.loader(e.get(0),{type:"dots"}),l=await v.settings.get().catch(()=>({}));f.destroy(),e.find("#field-site-title").val(l.title||""),e.find("#field-tagline").val(l.tagline||""),C(e.find("#field-admin-home").get(0),l.adminHome),e.find("#field-spacer-size").val(l.layoutOptions?.spacerSize??40),e.find("#field-seo-title").val(l.seo?.defaultTitle||""),e.find("#field-seo-separator").val(l.seo?.titleSeparator||" | "),e.find("#field-seo-desc").val(l.seo?.defaultDescription||""),e.find("#field-footer-copy").val(l.footer?.copyright||""),e.find("#field-footer-motion-toggle").prop("checked",l.footer?.showMotionToggle!==!1),e.find("#field-social-twitter").val(l.social?.twitter||""),e.find("#field-social-facebook").val(l.social?.facebook||""),e.find("#field-social-instagram").val(l.social?.instagram||""),e.find("#field-social-linkedin").val(l.social?.linkedin||""),e.find("#field-social-github").val(l.social?.github||""),e.find("#field-social-youtube").val(l.social?.youtube||""),e.find("#field-smtp-host").val(l.smtp?.host||""),e.find("#field-smtp-port").val(l.smtp?.port||587),e.find("#field-smtp-user").val(l.smtp?.user||""),e.find("#field-smtp-pass").val(l.smtp?.pass||""),e.find("#field-smtp-secure").prop("checked",l.smtp?.secure||!1),e.find("#field-smtp-from-address").val(l.smtp?.fromAddress||""),e.find("#field-smtp-from-name").val(l.smtp?.fromName||""),y(l.smtp?.host||""),e.find("#field-smtp-host").on("input",function(){y(this.value)});const c=l.backToTop||{};e.find("#field-btt-enabled").prop("checked",c.enabled!==!1),e.find("#field-btt-threshold").val(c.scrollThreshold??300),e.find("#field-btt-position").val(c.position||"bottom-right"),e.find("#field-btt-offset").val(c.offset??32),e.find("#field-btt-bottom-offset").val(c.bottomOffset??c.offset??32),e.find("#field-btt-label").val(c.label||""),e.find("#field-btt-smooth").prop("checked",c.smooth!==!1);const i=l.cookieConsent||{};e.find("#field-cc-enabled").prop("checked",i.enabled!==!1),e.find("#field-cc-message").val(i.message||""),e.find("#field-cc-accept-all").val(i.acceptAllText||"Accept All"),e.find("#field-cc-reject-all").val(i.rejectAllText||"Reject All"),e.find("#field-cc-customize").val(i.customizeText||"Customize"),e.find("#field-cc-save-prefs").val(i.savePreferencesText||"Save Preferences"),e.find("#field-cc-privacy-text").val(i.privacyPolicyText||"Privacy Policy"),e.find("#field-cc-privacy-url").val(i.privacyPolicyUrl||""),e.find("#field-cc-cookie-text").val(i.cookiePolicyText||"Cookie Policy"),e.find("#field-cc-cookie-url").val(i.cookiePolicyUrl||""),e.find("#field-cc-position").val(i.position||"bottom"),e.find("#field-cc-layout").val(i.layout||"bar"),e.find("#field-cc-theme").val(i.theme||"dark"),e.find("#field-cc-show-functional").prop("checked",i.showFunctional!==!1),e.find("#field-cc-show-analytics").prop("checked",i.showAnalytics!==!1),e.find("#field-cc-show-marketing").prop("checked",i.showMarketing!==!1),e.find("#field-cc-version").val(i.consentVersion||"1.0");const n=l.breadcrumbs||{};e.find("#field-breadcrumbs-enabled").prop("checked",n.enabled===!0),e.find("#field-breadcrumbs-home-label").val(n.homeLabel||"Home"),e.find("#field-breadcrumbs-position").val(n.position||"TL"),e.find("#field-bc-offset-x").val(n.offsetX??8),e.find("#field-bc-offset-y").val(n.offsetY??8);function m(t){e.find(".bc-pos-btn").each(function(){const s=this.dataset.pos===t;this.style.background=s?"var(--dm-primary, #5b8cff)":"",this.style.color=s?"var(--dm-primary-text)":""})}m(n.position||"TL"),e.find(".bc-pos-btn").on("click",function(){const t=this.dataset.pos;e.find("#field-breadcrumbs-position").val(t),m(t)});let a=null;try{const{css:t}=await b("/settings/custom-css");e.find("#field-custom-css").val(t||""),E.editor&&(a=E.editor(e.find("#field-custom-css").get(0),{mode:"code",language:"css",lineNumbers:!0,showToolbar:!1,minHeight:420,placeholder:"/* Add your custom CSS here */",characterCount:!0}))}catch{}a&&a._editorEl&&a._editorEl.addEventListener("keydown",t=>{if(!t.ctrlKey&&!t.metaKey)return;if(t.key==="s"){t.preventDefault(),e.find("#save-css-btn").get(0)?.click();return}const s=a._editorEl,r=s.selectionStart!==s.selectionEnd;if((t.key==="c"||t.key==="x")&&!r){t.preventDefault();const o=s.value,p=s.selectionStart,h=o.lastIndexOf(`
2
+ `,p-1)+1,u=o.indexOf(`
3
+ `,p),g=u===-1?o.slice(h):o.slice(h,u+1);if(navigator.clipboard.writeText(g),t.key==="x"){const k=o.slice(0,h),x=u===-1?"":o.slice(u+1);s.value=k+x,s.selectionStart=s.selectionEnd=h,s.dispatchEvent(new Event("input",{bubbles:!0})),E.toast("Line cut",{type:"info",duration:1500})}else E.toast("Line copied",{type:"info",duration:1500})}}),e.find("#save-settings-btn").on("click",async()=>{const t={title:e.find("#field-site-title").val().trim(),tagline:e.find("#field-tagline").val().trim(),...e.find("#field-admin-home option").length&&{adminHome:e.find("#field-admin-home").val()||"/"},layoutOptions:{spacerSize:parseInt(e.find("#field-spacer-size").val(),10)||40},seo:{defaultTitle:e.find("#field-seo-title").val().trim(),titleSeparator:e.find("#field-seo-separator").val()||" | ",defaultDescription:e.find("#field-seo-desc").val().trim()},footer:{copyright:e.find("#field-footer-copy").val().trim(),showMotionToggle:e.find("#field-footer-motion-toggle").prop("checked"),links:l.footer?.links||[]},social:{twitter:e.find("#field-social-twitter").val().trim(),facebook:e.find("#field-social-facebook").val().trim(),instagram:e.find("#field-social-instagram").val().trim(),linkedin:e.find("#field-social-linkedin").val().trim(),github:e.find("#field-social-github").val().trim(),youtube:e.find("#field-social-youtube").val().trim()},smtp:{host:e.find("#field-smtp-host").val().trim(),port:parseInt(e.find("#field-smtp-port").val(),10)||587,user:e.find("#field-smtp-user").val().trim(),pass:e.find("#field-smtp-pass").val(),secure:e.find("#field-smtp-secure").prop("checked"),fromAddress:e.find("#field-smtp-from-address").val().trim(),fromName:e.find("#field-smtp-from-name").val().trim()},backToTop:{enabled:e.find("#field-btt-enabled").prop("checked"),scrollThreshold:parseInt(e.find("#field-btt-threshold").val(),10)||300,position:e.find("#field-btt-position").val()||"bottom-right",offset:parseInt(e.find("#field-btt-offset").val(),10)||32,bottomOffset:parseInt(e.find("#field-btt-bottom-offset").val(),10)||32,label:e.find("#field-btt-label").val().trim(),smooth:e.find("#field-btt-smooth").prop("checked")},cookieConsent:{enabled:e.find("#field-cc-enabled").prop("checked"),message:e.find("#field-cc-message").val().trim(),acceptAllText:e.find("#field-cc-accept-all").val().trim(),rejectAllText:e.find("#field-cc-reject-all").val().trim(),customizeText:e.find("#field-cc-customize").val().trim(),savePreferencesText:e.find("#field-cc-save-prefs").val().trim(),privacyPolicyText:e.find("#field-cc-privacy-text").val().trim(),privacyPolicyUrl:e.find("#field-cc-privacy-url").val().trim(),cookiePolicyText:e.find("#field-cc-cookie-text").val().trim(),cookiePolicyUrl:e.find("#field-cc-cookie-url").val().trim(),position:e.find("#field-cc-position").val(),layout:e.find("#field-cc-layout").val(),theme:e.find("#field-cc-theme").val(),showFunctional:e.find("#field-cc-show-functional").prop("checked"),showAnalytics:e.find("#field-cc-show-analytics").prop("checked"),showMarketing:e.find("#field-cc-show-marketing").prop("checked"),consentVersion:e.find("#field-cc-version").val().trim()||"1.0"}},s=e.find("#save-settings-btn");s.prop("disabled",!0);try{await v.settings.save(t),document.dispatchEvent(new CustomEvent("dm:admin-home-changed")),E.toast("Settings saved.",{type:"success"})}catch{E.toast("Failed to save settings.",{type:"error"})}finally{s.prop("disabled",!1)}}),e.find("#send-test-email-btn").on("click",async()=>{const t=e.find("#field-test-email-to").val().trim(),s=e.find("#test-email-result").get(0),r=e.find("#send-test-email-btn");r.prop("disabled",!0),s&&(s.textContent="Sending\u2026",s.style.color="");try{const o=await b("/settings/test-email",{method:"POST",body:JSON.stringify({to:t||void 0})});s&&(s.textContent=o.message||"Test email sent.",s.style.color="var(--dm-success,#4ade80)")}catch(o){s&&(s.textContent=o.message||"Failed to send test email.",s.style.color="var(--dm-danger,#f87171)")}finally{r.prop("disabled",!1)}}),e.find("#save-breadcrumbs-btn").on("click",async()=>{const t=e.find("#save-breadcrumbs-btn");t.prop("disabled",!0);try{const s={enabled:e.find("#field-breadcrumbs-enabled").prop("checked"),homeLabel:e.find("#field-breadcrumbs-home-label").val().trim()||"Home",position:e.find("#field-breadcrumbs-position").val()||"TL",offsetX:parseInt(e.find("#field-bc-offset-x").val(),10)||8,offsetY:parseInt(e.find("#field-bc-offset-y").val(),10)||8};await v.settings.save({breadcrumbs:s}),l.breadcrumbs=s,E.toast("Breadcrumbs settings saved.",{type:"success"})}catch{E.toast("Failed to save breadcrumbs settings.",{type:"error"})}finally{t.prop("disabled",!1)}}),e.find("#save-css-btn").on("click",async()=>{const t=a?a.getValue():e.find("#field-custom-css").val(),s=e.find("#save-css-btn");s.prop("disabled",!0);try{await b("/settings/custom-css",{method:"PUT",body:JSON.stringify({css:t})}),E.toast("Custom CSS saved.",{type:"success"})}catch(r){E.toast(r.message||"Failed to save CSS.",{type:"error"})}finally{s.prop("disabled",!1)}});async function d(){try{const t=await b("/cache/status"),s=t.enabled?`enabled (${t.driver})`:"disabled",r=t.maxItems?` \xB7 ${t.size} / ${t.maxItems} entries`:"";e.find("#cache-status").text(s+r)}catch{e.find("#cache-status").text("unknown")}try{const{entries:t}=await b("/cache/keys");T(e.find("#cache-keys-table"),t),e.find("#cache-key-count").text(`(${t.length})`)}catch(t){e.find("#cache-keys-table").get(0).textContent="Failed to load keys: "+t.message}}await d(),e.find("#btn-clear-cache").on("click",async()=>{if(!await E.confirm("Clear the entire response cache?"))return;const t=e.find("#btn-clear-cache");t.prop("disabled",!0);try{await b("/cache/clear",{method:"POST"}),E.toast("Cache cleared.",{type:"success"}),await d()}catch(s){E.toast(s.message||"Failed to clear cache.",{type:"error"})}finally{t.prop("disabled",!1)}}),e.find("#btn-refresh-cache-keys").on("click",d)}};function y(e){const f=document.getElementById("smtp-unconfigured-notice");f&&(f.style.display=String(e||"").trim()?"none":"",f.style.display===""&&Domma.icons.scan(f))}function T(e,f){const l=e.get(0);if(!l)return;for(;l.firstChild;)l.removeChild(l.firstChild);if(!f.length){const d=document.createElement("p");d.className="text-muted",d.style.fontSize=".875rem",d.textContent="Cache is empty.",l.appendChild(d);return}const c=[...f].reverse(),i=document.createElement("table");i.className="table table-compact";const n=document.createElement("thead"),m=document.createElement("tr");for(const d of["Key","Tags","Expires"]){const t=document.createElement("th");t.textContent=d,m.appendChild(t)}n.appendChild(m),i.appendChild(n);const a=document.createElement("tbody");for(const d of c){const t=document.createElement("tr"),s=document.createElement("td");s.style.fontFamily="monospace",s.style.fontSize="12px",s.textContent=d.key,t.appendChild(s);const r=document.createElement("td");r.style.fontSize="12px";for(const p of d.tags){const h=document.createElement("span");h.className="badge",h.style.marginRight="4px",h.style.fontSize="11px",h.textContent=p,r.appendChild(h)}t.appendChild(r);const o=document.createElement("td");if(o.style.fontSize="12px",o.style.color="var(--dm-text-muted, #6b7280)",d.expiresAt===null)o.textContent="no expiry";else{const p=d.expiresAt-Date.now();p<=0?o.textContent="expired":p<6e4?o.textContent=`${Math.round(p/1e3)}s`:p<36e5?o.textContent=`${Math.round(p/6e4)}m`:o.textContent=`${Math.round(p/36e5)}h`}t.appendChild(o),a.appendChild(t)}i.appendChild(a),l.appendChild(i)}
@@ -1 +1 @@
1
- import{api as x}from"../api.js";import{populateThemeSelect as T}from"../lib/themes.js";const ce=[{value:"#site-navbar",label:"Navigation bar"},{value:"#site-footer",label:"Footer"},{value:".site-content",label:"Page content area"},{value:".page-body",label:"Page body (inside the container)"},{value:".site-sidebar",label:"Sidebar"},{value:".card",label:"Every card"},{value:".cms-form-embed",label:"Embedded forms"},{value:".cms-collection",label:"Collection displays"}];export const themeView={templateUrl:"/admin/js/templates/theme.html",async onMount(o){E.tabs(o.find("#theme-tabs").get(0));const K=E.loader(o.get(0),{type:"dots"});let d,V;try{[d,V]=await Promise.all([x.theme.get(),x.theme.catalog().catch(()=>({themes:[],tokens:[]}))])}catch(e){K.destroy(),E.toast(`Could not load theme settings: ${e.message||e}`,{type:"error"});return}K.destroy();const z=V.tokens||[],B=new Map;async function _(e){if(!e)return null;if(B.has(e))return B.get(e);const t=await x.theme.themeTokens(e).catch(()=>null),n=t&&t.tokens?t.tokens:null;return B.set(e,n),n}function ne(){const e=o.find("#theme-auto-enabled").prop("checked");return d.baseTheme||(e?S.val():f.val())||d.theme}const X=["--dm-card-bg","--dm-surface"],Y=[...X,"--dm-text","--dm-primary","--dm-background"];function H(e){const t=document.createElement("span");return t.style.cssText="display:inline-block;width:.7rem;height:.7rem;border-radius:2px;border:1px solid var(--dm-border);vertical-align:middle;margin-right:.25rem;",/^#[0-9a-f]{3,8}$/i.test(e)?t.style.background=e:t.style.background="transparent",t}async function D(e){const t=e.querySelector(".ov-theme-diff");if(!t)return;for(;t.firstChild;)t.removeChild(t.firstChild);const n=e.querySelector(".ov-theme")?.value;if(!n)return;const s=ne();if(n===s){const r=document.createElement("p");r.className="form-hint text-muted",r.style.cssText="margin:.4rem 0 0;font-size:.8rem;",r.textContent="That is already the site theme, so this changes nothing on its own. Set a token below instead.",t.appendChild(r);return}const[a,l]=await Promise.all([_(s),_(n)]);if(!a||!l)return;const c=Object.keys(l).filter(r=>String(a[r]??"").toLowerCase()!==String(l[r]).toLowerCase()),i=X.filter(r=>c.includes(r)),m=document.createElement("div");m.style.cssText="margin-top:.5rem;padding:.5rem .6rem;border-radius:var(--dm-radius-md);background:var(--dm-surface-raised);font-size:.8rem;";const h=document.createElement("p");if(h.style.cssText="margin:0 0 .35rem;",h.textContent=`Changes ${c.length} of ${Object.keys(l).length} tokens here.`,m.appendChild(h),!i.length){const r=document.createElement("p");r.style.cssText="margin:0 0 .35rem;color:var(--dm-warning);",r.textContent="Both themes use the same surface colour, so this region keeps the fill it has now. Set --dm-card-bg (a card) or --dm-surface (most other regions) below to change it.",m.appendChild(r)}const p=document.createElement("div");p.style.cssText="display:flex;flex-wrap:wrap;gap:.15rem .75rem;";const v=[...Y.filter(r=>c.includes(r)),...c.filter(r=>!Y.includes(r))];for(const r of v.slice(0,6)){const u=document.createElement("span");u.style.cssText="white-space:nowrap;color:var(--dm-text-muted);",u.appendChild(H(a[r]||""));const C=document.createElement("span");C.textContent=`${r} `,u.appendChild(C),u.appendChild(H(l[r])),p.appendChild(u)}if(v.length>6){const r=document.createElement("span");r.style.cssText="color:var(--dm-text-muted);",r.textContent=`+${v.length-6} more`,p.appendChild(r)}m.appendChild(p),t.appendChild(m)}let b=Array.isArray(d.overrides)?d.overrides.map(J):[];const f=o.find("#theme-frontend"),F=o.find("#theme-admin");T(f.get(0)),T(F.get(0),{includeAdmin:!0}),f.val(d.baseTheme||d.theme||"charcoal-dark"),F.val(d.adminTheme||"charcoal-dark"),d.baseTheme&&(f.prop("disabled",!0),o.find("#theme-auto-enabled").prop("disabled",!0),o.find("#theme-auto-roller-hint").show());const S=o.find("#theme-day"),L=o.find("#theme-night");T(S.get(0)),T(L.get(0));const y=d.autoTheme||{};S.val(y.dayTheme||"charcoal-light"),L.val(y.nightTheme||"charcoal-dark"),o.find("#theme-day-start").val(y.dayStart||"07:00"),o.find("#theme-night-start").val(y.nightStart||"19:00");const N=d.switcher||{},q=o.find("#theme-switcher-enabled"),oe=o.find("#theme-switcher-fields");q.prop("checked",N.enabled===!0),o.find("#theme-switcher-position").val(N.position||"right-center"),o.find("#theme-switcher-offset-x").val(N.offsetX??32),o.find("#theme-switcher-offset-y").val(N.offsetY??32);const U=()=>{oe.get(0).style.display=q.prop("checked")?"":"none"};U(),q.on("change",U);const W=o.find("#theme-auto-enabled"),G=o.find("#theme-auto-fields");d.baseTheme||(W.prop("checked",!!y.enabled),y.enabled&&(G.show(),f.prop("disabled",!0)),W.on("change",function(){G.toggle(this.checked),f.prop("disabled",this.checked)})),o.find("#theme-font-family").val(d.font?.family||"Roboto"),o.find("#theme-font-size").val(String(d.font?.size??16));function J(e){return{id:e.id||`ov-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,label:e.label||"",enabled:e.enabled!==!1,scope:e.scope||"",theme:e.theme||"",tokens:{...e.tokens||{}},css:e.css||""}}const g=document.getElementById("theme-overrides"),se=document.getElementById("theme-overrides-empty");function P(){for(;g.firstChild;)g.removeChild(g.firstChild);se.style.display=b.length?"none":"",b.forEach((e,t)=>g.appendChild(ae(e,t))),Domma.icons.scan("#theme-overrides")}function ae(e,t){const n=document.createElement("div");n.className="card mb-3",n.dataset.i=String(t);const s=document.createElement("div");s.className="card-header";const a=document.createElement("h3");a.className="m-0",a.style.fontSize="1rem",a.textContent=e.label||e.scope||`Override ${t+1}`,s.appendChild(a);const l=document.createElement("div");l.style.cssText="display:flex;gap:.5rem;align-items:center;";const c=document.createElement("label");c.style.cssText="display:flex;align-items:center;gap:.35rem;font-size:.85rem;cursor:pointer;";const i=document.createElement("input");i.type="checkbox",i.className="form-check ov-enabled",i.checked=e.enabled,c.appendChild(i),c.appendChild(document.createTextNode("Enabled")),l.appendChild(c);const m=document.createElement("button");m.type="button",m.className="btn btn-ghost btn-sm ov-remove",m.textContent="Remove",l.appendChild(m),s.appendChild(l),n.appendChild(s);const h=document.createElement("div");h.className="card-body";const p=document.createElement("div");p.className="row mb-3",p.appendChild(M("col-4","Name",$("ov-label",e.label,"Navbar island"))),p.appendChild(M("col-8","Target selector",le(e.scope),"The region to re-theme. Everything inside it follows.")),h.appendChild(p);const v=document.createElement("div");v.className="row mb-3";const r=document.createElement("select");r.className="form-select ov-theme",T(r,{includeDefault:!0,defaultLabel:"Keep the site theme"}),r.value=e.theme||"",v.appendChild(M("col-6","Adopt a theme here",r,"Copies every token of that theme onto this region.")),h.appendChild(v);const u=document.createElement("div");u.className="ov-theme-diff",h.appendChild(u);const C=document.createElement("div");C.className="ov-tokens mb-2",Object.entries(e.tokens||{}).forEach(([re,de])=>{C.appendChild(Q(re,de))}),h.appendChild(Z("Token overrides",C));const A=document.createElement("button");A.type="button",A.className="btn btn-ghost btn-sm ov-add-token",A.textContent="+ Add token",h.appendChild(A);const w=document.createElement("details");w.style.marginTop="1rem";const R=document.createElement("summary");R.style.cssText="cursor:pointer;font-size:.85rem;",R.textContent="Custom CSS for this override",w.appendChild(R);const k=document.createElement("textarea");k.className="form-input ov-css",k.rows=4,k.placeholder="#site-navbar .brand { letter-spacing: .04em; }",k.value=e.css||"",k.style.marginTop=".5rem",w.appendChild(k);const I=document.createElement("p");return I.className="form-hint text-muted",I.style.cssText="font-size:.8rem;margin-top:.35rem;",I.textContent="Appended verbatim after the generated rule - you own these selectors. Prefer tokens: they survive a Domma upgrade, hand-written rules fight the cascade.",w.appendChild(I),h.appendChild(w),n.appendChild(h),setTimeout(()=>D(n),0),n}function Q(e,t){const n=document.createElement("div");n.className="ov-token-row",n.style.cssText="display:flex;gap:.5rem;align-items:center;margin-bottom:.5rem;";const s=document.createElement("select");if(s.className="form-select ov-token-name",s.style.flex="1 1 auto",z.length){for(const i of z){const m=document.createElement("option");m.value=i,m.textContent=i,s.appendChild(m)}s.value=e||z[0]}else{const i=document.createElement("option");i.value=e||"",i.textContent=e||"(no tokens found)",s.appendChild(i)}const a=document.createElement("input");a.type="text",a.className="form-input ov-token-value",a.style.flex="0 0 150px",a.placeholder="#0096c7",a.value=t||"";const l=document.createElement("input");l.type="color",l.className="ov-token-swatch",l.style.cssText="flex:0 0 38px;height:34px;padding:0;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);background:none;cursor:pointer;",/^#[0-9a-f]{6}$/i.test(t||"")&&(l.value=t),l.addEventListener("input",()=>{a.value=l.value,a.dispatchEvent(new Event("input",{bubbles:!0}))});const c=document.createElement("button");return c.type="button",c.className="btn btn-ghost btn-sm ov-token-remove",c.textContent="\xD7",n.append(s,a,l,c),n}function M(e,t,n,s){const a=document.createElement("div");return a.className=e,a.appendChild(Z(t,n,s)),a}function Z(e,t,n){const s=document.createDocumentFragment(),a=document.createElement("label");if(a.className="form-label",a.textContent=e,s.appendChild(a),s.appendChild(t),n){const l=document.createElement("p");l.className="form-hint text-muted",l.style.cssText="margin-top:.35rem;font-size:.8rem;",l.textContent=n,s.appendChild(l)}return s}function $(e,t,n){const s=document.createElement("input");return s.type="text",s.className=`form-input ${e}`,s.value=t||"",s.placeholder=n||"",s}function le(e){const t=document.createElement("div"),n=$("ov-scope",e,"#site-navbar");if(n.setAttribute("list","theme-scope-options"),t.appendChild(n),!document.getElementById("theme-scope-options")){const s=document.createElement("datalist");s.id="theme-scope-options";for(const a of ce){const l=document.createElement("option");l.value=a.value,l.label=a.label,s.appendChild(l)}t.appendChild(s)}return t}function O(){const e=g.querySelectorAll(".card"),t=[];return e.forEach((n,s)=>{const a={};n.querySelectorAll(".ov-token-row").forEach(l=>{const c=l.querySelector(".ov-token-name")?.value?.trim(),i=l.querySelector(".ov-token-value")?.value?.trim();c&&i&&(a[c]=i)}),t.push({id:b[s]?.id,label:n.querySelector(".ov-label")?.value?.trim()||"",enabled:!!n.querySelector(".ov-enabled")?.checked,scope:n.querySelector(".ov-scope")?.value?.trim()||"",theme:n.querySelector(".ov-theme")?.value||"",tokens:a,css:n.querySelector(".ov-css")?.value||""})}),b=t,t}o.on("click",".ov-remove",function(){const e=Number(this.closest(".card").dataset.i);O(),b.splice(e,1),P()}),o.on("change",".ov-theme",function(){D(this.closest(".card"))}),o.on("change","#theme-frontend, #theme-auto-enabled, #theme-day",()=>{g.querySelectorAll(".card").forEach(D)}),o.on("click",".ov-add-token",function(){this.closest(".card").querySelector(".ov-tokens").appendChild(Q("",""))}),o.on("click",".ov-token-remove",function(){this.closest(".ov-token-row").remove()}),o.find("#theme-add-override").on("click",()=>{O(),b.push(J({label:"",scope:"",enabled:!0})),P()}),P();async function j(){const e=document.getElementById("theme-css-preview"),t={...ee(),overrides:O()};try{const n=await x.theme.compile(t);e.textContent=n.css?.trim()||"(no overrides - nothing is injected)",e.style.color=""}catch(n){e.textContent=n.message||String(n),e.style.color="var(--dm-danger)"}}o.find("#theme-refresh-css").on("click",j);function ee(){const e=!d.baseTheme&&o.find("#theme-auto-enabled").prop("checked"),t={enabled:e,dayTheme:S.val()||"charcoal-light",nightTheme:L.val()||"charcoal-dark",dayStart:o.find("#theme-day-start").val()||"07:00",nightStart:o.find("#theme-night-start").val()||"19:00"};return{theme:d.baseTheme?d.theme:e?t.dayTheme:f.val(),...d.baseTheme?{baseTheme:d.baseTheme}:{},adminTheme:F.val(),autoTheme:t,font:{family:o.find("#theme-font-family").val()||"Roboto",size:parseInt(o.find("#theme-font-size").val(),10)||16},switcher:{enabled:q.prop("checked"),position:o.find("#theme-switcher-position").val()||"right-center",offsetX:parseInt(o.find("#theme-switcher-offset-x").val(),10)||0,offsetY:parseInt(o.find("#theme-switcher-offset-y").val(),10)||0,themes:Array.isArray(d.switcher?.themes)?d.switcher.themes:[]}}}function te(e){const t=document.getElementById("theme-errors");for(;t.firstChild;)t.removeChild(t.firstChild);if(!e||!e.length){t.style.display="none";return}t.style.display="";for(const n of e){const s=document.createElement("div");s.textContent=n,t.appendChild(s)}t.scrollIntoView({behavior:"smooth",block:"nearest"})}o.find("#theme-save-btn").on("click",async function(){te(null);const e={...ee(),overrides:O()};this.disabled=!0;try{d=await x.theme.save(e),Domma.theme.set(e.adminTheme),E.toast("Theme saved.",{type:"success"}),await j()}catch(t){const n=t.validation||[t.message||"Could not save the theme."];te(n),E.toast("Theme not saved.",{type:"error"})}finally{this.disabled=!1}}),await j()}};
1
+ import{api as w}from"../api.js";import{populateThemeSelect as A}from"../lib/themes.js";const he=[{value:"#site-navbar",label:"Navigation bar"},{value:"#site-footer",label:"Footer"},{value:".site-content",label:"Page content area"},{value:".page-body",label:"Page body (inside the container)"},{value:".site-sidebar",label:"Sidebar"},{value:".card",label:"Every card"},{value:".cms-form-embed",label:"Embedded forms"},{value:".cms-collection",label:"Collection displays"}];export const themeView={templateUrl:"/admin/js/templates/theme.html",async onMount(s){E.tabs(s.find("#theme-tabs").get(0));const X=E.loader(s.get(0),{type:"dots"});let m,q;try{[m,q]=await Promise.all([w.theme.get(),w.theme.catalog().catch(()=>({themes:[],tokens:[]}))])}catch(e){X.destroy(),E.toast(`Could not load theme settings: ${e.message||e}`,{type:"error"});return}X.destroy();const D=q.tokens||[],Y=Array.isArray(q.custom)?q.custom:[];function x(e){if(!Y.length)return;const t=document.createElement("optgroup");t.label="Custom themes";for(const n of Y){const o=document.createElement("option");o.value=n.id,o.textContent=`${n.label} (on ${n.baseTheme})`,t.appendChild(o)}e.appendChild(t)}function T(e,t){const n=e.get?e.get(0):e;if(t&&![...n.options].some(o=>o.value===t)){const o=document.createElement("option");o.value=t,o.textContent=`${t} (not available)`,n.appendChild(o)}n.value=t}const M=new Map;async function H(e){if(!e)return null;if(M.has(e))return M.get(e);const t=await w.theme.themeTokens(e).catch(()=>null),n=t&&t.tokens?t.tokens:null;return M.set(e,n),n}function ae(){return(s.find("#theme-auto-enabled").prop("checked")?S.val():f.val())||m.theme}const U=["--dm-card-bg","--dm-surface"],W=[...U,"--dm-text","--dm-primary","--dm-background"];function G(e){const t=document.createElement("span");return t.style.cssText="display:inline-block;width:.7rem;height:.7rem;border-radius:2px;border:1px solid var(--dm-border);vertical-align:middle;margin-right:.25rem;",/^#[0-9a-f]{3,8}$/i.test(e)?t.style.background=e:t.style.background="transparent",t}async function j(e){const t=e.querySelector(".ov-theme-diff");if(!t)return;for(;t.firstChild;)t.removeChild(t.firstChild);const n=e.querySelector(".ov-theme")?.value;if(!n)return;const o=ae();if(n===o){const l=document.createElement("p");l.className="form-hint text-muted",l.style.cssText="margin:.4rem 0 0;font-size:.8rem;",l.textContent="That is already the site theme, so this changes nothing on its own. Set a token below instead.",t.appendChild(l);return}const[a,r]=await Promise.all([H(o),H(n)]);if(!a||!r)return;const c=Object.keys(r).filter(l=>String(a[l]??"").toLowerCase()!==String(r[l]).toLowerCase()),d=U.filter(l=>c.includes(l)),i=document.createElement("div");i.style.cssText="margin-top:.5rem;padding:.5rem .6rem;border-radius:var(--dm-radius-md);background:var(--dm-surface-raised);font-size:.8rem;";const h=document.createElement("p");if(h.style.cssText="margin:0 0 .35rem;",h.textContent=`Changes ${c.length} of ${Object.keys(r).length} tokens here.`,i.appendChild(h),!d.length){const l=document.createElement("p");l.style.cssText="margin:0 0 .35rem;color:var(--dm-warning);",l.textContent="Both themes use the same surface colour, so this region keeps the fill it has now. Set --dm-card-bg (a card) or --dm-surface (most other regions) below to change it.",i.appendChild(l)}const p=document.createElement("div");p.style.cssText="display:flex;flex-wrap:wrap;gap:.15rem .75rem;";const v=[...W.filter(l=>c.includes(l)),...c.filter(l=>!W.includes(l))];for(const l of v.slice(0,6)){const u=document.createElement("span");u.style.cssText="white-space:nowrap;color:var(--dm-text-muted);",u.appendChild(G(a[l]||""));const C=document.createElement("span");C.textContent=`${l} `,u.appendChild(C),u.appendChild(G(r[l])),p.appendChild(u)}if(v.length>6){const l=document.createElement("span");l.style.cssText="color:var(--dm-text-muted);",l.textContent=`+${v.length-6} more`,p.appendChild(l)}i.appendChild(p),t.appendChild(i)}let b=Array.isArray(m.overrides)?m.overrides.map($):[];const f=s.find("#theme-frontend"),O=s.find("#theme-admin");A(f.get(0)),A(O.get(0),{includeAdmin:!0}),x(f.get(0)),x(O.get(0)),T(f,m.theme||"charcoal-dark"),T(O,m.adminTheme||"charcoal-dark");const S=s.find("#theme-day"),I=s.find("#theme-night");A(S.get(0)),A(I.get(0)),x(S.get(0)),x(I.get(0));const g=m.autoTheme||{};T(S,g.dayTheme||"charcoal-light"),T(I,g.nightTheme||"charcoal-dark"),s.find("#theme-day-start").val(g.dayStart||"07:00"),s.find("#theme-night-start").val(g.nightStart||"19:00");const z=m.switcher||{},B=s.find("#theme-switcher-enabled"),le=s.find("#theme-switcher-fields");B.prop("checked",z.enabled===!0),s.find("#theme-switcher-position").val(z.position||"right-center"),s.find("#theme-switcher-offset-x").val(z.offsetX??32),s.find("#theme-switcher-offset-y").val(z.offsetY??32);const J=()=>{le.get(0).style.display=B.prop("checked")?"":"none"};J(),B.on("change",J);const Q=s.find("#theme-auto-enabled"),Z=s.find("#theme-auto-fields");Q.prop("checked",!!g.enabled),g.enabled&&(Z.show(),f.prop("disabled",!0)),Q.on("change",function(){Z.toggle(this.checked),f.prop("disabled",this.checked)}),s.find("#theme-font-family").val(m.font?.family||"Roboto"),s.find("#theme-font-size").val(String(m.font?.size??16));function $(e){return{id:e.id||`ov-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,label:e.label||"",enabled:e.enabled!==!1,scope:e.scope||"",theme:e.theme||"",tokens:{...e.tokens||{}},css:e.css||""}}const y=document.getElementById("theme-overrides"),re=document.getElementById("theme-overrides-empty");function R(){for(;y.firstChild;)y.removeChild(y.firstChild);re.style.display=b.length?"none":"",b.forEach((e,t)=>y.appendChild(ce(e,t))),Domma.icons.scan("#theme-overrides")}function ce(e,t){const n=document.createElement("div");n.className="card mb-3",n.dataset.i=String(t);const o=document.createElement("div");o.className="card-header";const a=document.createElement("h3");a.className="m-0",a.style.fontSize="1rem",a.textContent=e.label||e.scope||`Override ${t+1}`,o.appendChild(a);const r=document.createElement("div");r.style.cssText="display:flex;gap:.5rem;align-items:center;";const c=document.createElement("label");c.style.cssText="display:flex;align-items:center;gap:.35rem;font-size:.85rem;cursor:pointer;";const d=document.createElement("input");d.type="checkbox",d.className="form-check ov-enabled",d.checked=e.enabled,c.appendChild(d),c.appendChild(document.createTextNode("Enabled")),r.appendChild(c);const i=document.createElement("button");i.type="button",i.className="btn btn-ghost btn-sm ov-remove",i.textContent="Remove",r.appendChild(i),o.appendChild(r),n.appendChild(o);const h=document.createElement("div");h.className="card-body";const p=document.createElement("div");p.className="row mb-3",p.appendChild(V("col-4","Name",ne("ov-label",e.label,"Navbar island"))),p.appendChild(V("col-8","Target selector",de(e.scope),"The region to re-theme. Everything inside it follows.")),h.appendChild(p);const v=document.createElement("div");v.className="row mb-3";const l=document.createElement("select");l.className="form-select ov-theme",A(l,{includeDefault:!0,defaultLabel:"Keep the site theme"}),x(l),T(l,e.theme||""),v.appendChild(V("col-6","Adopt a theme here",l,"Copies every token of that theme onto this region.")),h.appendChild(v);const u=document.createElement("div");u.className="ov-theme-diff",h.appendChild(u);const C=document.createElement("div");C.className="ov-tokens mb-2",Object.entries(e.tokens||{}).forEach(([ie,me])=>{C.appendChild(ee(ie,me))}),h.appendChild(te("Token overrides",C));const L=document.createElement("button");L.type="button",L.className="btn btn-ghost btn-sm ov-add-token",L.textContent="+ Add token",h.appendChild(L);const N=document.createElement("details");N.style.marginTop="1rem";const _=document.createElement("summary");_.style.cssText="cursor:pointer;font-size:.85rem;",_.textContent="Custom CSS for this override",N.appendChild(_);const k=document.createElement("textarea");k.className="form-input ov-css",k.rows=4,k.placeholder="#site-navbar .brand { letter-spacing: .04em; }",k.value=e.css||"",k.style.marginTop=".5rem",N.appendChild(k);const P=document.createElement("p");return P.className="form-hint text-muted",P.style.cssText="font-size:.8rem;margin-top:.35rem;",P.textContent="Appended verbatim after the generated rule - you own these selectors. Prefer tokens: they survive a Domma upgrade, hand-written rules fight the cascade.",N.appendChild(P),h.appendChild(N),n.appendChild(h),setTimeout(()=>j(n),0),n}function ee(e,t){const n=document.createElement("div");n.className="ov-token-row",n.style.cssText="display:flex;gap:.5rem;align-items:center;margin-bottom:.5rem;";const o=document.createElement("select");if(o.className="form-select ov-token-name",o.style.flex="1 1 auto",D.length){for(const d of D){const i=document.createElement("option");i.value=d,i.textContent=d,o.appendChild(i)}o.value=e||D[0]}else{const d=document.createElement("option");d.value=e||"",d.textContent=e||"(no tokens found)",o.appendChild(d)}const a=document.createElement("input");a.type="text",a.className="form-input ov-token-value",a.style.flex="0 0 150px",a.placeholder="#0096c7",a.value=t||"";const r=document.createElement("input");r.type="color",r.className="ov-token-swatch",r.style.cssText="flex:0 0 38px;height:34px;padding:0;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);background:none;cursor:pointer;",/^#[0-9a-f]{6}$/i.test(t||"")&&(r.value=t),r.addEventListener("input",()=>{a.value=r.value,a.dispatchEvent(new Event("input",{bubbles:!0}))});const c=document.createElement("button");return c.type="button",c.className="btn btn-ghost btn-sm ov-token-remove",c.textContent="\xD7",n.append(o,a,r,c),n}function V(e,t,n,o){const a=document.createElement("div");return a.className=e,a.appendChild(te(t,n,o)),a}function te(e,t,n){const o=document.createDocumentFragment(),a=document.createElement("label");if(a.className="form-label",a.textContent=e,o.appendChild(a),o.appendChild(t),n){const r=document.createElement("p");r.className="form-hint text-muted",r.style.cssText="margin-top:.35rem;font-size:.8rem;",r.textContent=n,o.appendChild(r)}return o}function ne(e,t,n){const o=document.createElement("input");return o.type="text",o.className=`form-input ${e}`,o.value=t||"",o.placeholder=n||"",o}function de(e){const t=document.createElement("div"),n=ne("ov-scope",e,"#site-navbar");if(n.setAttribute("list","theme-scope-options"),t.appendChild(n),!document.getElementById("theme-scope-options")){const o=document.createElement("datalist");o.id="theme-scope-options";for(const a of he){const r=document.createElement("option");r.value=a.value,r.label=a.label,o.appendChild(r)}t.appendChild(o)}return t}function F(){const e=y.querySelectorAll(".card"),t=[];return e.forEach((n,o)=>{const a={};n.querySelectorAll(".ov-token-row").forEach(r=>{const c=r.querySelector(".ov-token-name")?.value?.trim(),d=r.querySelector(".ov-token-value")?.value?.trim();c&&d&&(a[c]=d)}),t.push({id:b[o]?.id,label:n.querySelector(".ov-label")?.value?.trim()||"",enabled:!!n.querySelector(".ov-enabled")?.checked,scope:n.querySelector(".ov-scope")?.value?.trim()||"",theme:n.querySelector(".ov-theme")?.value||"",tokens:a,css:n.querySelector(".ov-css")?.value||""})}),b=t,t}s.on("click",".ov-remove",function(){const e=Number(this.closest(".card").dataset.i);F(),b.splice(e,1),R()}),s.on("change",".ov-theme",function(){j(this.closest(".card"))}),s.on("change","#theme-frontend, #theme-auto-enabled, #theme-day",()=>{y.querySelectorAll(".card").forEach(j)}),s.on("click",".ov-add-token",function(){this.closest(".card").querySelector(".ov-tokens").appendChild(ee("",""))}),s.on("click",".ov-token-remove",function(){this.closest(".ov-token-row").remove()}),s.find("#theme-add-override").on("click",()=>{F(),b.push($({label:"",scope:"",enabled:!0})),R()}),R();async function K(){const e=document.getElementById("theme-css-preview"),t={...oe(),overrides:F()};try{const n=await w.theme.compile(t);e.textContent=n.css?.trim()||"(no overrides - nothing is injected)",e.style.color=""}catch(n){e.textContent=n.message||String(n),e.style.color="var(--dm-danger)"}}s.find("#theme-refresh-css").on("click",K);function oe(){const e=s.find("#theme-auto-enabled").prop("checked"),t={enabled:e,dayTheme:S.val()||"charcoal-light",nightTheme:I.val()||"charcoal-dark",dayStart:s.find("#theme-day-start").val()||"07:00",nightStart:s.find("#theme-night-start").val()||"19:00"};return{theme:e?t.dayTheme:f.val(),adminTheme:O.val(),autoTheme:t,font:{family:s.find("#theme-font-family").val()||"Roboto",size:parseInt(s.find("#theme-font-size").val(),10)||16},switcher:{enabled:B.prop("checked"),position:s.find("#theme-switcher-position").val()||"right-center",offsetX:parseInt(s.find("#theme-switcher-offset-x").val(),10)||0,offsetY:parseInt(s.find("#theme-switcher-offset-y").val(),10)||0,themes:Array.isArray(m.switcher?.themes)?m.switcher.themes:[]}}}function se(e){const t=document.getElementById("theme-errors");for(;t.firstChild;)t.removeChild(t.firstChild);if(!e||!e.length){t.style.display="none";return}t.style.display="";for(const n of e){const o=document.createElement("div");o.textContent=n,t.appendChild(o)}t.scrollIntoView({behavior:"smooth",block:"nearest"})}s.find("#theme-save-btn").on("click",async function(){se(null);const e={...oe(),overrides:F()};this.disabled=!0;try{m=await w.theme.save(e);const{refreshAdminTheme:n}=await import("../lib/admin-theme.js");await n(w),E.toast("Theme saved.",{type:"success"}),await K()}catch(t){const n=t.validation||[t.message||"Could not save the theme."];se(n),E.toast("Theme not saved.",{type:"error"})}finally{this.disabled=!1}}),await K()}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.71.0",
3
+ "version": "0.73.0",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
@@ -82,7 +82,7 @@
82
82
  "@fastify/rate-limit": "^10.3.0",
83
83
  "@fastify/static": "9.1.1",
84
84
  "bcryptjs": "^3.0.3",
85
- "domma-js": "0.44.1",
85
+ "domma-js": "0.44.3",
86
86
  "dotenv": "^17.2.3",
87
87
  "fastify": "5.8.5",
88
88
  "gray-matter": "^4.0.3",