domma-cms 1.6.4 → 1.6.5

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 +1 @@
1
- export const FREQUENT_KEY="frequent",FREQUENT_MAX=12;export function findLeaf(t,e){for(const r of t||[])if(!(!r||r.type||r.hidden)){if(Array.isArray(r.items)&&r.items.length){const n=findLeaf(r.items,e);if(n)return n}else if(r.url===e)return r}return null}export function frequentFolder(t,e,{hidden:r=!1}={}){const n=[];for(const i of Array.isArray(e)?e:[]){const o=typeof i=="string"&&i?findLeaf(t,i):null;o&&n.push({text:o.text,url:o.url,...o.icon&&{icon:o.icon},...o.colour&&{colour:o.colour}})}return n.length?{text:"Frequent",key:FREQUENT_KEY,icon:"star",items:n,frequent:!0}:r?null:{text:"Frequent",key:FREQUENT_KEY,icon:"star",items:[],frequent:!0,empty:!0}}export function addFrequent(t,e){const r=(Array.isArray(t)?t:[]).filter(n=>typeof n=="string"&&n);return!e||r.includes(e)||r.length>=12?r:[...r,e]}export function removeFrequent(t,e){return(Array.isArray(t)?t:[]).filter(r=>r!==e)}export function moveFrequent(t,e,r){const n=Array.isArray(t)?[...t]:[],i=n.indexOf(e),o=i+r;return i<0||o<0||o>=n.length||([n[i],n[o]]=[n[o],n[i]]),n}export function storageKey(t){const e=t&&(t.id||t._id||t.email);return`sidebar.frequent.${e?String(e):"anon"}`}export function readFrequent(t){try{const e=S.get(storageKey(t));return Array.isArray(e)?e.filter(r=>typeof r=="string"&&r):[]}catch{return[]}}export function readFrequentHidden(t){try{return S.get(`${storageKey(t)}.hidden`)===!0}catch{return!1}}export function writeFrequentHidden(t,e){try{return S.set(`${storageKey(t)}.hidden`,!!e),!0}catch{return!1}}export function writeFrequent(t,e){try{return S.set(storageKey(t),Array.isArray(e)?e:[]),!0}catch{return!1}}
1
+ export const FREQUENT_KEY="frequent",FREQUENT_MAX=12;export function findLeaf(e,t){for(const r of e||[])if(!(!r||r.type||r.hidden)){if(Array.isArray(r.items)&&r.items.length){const n=findLeaf(r.items,t);if(n)return n}else if(r.url===t)return r}return null}export function frequentFolder(e,t,{hidden:r=!1}={}){const n=[];for(const o of Array.isArray(t)?t:[]){const i=typeof o=="string"&&o?findLeaf(e,o):null;i&&n.push({text:i.text,url:i.url,...i.icon&&{icon:i.icon},...i.colour&&{colour:i.colour}})}return n.length?{text:"Frequent",key:FREQUENT_KEY,icon:"star",items:n,frequent:!0}:r?null:{text:"Frequent",key:FREQUENT_KEY,icon:"star",items:[],frequent:!0,empty:!0}}export function addFrequent(e,t){const r=(Array.isArray(e)?e:[]).filter(n=>typeof n=="string"&&n);return!t||r.includes(t)||r.length>=12?r:[...r,t]}export function removeFrequent(e,t){return(Array.isArray(e)?e:[]).filter(r=>r!==t)}export function moveFrequent(e,t,r){const n=Array.isArray(e)?[...e]:[],o=n.indexOf(t),i=o+r;return o<0||i<0||i>=n.length||([n[o],n[i]]=[n[i],n[o]]),n}export function storageKey(e){const t=e&&(e.id||e._id||e.email);return`sidebar.frequent.${t?String(t):"anon"}`}export function readFrequent(e){try{const t=S.get(storageKey(e));return Array.isArray(t)?t.filter(r=>typeof r=="string"&&r):[]}catch{return[]}}export function readFrequentHidden(e){try{return S.get(`${storageKey(e)}.hidden`)===!0}catch{return!1}}export function writeFrequentHidden(e,t){try{return S.set(`${storageKey(e)}.hidden`,!!t),!0}catch{return!1}}export function writeFrequent(e,t){try{return S.set(storageKey(e),Array.isArray(t)?t:[]),!0}catch{return!1}}let u=null;export function resetFrequentSync(){u=null}export function saveFrequent(e,t){return e.put("/sidebar/frequent",{urls:readFrequent(t),hidden:readFrequentHidden(t)})}export async function syncFrequent(e,t){const r=t&&(t.id||t._id||t.email);if(!r||u===String(r))return;const n=await e.get("/sidebar/frequent");if(!(!n||typeof n!="object")){if(u=String(r),!n.saved){(readFrequent(t).length||readFrequentHidden(t))&&await saveFrequent(e,t);return}writeFrequent(t,Array.isArray(n.urls)?n.urls:[]),writeFrequentHidden(t,n.hidden===!0)}}
@@ -1,7 +1,7 @@
1
- import{api as g,getUser as P}from"../api.js";import{isPathAllowed as ce}from"./admin-scope.js";import{colourToCss as K,applyFloat as de,applyHighlight as ue,itemHighlightAttrs as me}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{placeTools as fe,ensureLockedTools as pe,managePluginsItem as he,stripItemByUrl as ge,insertFoldersBeforeSystem as be,pruneEmptyToolsFolder as ye,sidebarStateKey as V,canAny as O,filterByPermission as ve,MANAGE_PLUGINS_URL as X}from"./sidebar-grouping.js";import{arrangeButtonMarkup as xe,canArrange as $e,startArrange as we}from"./sidebar-arrange.js";import{collapseButtonMarkup as ke,wireSidebarSize as Se,COLLAPSED_WIDTH as Ce}from"./sidebar-size.js";import{FREQUENT_MAX as Z,addFrequent as qe,frequentFolder as Ae,moveFrequent as Y,readFrequent as U,readFrequentHidden as Fe,removeFrequent as Le,writeFrequent as Ee,writeFrequentHidden as J}from"./sidebar-frequent.js";import{hotkeyFor as Q,hotkeyTitle as Te,setHotkeyTree as Me}from"./hotkeys-ui.js";import{hotkeyLabel as He}from"./hotkeys.js";function h(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const Ne=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Menus",url:"#/menus",icon:"menu",permission:"menus"}];async function je(e){if(!O(e,"projects"))return[];let n=[];try{n=await g.projects.listWithCounts()}catch{return[]}if(!Array.isArray(n))return[];const o=[{key:"pages",text:"Pages",icon:"file-text",path:"/pages"},{key:"collections",text:"Collections",icon:"database",path:"/collections"},{key:"forms",text:"Forms",icon:"layout",path:"/forms"},{key:"actions",text:"Actions",icon:"zap",path:"/actions"},{key:"menus",text:"Menus",icon:"menu",path:"/menus"},{key:"blocks",text:"Blocks",icon:"box",path:"/blocks"},{key:"components",text:"Components",icon:"component",path:"/components"},{key:"views",text:"Views",icon:"eye",path:"/views"},{key:"roles",text:"Roles",icon:"shield",path:"/roles"},{key:"users",text:"Users",icon:"users",path:"/users"},{key:"apis",text:"APIs",icon:"code",path:"/apis"}];return n.map(t=>{const c="#/projects/"+encodeURIComponent(t.slug),a=t.counts||{},i=[{text:"Overview",url:c,icon:t.icon||"folder"}];for(const d of o){const r=Number(a[d.key])||0;r<=0||i.push({text:`${d.text} (${r})`,url:c+d.path,icon:d.icon})}i.push({text:"Settings",url:c+"/settings",icon:"settings"});const u=t.enabled===!1;return{text:t.name||t.slug,key:`project.${t.slug}`,icon:t.icon||"folder",items:i,...u&&{colour:"neutral",badge:{text:"off"}}}})}function Be(e,n){for(const o of e)if(!o.hidden&&(o.key==="projects"||!o.key&&(o.text||"").toLowerCase()==="projects")){o.items=Array.isArray(o.items)?o.items.slice():[],o.items.push(...n);return}}function ee(e,n){const o=ne.includes(n.tone)?n.tone:"info",t=n.label||String(n.count);n.details?N.set(e,n.details):N.delete(e);const s=!n.inventory&&F.some(c=>c.url===e&&!c.inventory)?" data-live":"";return`<span class="sidebar-badge sidebar-badge--${o}" data-count-for="${h(e)}"${s} data-badge-label="${h(t)}" aria-label="${h(t)}">${h(String(n.count))}</span>`}function te(e){for(const n of e.querySelectorAll("details")){const o=n.querySelector(":scope > summary");if(!o)continue;let t=0,s=!1;const c=[];for(const r of n.querySelectorAll(":scope > .sidebar-children .sidebar-badge[data-live]")){const m=Number(r.textContent)||0;m&&(t+=m,s||(s=r.classList.contains("sidebar-badge--danger")),c.push({text:r.closest("a")?.querySelector(".sidebar-text")?.textContent.trim()??"",meta:r.dataset.badgeLabel,tone:r.classList.contains("sidebar-badge--danger")?"danger":void 0}))}const a=`folder:${n.dataset.stateKey||o.textContent.trim()}`;let i=o.querySelector(":scope > .sidebar-badge--sum");if(!t){i?.remove(),N.delete(a);continue}i||(i=document.createElement("span"),o.querySelector(".sidebar-text")?.after(i));const u=o.querySelector(".sidebar-text")?.textContent.trim()||"This folder",d=c.map(r=>`${r.text}: ${r.meta}`).join(" \xB7 ");i.className=`sidebar-badge sidebar-badge--sum sidebar-badge--${s?"danger":"info"}`,i.dataset.countFor=a,i.dataset.badgeLabel=d,i.setAttribute("aria-label",d),i.textContent=String(t),N.set(a,{title:u,items:c})}}const N=new Map;function Pe(e,n){if(!e.url||!n)return"";const o=n[e.url];return!o||!(o.count>0)&&!o.showZero?"":ee(e.url,o)}const ne=["info","attention","danger","live"],Re={"#/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 z(e,{fallbackLabel:n="",tone:o="info"}={}){const t=Number(typeof e=="object"&&e!==null?e.count:e);if(!Number.isFinite(t)||t<0)return null;const s=typeof e?.label=="string"&&e.label||(n?`${t} - ${n}`:String(t)),c={count:Math.round(t),label:s,tone:ne.includes(e?.tone)?e.tone:o};return e?.details&&typeof e.details=="object"&&(c.details=e.details),e?.inventory===!0&&(c.inventory=!0),e?.showZero===!0&&(c.showZero=!0),c}function se(e,n,o=null,{inFrequent:t=!1}={}){if(e&&e.type==="separator")return'<hr class="sidebar-divider">';if(e&&e.type==="spacer"){const b=["xs","sm","md","lg","xl","flex"].includes(e.size)?e.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${h(b)}" aria-hidden="true"></div>`}const s=String(e.text||"?").trim().charAt(0).toUpperCase()||"?",c=e.icon?`<span data-icon="${h(e.icon)}"></span>`:`<span class="sidebar-initial" aria-hidden="true">${h(s)}</span>`,a=h(e.text||""),i=e.url?h(e.url):"",u=Array.isArray(e.items)&&e.items.length>0,d=e.colour?K(e.colour):"",r=me(e),m=[r.hover&&` data-menu-item-hover="${h(r.hover)}"`,r.active&&` data-menu-item-active="${h(r.active)}"`].filter(Boolean).join(""),y=[d&&`color:${h(d)}`,r.accent&&`--dm-menu-highlight:${h(r.accent)}`].filter(Boolean).join(";"),v=y?` style="${y}"`:"",k=e.url?Te(e.url,e.text||"",{inFrequent:t}):"",C=k?` title="${h(k)}"`:"";let q="";if(e.badge&&e.badge.text!=null&&e.badge.text!==""){const b=e.badge.variant?K(e.badge.variant):"";q=`<span class="dm-menu-badge"${b?` style="background:${h(b)};color:var(--dm-text-inverse,#fff)"`:""}>${h(String(e.badge.text))}</span>`}if(e.frequent&&e.empty){const b=V(e,o),A=S.get(b)!==!1;return`<details data-state-key="${h(b)}"${A?" open":""} data-frequent data-frequent-empty>
2
- <summary${v}>${c} <span class="sidebar-text">${a}</span></summary>
1
+ import{api as g,getUser as L}from"../api.js";import{isPathAllowed as ue}from"./admin-scope.js";import{colourToCss as V,applyFloat as fe,applyHighlight as me,itemHighlightAttrs as pe}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{placeTools as he,ensureLockedTools as ge,managePluginsItem as be,stripItemByUrl as ye,insertFoldersBeforeSystem as ve,pruneEmptyToolsFolder as xe,sidebarStateKey as X,canAny as O,filterByPermission as $e,MANAGE_PLUGINS_URL as Z}from"./sidebar-grouping.js";import{arrangeButtonMarkup as we,canArrange as ke,startArrange as Se}from"./sidebar-arrange.js";import{collapseButtonMarkup as qe,wireSidebarSize as Ce,COLLAPSED_WIDTH as Ae}from"./sidebar-size.js";import{FREQUENT_MAX as Y,addFrequent as Fe,frequentFolder as Le,moveFrequent as J,readFrequent as z,readFrequentHidden as Ee,removeFrequent as Te,saveFrequent as Me,syncFrequent as He,writeFrequent as Ne,writeFrequentHidden as Q}from"./sidebar-frequent.js";import{hotkeyFor as ee,hotkeyTitle as je,setHotkeyTree as Be}from"./hotkeys-ui.js";import{hotkeyLabel as Pe}from"./hotkeys.js";function m(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const Re=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Menus",url:"#/menus",icon:"menu",permission:"menus"}];async function _e(e){if(!O(e,"projects"))return[];let n=[];try{n=await g.projects.listWithCounts()}catch{return[]}if(!Array.isArray(n))return[];const s=[{key:"pages",text:"Pages",icon:"file-text",path:"/pages"},{key:"collections",text:"Collections",icon:"database",path:"/collections"},{key:"forms",text:"Forms",icon:"layout",path:"/forms"},{key:"actions",text:"Actions",icon:"zap",path:"/actions"},{key:"menus",text:"Menus",icon:"menu",path:"/menus"},{key:"blocks",text:"Blocks",icon:"box",path:"/blocks"},{key:"components",text:"Components",icon:"component",path:"/components"},{key:"views",text:"Views",icon:"eye",path:"/views"},{key:"roles",text:"Roles",icon:"shield",path:"/roles"},{key:"users",text:"Users",icon:"users",path:"/users"},{key:"apis",text:"APIs",icon:"code",path:"/apis"}];return n.map(t=>{const o="#/projects/"+encodeURIComponent(t.slug),a=t.counts||{},d=[{text:"Overview",url:o,icon:t.icon||"folder"}];for(const c of s){const r=Number(a[c.key])||0;r<=0||d.push({text:`${c.text} (${r})`,url:o+c.path,icon:c.icon})}d.push({text:"Settings",url:o+"/settings",icon:"settings"});const u=t.enabled===!1;return{text:t.name||t.slug,key:`project.${t.slug}`,icon:t.icon||"folder",items:d,...u&&{colour:"neutral",badge:{text:"off"}}}})}function Ie(e,n){for(const s of e)if(!s.hidden&&(s.key==="projects"||!s.key&&(s.text||"").toLowerCase()==="projects")){s.items=Array.isArray(s.items)?s.items.slice():[],s.items.push(...n);return}}function te(e,n){const s=se.includes(n.tone)?n.tone:"info",t=n.label||String(n.count);n.details?j.set(e,n.details):j.delete(e);const i=!n.inventory&&A.some(o=>o.url===e&&!o.inventory)?" data-live":"";return`<span class="sidebar-badge sidebar-badge--${s}" data-count-for="${m(e)}"${i} data-badge-label="${m(t)}" aria-label="${m(t)}">${m(String(n.count))}</span>`}function ne(e){for(const n of e.querySelectorAll("details")){const s=n.querySelector(":scope > summary");if(!s)continue;let t=0,i=!1;const o=[];for(const r of n.querySelectorAll(":scope > .sidebar-children .sidebar-badge[data-live]")){const f=Number(r.textContent)||0;f&&(t+=f,i||(i=r.classList.contains("sidebar-badge--danger")),o.push({text:r.closest("a")?.querySelector(".sidebar-text")?.textContent.trim()??"",meta:r.dataset.badgeLabel,tone:r.classList.contains("sidebar-badge--danger")?"danger":void 0}))}const a=`folder:${n.dataset.stateKey||s.textContent.trim()}`;let d=s.querySelector(":scope > .sidebar-badge--sum");if(!t){d?.remove(),j.delete(a);continue}d||(d=document.createElement("span"),s.querySelector(".sidebar-text")?.after(d));const u=s.querySelector(".sidebar-text")?.textContent.trim()||"This folder",c=o.map(r=>`${r.text}: ${r.meta}`).join(" \xB7 ");d.className=`sidebar-badge sidebar-badge--sum sidebar-badge--${i?"danger":"info"}`,d.dataset.countFor=a,d.dataset.badgeLabel=c,d.setAttribute("aria-label",c),d.textContent=String(t),j.set(a,{title:u,items:o})}}const j=new Map;function Oe(e,n){if(!e.url||!n)return"";const s=n[e.url];return!s||!(s.count>0)&&!s.showZero?"":te(e.url,s)}const se=["info","attention","danger","live"],Ue={"#/pages":["page","pages"],"#/media":["file in the media library","files in the media library"],"#/menus":["menu","menus"],"#/context-menus":["context menu","context menus"],"#/theme":["element override in the theme","element overrides in the theme"],"#/collections":["collection","collections"],"#/forms":["form","forms"],"#/views":["view","views"],"#/actions":["action","actions"],"#/blocks":["block","blocks"],"#/components":["component","components"],"#/users":["user","users"],"#/plugins":["plugin installed","plugins installed"],"#/system/notifications":["unread notification","unread notifications"]};function W(e,{fallbackLabel:n="",tone:s="info"}={}){const t=Number(typeof e=="object"&&e!==null?e.count:e);if(!Number.isFinite(t)||t<0)return null;const i=typeof e?.label=="string"&&e.label||(n?`${t} - ${n}`:String(t)),o={count:Math.round(t),label:i,tone:se.includes(e?.tone)?e.tone:s};return e?.details&&typeof e.details=="object"&&(o.details=e.details),e?.inventory===!0&&(o.inventory=!0),e?.showZero===!0&&(o.showZero=!0),o}function oe(e,n,s=null,{inFrequent:t=!1}={}){if(e&&e.type==="separator")return'<hr class="sidebar-divider">';if(e&&e.type==="spacer"){const y=["xs","sm","md","lg","xl","flex"].includes(e.size)?e.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${m(y)}" aria-hidden="true"></div>`}const i=String(e.text||"?").trim().charAt(0).toUpperCase()||"?",o=e.icon?`<span data-icon="${m(e.icon)}"></span>`:`<span class="sidebar-initial" aria-hidden="true">${m(i)}</span>`,a=m(e.text||""),d=e.url?m(e.url):"",u=Array.isArray(e.items)&&e.items.length>0,c=e.colour?V(e.colour):"",r=pe(e),f=[r.hover&&` data-menu-item-hover="${m(r.hover)}"`,r.active&&` data-menu-item-active="${m(r.active)}"`].filter(Boolean).join(""),x=[c&&`color:${m(c)}`,r.accent&&`--dm-menu-highlight:${m(r.accent)}`].filter(Boolean).join(";"),w=x?` style="${x}"`:"",k=e.url?je(e.url,e.text||"",{inFrequent:t}):"",q=k?` title="${m(k)}"`:"";let F="";if(e.badge&&e.badge.text!=null&&e.badge.text!==""){const y=e.badge.variant?V(e.badge.variant):"";F=`<span class="dm-menu-badge"${y?` style="background:${m(y)};color:var(--dm-text-inverse,#fff)"`:""}>${m(String(e.badge.text))}</span>`}if(e.frequent&&e.empty){const y=X(e,s),C=S.get(y)!==!1;return`<details data-state-key="${m(y)}"${C?" open":""} data-frequent data-frequent-empty>
2
+ <summary${w}>${o} <span class="sidebar-text">${a}</span></summary>
3
3
  <div class="sidebar-children"><p class="sidebar-frequent-empty">Right-click any Tool and choose <strong>Add to Frequent</strong> to keep it here.</p></div>
4
- </details>`}if(u){const b=V(e,o),A=S.get(b)!==!1,j=e.items.map(L=>se(L,n,b,{inFrequent:!!e.frequent})).join(""),B=e.frequent?' data-frequent title="Your Frequent Tools - right-click any Tool to add or remove it"':"";return`<details data-state-key="${h(b)}"${A?" open":""}${B}>
5
- <summary${v}${C}>${c} <span class="sidebar-text">${a}</span>${q}</summary>
6
- <div class="sidebar-children">${j}</div>
7
- </details>`}return`<a href="${i}" class="sidebar-link" data-url="${i}"${v}${m}${C}>${c} <span class="sidebar-text">${a}</span>${Pe(e,n)}${q}</a>`}async function _e(e=[],n=[],o=null){const t=f=>f.then(x=>Array.isArray(x)?x.length:Array.isArray(x?.entries)?x.entries.length:0).catch(()=>0);F=Ue(e,o);const s=new Set(F.map(f=>f.url)),c={pages:"#/pages",media:"#/media",menus:"#/menus","context-menus":"#/context-menus",theme:"#/theme",collections:"#/collections",forms:"#/forms",views:"#/views",actions:"#/actions",blocks:"#/blocks",components:"#/components",users:"#/users",plugins:"#/plugins",notifications:"#/system/notifications"},a=(f,x)=>O(n,f)&&!s.has(c[f])?x():Promise.resolve(0),[i,u,d,r,m,y,v,k,C,q,b,A,j,B]=await Promise.all([a("pages",()=>t(g.pages.list())),a("media",()=>t(g.media.list())),a("menus",()=>t(g.menus.list())),a("context-menus",()=>t(g.contextMenus.list())),a("theme",()=>g.theme.get().then(f=>(f?.overrides||[]).length).catch(()=>0)),a("collections",()=>t(g.collections.list())),a("forms",()=>t(g.forms.list())),a("views",()=>t(g.views.list())),a("actions",()=>t(g.actions.list())),a("blocks",()=>t(g.blocks.list())),a("components",()=>t(g.components.list())),a("users",()=>t(g.users.list())),a("plugins",()=>t(g.plugins.list())),a("notifications",()=>g.system?.notifications?.unreadCount?.().then(f=>f?.count??0).catch(()=>0)??Promise.resolve(0))]),L={};await Promise.all(F.map(async f=>{const x=await oe(f);x&&(L[f.url]=x)}));const _={"#/pages":i,"#/media":u,"#/menus":d,"#/context-menus":r,"#/theme":m,"#/collections":y,"#/forms":v,"#/views":k,"#/actions":C,"#/blocks":q,"#/components":b,"#/users":A,"#/plugins":j,"#/system/notifications":B},I={};for(const[f,x]of Object.entries(_)){if(s.has(f))continue;const[l,w]=Re[f]??["item","items"],p=z(x);p&&(I[f]={...p,label:`${p.count} ${p.count===1?l:w}`})}return{...I,...L}}let F=[];const Ie="dm:sidebar-count",Oe=6e4;function Ue(e=[],n=null){const o=[],t=s=>{if(s&&!(n&&typeof s.url=="string"&&s.url.startsWith("#/")&&!ce(n,s.url.slice(1)))){s.url&&typeof s.countUrl=="string"&&s.countUrl&&o.push({url:s.url,countUrl:s.countUrl,text:s.text||"",countLabel:s.countLabel||"",inventory:s.inventory===!0});for(const c of[...s.children||[],...s.items||[]])t(c)}};for(const s of e??[])t(s?.item??s);return o}async function oe(e){try{const n=new Date,o=`${n.getFullYear()}-${String(n.getMonth()+1).padStart(2,"0")}-${String(n.getDate()).padStart(2,"0")}`,t=e.countUrl+(e.countUrl.includes("?")?"&":"?")+`today=${o}`;return z(await H.get(t),{fallbackLabel:e.countLabel||e.text})}catch{return null}}function ie(e,n,o){const t=[...e.querySelectorAll("a.sidebar-link")].filter(s=>s.dataset.url===n);if(t.length){for(const s of t){const c=s.querySelector(".sidebar-badge[data-count-for]");if(!o||!(o.count>0)&&!o.showZero)c?.remove();else{const a=document.createRange();a.selectNodeContents(s);const i=a.createContextualFragment(ee(n,o)).firstElementChild;c?c.replaceWith(i):s.querySelector(".sidebar-text")?.after(i)}}W.set(n,o),te(e)}}const W=new Map;async function D(e,n=null){const o=n?F.filter(t=>t.url===n):F;await Promise.all(o.map(async t=>{const s=await oe(t),c=W.get(t.url);JSON.stringify(c??null)!==JSON.stringify(s)&&ie(e,t.url,s)}))}function ze(e){e.dataset.liveCounts||(e.dataset.liveCounts="1",window.addEventListener(Ie,n=>{const o=n.detail||{};if(o.url)if(o.count!==void 0){const t=F.find(s=>s.url===o.url);ie(e,o.url,z(o,{fallbackLabel:t?.countLabel||t?.text||""}))}else D(e,o.url)}),setInterval(()=>{!document.hidden&&document.body.contains(e)&&D(e)},Oe),document.addEventListener("visibilitychange",()=>{document.hidden||D(e)}),We(e))}function ae(e,n,o){const t=(a,i,u)=>{const d=document.createElement(a);return i&&(d.className=i),u!=null&&(d.textContent=String(u)),d};if(!o){e.textContent=n;return}e.appendChild(t("strong","dm-badge-pop-title",o.title||n)),(o.rows?.length||!o.items?.length)&&e.appendChild(t("p","dm-badge-pop-sub",n));const s=Array.isArray(o.rows)?o.rows.slice(0,6):[];if(s.length){const a=t("dl","dm-badge-pop-rows");for(const i of s)a.appendChild(t("dt",null,i.label)),a.appendChild(t("dd",i.tone==="danger"?"is-danger":null,i.value));e.appendChild(a)}const c=Array.isArray(o.items)?o.items.slice(0,6):[];if(c.length){const a=t("ul","dm-badge-pop-items");for(const i of c){const u=t("li",i.tone==="danger"?"is-danger":null);u.appendChild(t("span","dm-badge-pop-text",i.text)),i.meta&&u.appendChild(t("span","dm-badge-pop-meta",i.meta)),a.appendChild(u)}e.appendChild(a)}else o.empty&&e.appendChild(t("p","dm-badge-pop-empty",o.empty))}function We(e){let n=null,o=null;const t=()=>e.classList.contains("is-collapsed"),s=()=>{clearTimeout(o),n?.remove(),n=null},c=(d,r)=>{s(),o=setTimeout(()=>{n=document.createElement("div"),n.className="dm-help-pop dm-badge-pop",n.setAttribute("role","tooltip"),r(n),document.body.appendChild(n);const m=d.getBoundingClientRect(),y=n.getBoundingClientRect(),v=Math.min(m.right+10,innerWidth-y.width-8);n.style.left=`${Math.max(8,v)}px`,n.style.top=`${Math.max(8,Math.min(m.top+m.height/2-y.height/2,innerHeight-y.height-8))}px`},150)},a=d=>{const r=d?.dataset.badgeLabel;if(!r)return s();c(d,m=>ae(m,r,N.get(d.dataset.countFor)))},i=d=>{const r=d.querySelector(":scope > .sidebar-text")?.textContent.trim()||"",m=d.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");c(d,y=>{const v=m&&N.get(m.dataset.countFor);if(v)return ae(y,m.dataset.badgeLabel,{...v,title:v.title||r});if(!m){y.textContent=r;return}const k=document.createElement("strong");k.className="dm-badge-pop-title",k.textContent=r;const C=document.createElement("p");C.className="dm-badge-pop-sub",C.textContent=m.dataset.badgeLabel,y.append(k,C)})},u=d=>{const r=d.closest?.("a.sidebar-link, summary");return r&&e.contains(r)?r:null};e.addEventListener("mouseover",d=>{if(t()){const m=u(d.target);m&&!m.contains(d.relatedTarget)&&i(m);return}const r=d.target.closest?.(".sidebar-badge[data-badge-label]");r&&a(r)}),e.addEventListener("mouseout",d=>{if(t()){const r=u(d.target);r&&!r.contains(d.relatedTarget)&&s();return}d.target.closest?.(".sidebar-badge[data-badge-label]")&&s()}),e.addEventListener("focusin",d=>{const r=d.target.closest?.("a, summary");if(t()&&r&&e.contains(r)&&r.querySelector(":scope > .sidebar-text"))return i(r);const m=r?.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");m?a(m):s()}),e.addEventListener("focusout",s),e.addEventListener("click",s),window.addEventListener("scroll",s,!0),window.addEventListener("hashchange",s)}async function De(e=[]){if(!O(e,"settings"))return"Admin";try{const n=await g.settings.get();return n?.adminBrand?.title||n?.title||"Admin"}catch{return"Admin"}}export async function renderAdminSidebar({mount:e,permissions:n,adminScope:o=null}){const t=$(e).get(0);if(!t)return;let s,c=null,a=null,i=null,u=null,d=null;try{const l=await g.get("/sidebar/menu");s=Array.isArray(l?.items)?l.items:null,c=l?.variant||null,a=l?.position||null,i=l?.style||null,u=l?.float||null,d=l?.highlight||null}catch{s=null}(!s||!s.some(l=>l&&(!l.hidden||l.restricted)))&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),s=Ne.slice());let r=[];try{r=await H.get("/api/sidebar/registered-items")||[]}catch{}s=ge(s,X),s=fe(s,r.filter(l=>(l?.item??l)?.url!==X)),s=pe(s),s=be(s,[he()]);const m=await je(n);m.length&&Be(s,m),s=ve(s,n,o),s=ye(s);const y=Ae(s,U(P()),{hidden:Fe(P())});y&&(s=[y,...s]),Me(s);const[v,k]=await Promise.all([_e(r.filter(l=>O(n,l?.item?.permission)),n,o).catch(()=>({})),De(n)]),C=c?` dm-admin-sidebar--${h(c)}`:"";function q(l,w="px"){if(l==null)return l;const p=String(l).trim();return/^\d+(\.\d+)?$/.test(p)?`${p}${w}`:p}let b="";if(i){const l=[];i.fontFamily&&l.push(`font-family: ${i.fontFamily}, sans-serif`),i.fontSize&&l.push(`font-size: ${q(i.fontSize)}`),i.fontWeight&&l.push(`font-weight: ${i.fontWeight}`),i.letterSpacing&&l.push(`letter-spacing: ${q(i.letterSpacing,"em")}`);const w=[];if(l.length&&w.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("; ")} }`),i.iconSize){const p=q(i.iconSize);w.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${p} !important; height: ${p} !important; }`)}w.length&&(b=`<style data-admin-sidebar-style>${w.join(" ")}</style>`)}const A=$e(n)?xe():"",j=`<div class="dm-admin-sidebar-header"><span data-icon="layout"></span><span class="dm-admin-sidebar-title">${h(k)}</span>${A}${ke()}</div>`,B=s.some(l=>l&&l.type==="spacer"&&l.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",L=`${b}<nav class="dm-admin-sidebar${C}${B}">${j}${s.map(l=>se(l,v)).join("")}</nav>`,_=document.createRange();_.selectNodeContents(t);const I=_.createContextualFragment(L);t.replaceChildren(I),Domma.icons.scan(t),t.querySelector(".dm-admin-sidebar-arrange")?.addEventListener("click",()=>we(t));for(const l of F)W.set(l.url,v[l.url]??null);te(t),ze(t),t.removeAttribute("data-menu-hover"),t.removeAttribute("data-menu-active"),t.style.removeProperty("--dm-menu-highlight"),ue(t,{highlight:d});const f=document.querySelector(".dashboard-wrapper");["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(l=>{t.style[l]=""}),t.classList.remove("dm-menu-floating"),f&&(f.style.paddingLeft="",f.style.paddingRight=""),t.__dmGutter=()=>{if(a!=="floating"||!f)return;const l=u&&u.anchor||"TL",w=Number.isFinite(Number(u?.offsetX))?Number(u.offsetX):16,p=t.classList.contains("is-collapsed")?Ce:parseInt(t.style.width,10)||t.getBoundingClientRect().width||260,T=Math.round(p+w*2)+"px";l==="R"||l==="TR"||l==="BR"?f.style.paddingRight=T:f.style.paddingLeft=T},a==="floating"&&de(t,{position:"floating",float:u||{}}),t.querySelectorAll("details").forEach(l=>{l.addEventListener("toggle",function(){const w=this.getAttribute("data-state-key");w&&S.set(w,this.open);const p=this.querySelector(":scope > .sidebar-children");if(p)if(this.open){const T=p.scrollHeight;p.style.maxHeight="0",requestAnimationFrame(()=>{p.style.maxHeight=T+"px",p.addEventListener("transitionend",function le(){p.style.maxHeight="none",p.removeEventListener("transitionend",le)})})}else{const T=p.scrollHeight;p.style.maxHeight=T+"px",requestAnimationFrame(()=>{p.style.maxHeight="0"})}})}),Se(t),Xe(t),t.dataset.routeWired||(t.dataset.routeWired="1",M.subscribe("router:afterChange",()=>t.__dmSetActive?.()));function x(){const l=/^#\/(dashboards|my-dashboard)\b/.test(location.hash)?"#/":location.hash||"#/";$(t).find(".sidebar-link").removeClass("active"),$(t).find(`.sidebar-link[data-url="${l}"]`).addClass("active")}x(),t.__dmSetActive=x}function re(){try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}}function G(e){return`${location.origin}${location.pathname}${e}`}function R(e,n){const o=P(),t=e(U(o));if(!Ee(o,t)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}t.length&&J(o,!1),n&&E.toast(n,{type:"success",duration:2500}),re()}function Ge(e){if(!J(P(),e)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}E.toast(e?"Frequent hidden. Add a Tool to it from its right-click menu to bring it back.":"Frequent shown.",{type:"success",duration:3500}),re()}function Ke(e){const n=e.dataset.url;if(!n)return[];const o=e.querySelector(".sidebar-text")?.textContent.trim()||n,t=U(P()),s=t.includes(n),c=!!e.closest("details[data-frequent]"),a=t.indexOf(n),i=!s&&t.length>=Z;return[{header:o},{label:"Open",icon:"arrow-right",...Q(n)&&{shortcut:He(Q(n))},action:()=>{location.hash=n}},{label:"Open in a new tab",icon:"external-link",action:()=>window.open(G(n),"_blank","noopener")},{type:"divider"},s?{label:"Remove from Frequent",icon:"star",action:()=>R(u=>Le(u,n),`${o} removed from Frequent.`)}:{label:i?`Frequent is full (${Z})`:"Add to Frequent",icon:"star",disabled:i,action:()=>R(u=>qe(u,n),`${o} added to Frequent.`)},...c?[{label:"Move up",icon:"arrow-up",disabled:a<=0,action:()=>R(u=>Y(u,n,-1))},{label:"Move down",icon:"arrow-down",disabled:a<0||a>=t.length-1,action:()=>R(u=>Y(u,n,1))}]:[],{type:"divider"},{label:"Copy link",icon:"link",action:async()=>{try{await navigator.clipboard.writeText(G(n)),E.toast("Link copied.",{type:"success",duration:2e3})}catch{E.toast(G(n),{type:"info",duration:8e3})}}}]}function Ve(e,n){const o=e.closest("details"),t=e.querySelector(".sidebar-text")?.textContent.trim()||"Folder",s=c=>n.querySelectorAll("details").forEach(a=>{a.open!==c&&(a.open=c)});return[{header:t},{label:o?.open?"Close":"Open",icon:o?.open?"chevron-up":"chevron-down",action:()=>{o&&(o.open=!o.open)}},{label:"Open every folder",icon:"chevrons-down",action:()=>s(!0)},{label:"Close every folder",icon:"chevrons-up",action:()=>s(!1)},...o?.hasAttribute("data-frequent-empty")?[{type:"divider"},{label:"Hide Frequent until I add a Tool",icon:"eye-off",action:()=>Ge(!0)}]:o?.hasAttribute("data-frequent")?[{type:"divider"},{label:"Clear Frequent",icon:"x",action:()=>R(()=>[],"Frequent cleared.")}]:[]]}function Xe(e){e.dataset.ctxWired||typeof E>"u"||!E.contextMenu||(e.dataset.ctxWired="1",E.contextMenu(e,{match:"a.sidebar-link, details > summary",inherit:!1,density:"compact",items:n=>n.matches("summary")?Ve(n,e):Ke(n)}))}
4
+ </details>`}if(u){const y=X(e,s),C=S.get(y)!==!1,B=e.items.map(T=>oe(T,n,y,{inFrequent:!!e.frequent})).join(""),P=e.frequent?' data-frequent title="Your Frequent Tools - right-click any Tool to add or remove it"':"";return`<details data-state-key="${m(y)}"${C?" open":""}${P}>
5
+ <summary${w}${q}>${o} <span class="sidebar-text">${a}</span>${F}</summary>
6
+ <div class="sidebar-children">${B}</div>
7
+ </details>`}return`<a href="${d}" class="sidebar-link" data-url="${d}"${w}${f}${q}>${o} <span class="sidebar-text">${a}</span>${Oe(e,n)}${F}</a>`}async function ze(e=[],n=[],s=null){const t=p=>p.then(b=>Array.isArray(b)?b.length:Array.isArray(b?.entries)?b.entries.length:0).catch(()=>0);A=Ge(e,s);const i=new Set(A.map(p=>p.url)),o={pages:"#/pages",media:"#/media",menus:"#/menus","context-menus":"#/context-menus",theme:"#/theme",collections:"#/collections",forms:"#/forms",views:"#/views",actions:"#/actions",blocks:"#/blocks",components:"#/components",users:"#/users",plugins:"#/plugins",notifications:"#/system/notifications"},a=(p,b)=>O(n,p)&&!i.has(o[p])?b():Promise.resolve(0),[d,u,c,r,f,x,w,k,q,F,y,C,B,P]=await Promise.all([a("pages",()=>t(g.pages.list())),a("media",()=>t(g.media.list())),a("menus",()=>t(g.menus.list())),a("context-menus",()=>t(g.contextMenus.list())),a("theme",()=>g.theme.get().then(p=>(p?.overrides||[]).length).catch(()=>0)),a("collections",()=>t(g.collections.list())),a("forms",()=>t(g.forms.list())),a("views",()=>t(g.views.list())),a("actions",()=>t(g.actions.list())),a("blocks",()=>t(g.blocks.list())),a("components",()=>t(g.components.list())),a("users",()=>t(g.users.list())),a("plugins",()=>t(g.plugins.list())),a("notifications",()=>g.system?.notifications?.unreadCount?.().then(p=>p?.count??0).catch(()=>0)??Promise.resolve(0))]),T={};await Promise.all(A.map(async p=>{const b=await ie(p);b&&(T[p.url]=b)}));const U={"#/pages":d,"#/media":u,"#/menus":c,"#/context-menus":r,"#/theme":f,"#/collections":x,"#/forms":w,"#/views":k,"#/actions":q,"#/blocks":F,"#/components":y,"#/users":C,"#/plugins":B,"#/system/notifications":P},R={};for(const[p,b]of Object.entries(U)){if(i.has(p))continue;const[I,l]=Ue[p]??["item","items"],v=W(b);v&&(R[p]={...v,label:`${v.count} ${v.count===1?I:l}`})}return{...R,...T}}let A=[];const We="dm:sidebar-count",De=6e4;function Ge(e=[],n=null){const s=[],t=i=>{if(i&&!(n&&typeof i.url=="string"&&i.url.startsWith("#/")&&!ue(n,i.url.slice(1)))){i.url&&typeof i.countUrl=="string"&&i.countUrl&&s.push({url:i.url,countUrl:i.countUrl,text:i.text||"",countLabel:i.countLabel||"",inventory:i.inventory===!0});for(const o of[...i.children||[],...i.items||[]])t(o)}};for(const i of e??[])t(i?.item??i);return s}async function ie(e){try{const n=new Date,s=`${n.getFullYear()}-${String(n.getMonth()+1).padStart(2,"0")}-${String(n.getDate()).padStart(2,"0")}`,t=e.countUrl+(e.countUrl.includes("?")?"&":"?")+`today=${s}`;return W(await H.get(t),{fallbackLabel:e.countLabel||e.text})}catch{return null}}function ae(e,n,s){const t=[...e.querySelectorAll("a.sidebar-link")].filter(i=>i.dataset.url===n);if(t.length){for(const i of t){const o=i.querySelector(".sidebar-badge[data-count-for]");if(!s||!(s.count>0)&&!s.showZero)o?.remove();else{const a=document.createRange();a.selectNodeContents(i);const d=a.createContextualFragment(te(n,s)).firstElementChild;o?o.replaceWith(d):i.querySelector(".sidebar-text")?.after(d)}}D.set(n,s),ne(e)}}const D=new Map;async function G(e,n=null){const s=n?A.filter(t=>t.url===n):A;await Promise.all(s.map(async t=>{const i=await ie(t),o=D.get(t.url);JSON.stringify(o??null)!==JSON.stringify(i)&&ae(e,t.url,i)}))}function Ke(e){e.dataset.liveCounts||(e.dataset.liveCounts="1",window.addEventListener(We,n=>{const s=n.detail||{};if(s.url)if(s.count!==void 0){const t=A.find(i=>i.url===s.url);ae(e,s.url,W(s,{fallbackLabel:t?.countLabel||t?.text||""}))}else G(e,s.url)}),setInterval(()=>{!document.hidden&&document.body.contains(e)&&G(e)},De),document.addEventListener("visibilitychange",()=>{document.hidden||G(e)}),Ve(e))}function re(e,n,s){const t=(a,d,u)=>{const c=document.createElement(a);return d&&(c.className=d),u!=null&&(c.textContent=String(u)),c};if(!s){e.textContent=n;return}e.appendChild(t("strong","dm-badge-pop-title",s.title||n)),(s.rows?.length||!s.items?.length)&&e.appendChild(t("p","dm-badge-pop-sub",n));const i=Array.isArray(s.rows)?s.rows.slice(0,6):[];if(i.length){const a=t("dl","dm-badge-pop-rows");for(const d of i)a.appendChild(t("dt",null,d.label)),a.appendChild(t("dd",d.tone==="danger"?"is-danger":null,d.value));e.appendChild(a)}const o=Array.isArray(s.items)?s.items.slice(0,6):[];if(o.length){const a=t("ul","dm-badge-pop-items");for(const d of o){const u=t("li",d.tone==="danger"?"is-danger":null);u.appendChild(t("span","dm-badge-pop-text",d.text)),d.meta&&u.appendChild(t("span","dm-badge-pop-meta",d.meta)),a.appendChild(u)}e.appendChild(a)}else s.empty&&e.appendChild(t("p","dm-badge-pop-empty",s.empty))}function Ve(e){let n=null,s=null;const t=()=>e.classList.contains("is-collapsed"),i=()=>{clearTimeout(s),n?.remove(),n=null},o=(c,r)=>{i(),s=setTimeout(()=>{n=document.createElement("div"),n.className="dm-help-pop dm-badge-pop",n.setAttribute("role","tooltip"),r(n),document.body.appendChild(n);const f=c.getBoundingClientRect(),x=n.getBoundingClientRect(),w=Math.min(f.right+10,innerWidth-x.width-8);n.style.left=`${Math.max(8,w)}px`,n.style.top=`${Math.max(8,Math.min(f.top+f.height/2-x.height/2,innerHeight-x.height-8))}px`},150)},a=c=>{const r=c?.dataset.badgeLabel;if(!r)return i();o(c,f=>re(f,r,j.get(c.dataset.countFor)))},d=c=>{const r=c.querySelector(":scope > .sidebar-text")?.textContent.trim()||"",f=c.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");o(c,x=>{const w=f&&j.get(f.dataset.countFor);if(w)return re(x,f.dataset.badgeLabel,{...w,title:w.title||r});if(!f){x.textContent=r;return}const k=document.createElement("strong");k.className="dm-badge-pop-title",k.textContent=r;const q=document.createElement("p");q.className="dm-badge-pop-sub",q.textContent=f.dataset.badgeLabel,x.append(k,q)})},u=c=>{const r=c.closest?.("a.sidebar-link, summary");return r&&e.contains(r)?r:null};e.addEventListener("mouseover",c=>{if(t()){const f=u(c.target);f&&!f.contains(c.relatedTarget)&&d(f);return}const r=c.target.closest?.(".sidebar-badge[data-badge-label]");r&&a(r)}),e.addEventListener("mouseout",c=>{if(t()){const r=u(c.target);r&&!r.contains(c.relatedTarget)&&i();return}c.target.closest?.(".sidebar-badge[data-badge-label]")&&i()}),e.addEventListener("focusin",c=>{const r=c.target.closest?.("a, summary");if(t()&&r&&e.contains(r)&&r.querySelector(":scope > .sidebar-text"))return d(r);const f=r?.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");f?a(f):i()}),e.addEventListener("focusout",i),e.addEventListener("click",i),window.addEventListener("scroll",i,!0),window.addEventListener("hashchange",i)}async function Xe(e=[]){if(!O(e,"settings"))return"Admin";try{const n=await g.settings.get();return n?.adminBrand?.title||n?.title||"Admin"}catch{return"Admin"}}export async function renderAdminSidebar({mount:e,permissions:n,adminScope:s=null}){const t=$(e).get(0);if(!t)return;const i=He(g,L()).catch(()=>{});let o,a=null,d=null,u=null,c=null,r=null;try{const l=await g.get("/sidebar/menu");o=Array.isArray(l?.items)?l.items:null,a=l?.variant||null,d=l?.position||null,u=l?.style||null,c=l?.float||null,r=l?.highlight||null}catch{o=null}(!o||!o.some(l=>l&&(!l.hidden||l.restricted)))&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),o=Re.slice());let f=[];try{f=await H.get("/api/sidebar/registered-items")||[]}catch{}o=ye(o,Z),o=he(o,f.filter(l=>(l?.item??l)?.url!==Z)),o=ge(o),o=ve(o,[be()]);const x=await _e(n);x.length&&Ie(o,x),o=$e(o,n,s),o=xe(o),await i;const w=Le(o,z(L()),{hidden:Ee(L())});w&&(o=[w,...o]),Be(o);const[k,q]=await Promise.all([ze(f.filter(l=>O(n,l?.item?.permission)),n,s).catch(()=>({})),Xe(n)]),F=a?` dm-admin-sidebar--${m(a)}`:"";function y(l,v="px"){if(l==null)return l;const h=String(l).trim();return/^\d+(\.\d+)?$/.test(h)?`${h}${v}`:h}let C="";if(u){const l=[];u.fontFamily&&l.push(`font-family: ${u.fontFamily}, sans-serif`),u.fontSize&&l.push(`font-size: ${y(u.fontSize)}`),u.fontWeight&&l.push(`font-weight: ${u.fontWeight}`),u.letterSpacing&&l.push(`letter-spacing: ${y(u.letterSpacing,"em")}`);const v=[];if(l.length&&v.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("; ")} }`),u.iconSize){const h=y(u.iconSize);v.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${h} !important; height: ${h} !important; }`)}v.length&&(C=`<style data-admin-sidebar-style>${v.join(" ")}</style>`)}const B=ke(n)?we():"",P=`<div class="dm-admin-sidebar-header"><span data-icon="layout"></span><span class="dm-admin-sidebar-title">${m(q)}</span>${B}${qe()}</div>`,T=o.some(l=>l&&l.type==="spacer"&&l.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",U=`${C}<nav class="dm-admin-sidebar${F}${T}">${P}${o.map(l=>oe(l,k)).join("")}</nav>`,R=document.createRange();R.selectNodeContents(t);const p=R.createContextualFragment(U);t.replaceChildren(p),Domma.icons.scan(t),t.querySelector(".dm-admin-sidebar-arrange")?.addEventListener("click",()=>Se(t));for(const l of A)D.set(l.url,k[l.url]??null);ne(t),Ke(t),t.removeAttribute("data-menu-hover"),t.removeAttribute("data-menu-active"),t.style.removeProperty("--dm-menu-highlight"),me(t,{highlight:r});const b=document.querySelector(".dashboard-wrapper");["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(l=>{t.style[l]=""}),t.classList.remove("dm-menu-floating"),b&&(b.style.paddingLeft="",b.style.paddingRight=""),t.__dmGutter=()=>{if(d!=="floating"||!b)return;const l=c&&c.anchor||"TL",v=Number.isFinite(Number(c?.offsetX))?Number(c.offsetX):16,h=t.classList.contains("is-collapsed")?Ae:parseInt(t.style.width,10)||t.getBoundingClientRect().width||260,N=Math.round(h+v*2)+"px";l==="R"||l==="TR"||l==="BR"?b.style.paddingRight=N:b.style.paddingLeft=N},d==="floating"&&fe(t,{position:"floating",float:c||{}}),t.querySelectorAll("details").forEach(l=>{l.addEventListener("toggle",function(){const v=this.getAttribute("data-state-key");v&&S.set(v,this.open);const h=this.querySelector(":scope > .sidebar-children");if(h)if(this.open){const N=h.scrollHeight;h.style.maxHeight="0",requestAnimationFrame(()=>{h.style.maxHeight=N+"px",h.addEventListener("transitionend",function de(){h.style.maxHeight="none",h.removeEventListener("transitionend",de)})})}else{const N=h.scrollHeight;h.style.maxHeight=N+"px",requestAnimationFrame(()=>{h.style.maxHeight="0"})}})}),Ce(t),Qe(t),t.dataset.routeWired||(t.dataset.routeWired="1",M.subscribe("router:afterChange",()=>t.__dmSetActive?.()));function I(){const l=/^#\/(dashboards|my-dashboard)\b/.test(location.hash)?"#/":location.hash||"#/";$(t).find(".sidebar-link").removeClass("active"),$(t).find(`.sidebar-link[data-url="${l}"]`).addClass("active")}I(),t.__dmSetActive=I}function le(){try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}}function K(e){return`${location.origin}${location.pathname}${e}`}function _(e,n){const s=L(),t=e(z(s));if(!Ne(s,t)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}t.length&&Q(s,!1),ce(s),n&&E.toast(n,{type:"success",duration:2500}),le()}function ce(e){Me(g,e).catch(()=>E.toast("Frequent is saved in this browser only - the server could not keep it.",{type:"warning"}))}function Ze(e){if(!Q(L(),e)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}ce(L()),E.toast(e?"Frequent hidden. Add a Tool to it from its right-click menu to bring it back.":"Frequent shown.",{type:"success",duration:3500}),le()}function Ye(e){const n=e.dataset.url;if(!n)return[];const s=e.querySelector(".sidebar-text")?.textContent.trim()||n,t=z(L()),i=t.includes(n),o=!!e.closest("details[data-frequent]"),a=t.indexOf(n),d=!i&&t.length>=Y;return[{header:s},{label:"Open",icon:"arrow-right",...ee(n)&&{shortcut:Pe(ee(n))},action:()=>{location.hash=n}},{label:"Open in a new tab",icon:"external-link",action:()=>window.open(K(n),"_blank","noopener")},{type:"divider"},i?{label:"Remove from Frequent",icon:"star",action:()=>_(u=>Te(u,n),`${s} removed from Frequent.`)}:{label:d?`Frequent is full (${Y})`:"Add to Frequent",icon:"star",disabled:d,action:()=>_(u=>Fe(u,n),`${s} added to Frequent.`)},...o?[{label:"Move up",icon:"arrow-up",disabled:a<=0,action:()=>_(u=>J(u,n,-1))},{label:"Move down",icon:"arrow-down",disabled:a<0||a>=t.length-1,action:()=>_(u=>J(u,n,1))}]:[],{type:"divider"},{label:"Copy link",icon:"link",action:async()=>{try{await navigator.clipboard.writeText(K(n)),E.toast("Link copied.",{type:"success",duration:2e3})}catch{E.toast(K(n),{type:"info",duration:8e3})}}}]}function Je(e,n){const s=e.closest("details"),t=e.querySelector(".sidebar-text")?.textContent.trim()||"Folder",i=o=>n.querySelectorAll("details").forEach(a=>{a.open!==o&&(a.open=o)});return[{header:t},{label:s?.open?"Close":"Open",icon:s?.open?"chevron-up":"chevron-down",action:()=>{s&&(s.open=!s.open)}},{label:"Open every folder",icon:"chevrons-down",action:()=>i(!0)},{label:"Close every folder",icon:"chevrons-up",action:()=>i(!1)},...s?.hasAttribute("data-frequent-empty")?[{type:"divider"},{label:"Hide Frequent until I add a Tool",icon:"eye-off",action:()=>Ze(!0)}]:s?.hasAttribute("data-frequent")?[{type:"divider"},{label:"Clear Frequent",icon:"x",action:()=>_(()=>[],"Frequent cleared.")}]:[]]}function Qe(e){e.dataset.ctxWired||typeof E>"u"||!E.contextMenu||(e.dataset.ctxWired="1",E.contextMenu(e,{match:"a.sidebar-link, details > summary",inherit:!1,density:"compact",items:n=>n.matches("summary")?Je(n,e):Ye(n)}))}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "1.6.4",
3
+ "version": "1.6.5",
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",
@@ -3,6 +3,8 @@
3
3
  *
4
4
  * GET /api/sidebar/menu - the admin-sidebar menu, filtered for the caller
5
5
  * GET /api/sidebar/registered-items - plugin-registered sidebar items, filtered for the caller
6
+ * GET /api/sidebar/frequent - the caller's own Frequent list
7
+ * PUT /api/sidebar/frequent - keep it ({urls, hidden})
6
8
  *
7
9
  * Both need only a signed-in user, NOT `menus.read`: every user who can reach
8
10
  * the admin needs a sidebar, and a role without menu-editing rights (a
@@ -19,6 +21,7 @@ import {getRegisteredSidebarItems} from '../../services/hooks.js';
19
21
  import {filterItemsForUser, getMenu} from '../../services/menus.js';
20
22
  import {isDashboardPro} from '../../services/widgets.js';
21
23
  import {boardName, readPrefs, readSite} from '../../services/dashboardSettings.js';
24
+ import {frequentProblems, readFrequent, writeFrequent} from '../../services/sidebarFrequent.js';
22
25
 
23
26
  export const ADMIN_SIDEBAR_SLUG = 'admin-sidebar';
24
27
 
@@ -61,4 +64,17 @@ export async function sidebarRoutes(fastify, opts = {}) {
61
64
  ? {...entry, item: {...entry.item, items: filterItemsForUser(entry.item.items, request.user)}}
62
65
  : entry);
63
66
  });
67
+
68
+ // Each person's own, so it follows them from browser to browser (sidebarFrequent.js).
69
+ fastify.get('/sidebar/frequent', signedIn, async (request) => readFrequent(request.user));
70
+
71
+ fastify.put('/sidebar/frequent', signedIn, async (request, reply) => {
72
+ const problems = frequentProblems(request.body);
73
+ if (problems.length) return reply.code(400).send({error: problems.join(' ')});
74
+ try {
75
+ return await writeFrequent(request.user, request.body);
76
+ } catch (err) {
77
+ return reply.code(400).send({error: err.message});
78
+ }
79
+ });
64
80
  }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The sidebar's Frequent folder, kept per user on the server so it follows the
3
+ * person to every browser and device (admin/js/lib/sidebar-frequent.js keeps
4
+ * a copy in the browser to draw from). content/sidebar/frequent/<user id>.json
5
+ * - per user, so ignored in a site repo like Dashboard Pro's prefs.
6
+ *
7
+ * Only the list is kept here, not what it may open: the sidebar draws Frequent
8
+ * from the user's already-filtered tree, so a url they cannot open never shows.
9
+ *
10
+ * Tested in tests/services/sidebar-frequent.test.js.
11
+ */
12
+ import fs from 'fs/promises';
13
+ import path from 'path';
14
+
15
+ /** As FREQUENT_MAX in admin/js/lib/sidebar-frequent.js. */
16
+ export const FREQUENT_MAX = 12;
17
+ const URL_MAX = 500;
18
+ const USER_ID_RE = /^[A-Za-z0-9_-]{1,80}$/;
19
+
20
+ /** A list, cleaned: admin hash urls only, once each, at most FREQUENT_MAX. */
21
+ export function normaliseFrequent(input) {
22
+ const p = input && typeof input === 'object' ? input : {};
23
+ const urls = [];
24
+ for (const u of Array.isArray(p.urls) ? p.urls : []) {
25
+ if (typeof u !== 'string' || !u.startsWith('#/') || u.length > URL_MAX || urls.includes(u)) continue;
26
+ urls.push(u);
27
+ if (urls.length >= FREQUENT_MAX) break;
28
+ }
29
+ return {urls, hidden: p.hidden === true};
30
+ }
31
+
32
+ /** What is wrong with a body sent to PUT, or [] when nothing is. */
33
+ export function frequentProblems(input) {
34
+ if (!input || typeof input !== 'object' || Array.isArray(input)) return ['Send {urls, hidden}.'];
35
+ const problems = [];
36
+ if (input.urls !== undefined && !Array.isArray(input.urls)) problems.push('urls must be a list.');
37
+ if (input.hidden !== undefined && typeof input.hidden !== 'boolean') problems.push('hidden must be true or false.');
38
+ return problems;
39
+ }
40
+
41
+ const DIR = () => path.resolve('content', 'sidebar', 'frequent');
42
+ const fileFor = (user) => (USER_ID_RE.test(String(user?.id || '')) ? path.join(DIR(), `${user.id}.json`) : null);
43
+
44
+ /**
45
+ * The user's list. `saved` is false until they have kept one here, so the
46
+ * browser knows to bring up a list it kept before Frequent moved to the server.
47
+ */
48
+ export async function readFrequent(user) {
49
+ const file = fileFor(user);
50
+ let data = null;
51
+ if (file) {
52
+ try { data = JSON.parse(await fs.readFile(file, 'utf8')); } catch { data = null; }
53
+ }
54
+ return {...normaliseFrequent(data), saved: !!data};
55
+ }
56
+
57
+ export async function writeFrequent(user, input) {
58
+ const file = fileFor(user);
59
+ if (!file) throw new Error('This account cannot keep a Frequent list.');
60
+ const data = normaliseFrequent(input);
61
+ await fs.mkdir(path.dirname(file), {recursive: true});
62
+ const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
63
+ await fs.writeFile(tmp, JSON.stringify(data, null, 2) + '\n', 'utf8');
64
+ await fs.rename(tmp, file);
65
+ return {...data, saved: true};
66
+ }
@@ -320,6 +320,8 @@ export function buildGitignore({sitePlugins = [], pluginContent = {}, sitePublic
320
320
  // Each admin's own Dashboard Pro layout (keyed by user id); the site default beside it is tracked.
321
321
  '/content/dashboard/layouts/',
322
322
  '/content/dashboard/prefs/',
323
+ // Each admin's own sidebar Frequent list (keyed by user id).
324
+ '/content/sidebar/frequent/',
323
325
  '/config/connections.json*',
324
326
  '/config/keys/',
325
327
  '/plugins/*/data/*.jsonl',