domma-cms 0.22.4 → 0.22.6

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.
@@ -0,0 +1 @@
1
+ export const TOOLS_FOLDER_TEXT="Tools",PLUGINS_FOLDER_TEXT="Plugins",MANAGE_PLUGINS_URL="#/plugins",MANAGE_PLUGINS_ITEM={text:"Manage Plugins",url:MANAGE_PLUGINS_URL,icon:"sliders",permission:"plugins"};export function stripItemByUrl(e,s){const t=[];for(const n of e||[]){if(!n||n.url===s)continue;const o={...n};Array.isArray(n.items)&&n.items.length&&(o.items=stripItemByUrl(n.items,s)),t.push(o)}return t}export function groupPluginItems(e){const s=[],t=[],n=[];for(const i of e||[])if(!(!i||!i.item)){if(i.parent){s.push(i);continue}i.item.core===!0?t.push(i.item):n.push(i.item)}const o=t.length?{text:TOOLS_FOLDER_TEXT,icon:"tool",items:t}:null,r={text:PLUGINS_FOLDER_TEXT,icon:"package",items:[{...MANAGE_PLUGINS_ITEM},...n]};return{toolsFolder:o,pluginsFolder:r,parented:s}}export function insertFoldersBeforeSystem(e,s){const t=(s||[]).filter(Boolean);if(!t.length)return e;const n=["system","documentation"],o=e.findIndex(r=>n.includes(String(r&&r.text||"").toLowerCase()));return o===-1?[...e,...t]:[...e.slice(0,o),...t,...e.slice(o)]}export function pruneEmptySynthesisedFolders(e){const s=new Set([TOOLS_FOLDER_TEXT,PLUGINS_FOLDER_TEXT]);return(e||[]).filter(t=>!t||!s.has(t.text)?!0:Array.isArray(t.items)&&t.items.length>0)}
@@ -0,0 +1 @@
1
+ import{test as l}from"node:test";import t from"node:assert/strict";import{groupPluginItems as u,stripItemByUrl as a,insertFoldersBeforeSystem as i,pruneEmptySynthesisedFolders as p,TOOLS_FOLDER_TEXT as m,PLUGINS_FOLDER_TEXT as g}from"./sidebar-grouping.js";const n=(s,e=null)=>({parent:e,item:s});l("groupPluginItems routes core items to Tools, optional to Plugins",()=>{const{toolsFolder:s,pluginsFolder:e}=u([n({text:"Analytics",url:"#/plugins/analytics",core:!0}),n({text:"Todo",url:"#/plugins/todo",core:!1}),n({text:"Notes",url:"#/plugins/notes"})]);t.equal(s.text,m),t.deepEqual(s.items.map(o=>o.text),["Analytics"]),t.equal(e.text,g),t.deepEqual(e.items.map(o=>o.text),["Manage Plugins","Todo","Notes"]),t.equal(e.items[0].url,"#/plugins"),t.equal(e.items[0].permission,"plugins")}),l("groupPluginItems omits Tools folder when no core plugins",()=>{const{toolsFolder:s,pluginsFolder:e}=u([n({text:"Todo",url:"#/plugins/todo",core:!1})]);t.equal(s,null),t.deepEqual(e.items.map(o=>o.text),["Manage Plugins","Todo"])}),l("groupPluginItems still returns Plugins folder (with Manage Plugins) when no plugins at all",()=>{const{toolsFolder:s,pluginsFolder:e}=u([]);t.equal(s,null),t.deepEqual(e.items.map(o=>o.text),["Manage Plugins"])}),l("groupPluginItems keeps explicitly-parented items separate",()=>{const{parented:s,pluginsFolder:e}=u([n({text:"Nested",url:"#/x"},"Content"),n({text:"Todo",url:"#/plugins/todo",core:!1})]);t.equal(s.length,1),t.equal(s[0].parent,"Content"),t.deepEqual(e.items.map(o=>o.text),["Manage Plugins","Todo"])}),l("stripItemByUrl removes the management link at any depth",()=>{const s=[{text:"Overview",items:[{text:"Dashboard",url:"#/"}]},{text:"System",items:[{text:"Users",url:"#/users"},{text:"Plugins",url:"#/plugins"}]}],o=a(s,"#/plugins").find(r=>r.text==="System");t.deepEqual(o.items.map(r=>r.text),["Users"]),t.equal(s.find(r=>r.text==="System").items.length,2)}),l("insertFoldersBeforeSystem places folders just before System",()=>{const e=i([{text:"Overview"},{text:"Data"},{text:"System"},{text:"Documentation"}],[{text:"Tools"},{text:"Plugins"},null]);t.deepEqual(e.map(o=>o.text),["Overview","Data","Tools","Plugins","System","Documentation"])}),l("insertFoldersBeforeSystem appends when no System/Documentation anchor",()=>{const e=i([{text:"Overview"},{text:"Data"}],[{text:"Plugins"}]);t.deepEqual(e.map(o=>o.text),["Overview","Data","Plugins"])}),l("pruneEmptySynthesisedFolders drops an empty Plugins folder but keeps built-ins",()=>{const e=p([{text:"Overview",items:[]},{text:"Tools",items:[{text:"Analytics"}]},{text:"Plugins",items:[]}]);t.deepEqual(e.map(o=>o.text),["Overview","Tools"])});
@@ -1,4 +1,4 @@
1
- import{api as c}from"../api.js";import{colourToCss as P}from"/public/js/menu-decor.mjs";function f(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const N=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Menus",url:"#/menus",icon:"menu"}];function L(e,i){return i?!e||!e.length?!1:e.includes(i)?!0:["read","create","update","delete"].some(t=>e.includes(`${i}.${t}`)):!0}function F(e,i){const t=[];for(const n of e){if(n.hidden||n.permission&&!L(i,n.permission))continue;const s=Array.isArray(n.items)&&n.items.length?F(n.items,i):[];t.push({...n,items:s})}return t}function T(e,i){if(!i||!i.length)return e;const t=(n,s)=>{for(const d of n){if((d.text||"").toLowerCase()===s.toLowerCase())return d;if(Array.isArray(d.items)){const r=t(d.items,s);if(r)return r}}return null};for(const n of i)if(n.parent){const s=t(e,n.parent);s?(s.items=Array.isArray(s.items)?s.items:[],s.items.push(n.item)):e.push(n.item)}else e.push(n.item);return e}async function W(e){if(!L(e,"projects"))return[];let i=[];try{i=await c.projects.list()}catch{return[]}const t={};await Promise.all(i.map(async s=>{try{const d=await c.projects.artefacts(s.slug);t[s.slug]=d}catch{t[s.slug]={}}}));const n=[{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:"views",text:"Views",icon:"eye",path:"/views"},{key:"roles",text:"Roles",icon:"shield",path:"/roles"},{key:"users",text:"Users",icon:"users",path:"/users"}];return i.map(s=>{const r="#/projects/"+encodeURIComponent(s.slug),h=t[s.slug]||{},u=[{text:"Overview",url:r,icon:s.icon||"folder"}];for(const l of n){const p=h[l.key],y=Array.isArray(p)?p.length:0;y<=0||u.push({text:`${l.text} (${y})`,url:r+l.path,icon:l.icon})}return u.push({text:"Settings",url:r+"/settings",icon:"settings"}),{text:s.name||s.slug,icon:s.icon||"folder",items:u}})}function D(e,i){for(const t of e)if((t.text||"").toLowerCase()==="projects"){t.items=Array.isArray(t.items)?t.items.slice():[],t.items.push(...i);return}}function U(e,i){if(!e.url||!i)return"";const t=i[e.url];return t==null||t<=0?"":`<span class="sidebar-badge">${f(String(t))}</span>`}function K(e){const i=(e.text||"").replace(/\s*\(\d+\)\s*$/,"").toLowerCase().replace(/\s+/g,"-"),t=(e.url||"").match(/^#\/projects\/([^/]+)/);return`sidebar.expanded.${t?`project-${decodeURIComponent(t[1])}.`:""}${i}`}function z(e,i){if(e&&e.type==="separator")return'<hr class="sidebar-divider">';const t=e.icon?`<span data-icon="${f(e.icon)}"></span>`:"",n=f(e.text||""),s=e.url?f(e.url):"",d=Array.isArray(e.items)&&e.items.length>0,r=e.colour?P(e.colour):"",h=r?` style="color:${f(r)}"`:"";let u="";if(e.badge&&e.badge.text!=null&&e.badge.text!==""){const l=e.badge.variant?P(e.badge.variant):"";u=`<span class="dm-menu-badge"${l?` style="background:${f(l)};color:#fff"`:""}>${f(String(e.badge.text))}</span>`}if(d){const l=K(e),p=S.get(l)!==!1,y=e.items.map(g=>z(g,i)).join("");return`<details data-state-key="${f(l)}"${p?" open":""}>
2
- <summary${h}>${t} <span class="sidebar-text">${n}</span>${u}</summary>
3
- <div class="sidebar-children">${y}</div>
4
- </details>`}return`<a href="${s}" class="sidebar-link" data-url="${s}"${h}>${t} <span class="sidebar-text">${n}</span>${U(e,i)}${u}</a>`}async function X(){const e=g=>g.then(b=>Array.isArray(b)?b.length:Array.isArray(b?.entries)?b.entries.length:0).catch(()=>0),[i,t,n,s,d,r,h,u,l,p,y]=await Promise.all([e(c.pages.list()),e(c.media.list()),e(c.collections.list()),e(c.forms.list()),e(c.views.list()),e(c.actions.list()),e(c.blocks.list()),e(c.components.list()),e(c.users.list()),e(c.plugins.list()),c.system?.notifications?.unreadCount?.().then(g=>g?.count??0).catch(()=>0)??Promise.resolve(0)]);return{"#/pages":i,"#/media":t,"#/collections":n,"#/forms":s,"#/views":d,"#/actions":r,"#/blocks":h,"#/components":u,"#/users":l,"#/plugins":p,"#/system/notifications":y}}async function O(){try{const e=await c.settings.get();return e?.adminBrand?.title||e?.title||"Admin"}catch{return"Admin"}}export async function renderAdminSidebar({mount:e,permissions:i}){const t=$(e).get(0);if(!t)return;let n,s=null,d=null,r=null;try{const a=await c.menus.get("admin-sidebar");n=Array.isArray(a?.items)?a.items:null,s=a?.variant||null,d=a?.position||null,r=a?.style||null}catch{n=null}(!n||!n.length)&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),n=N.slice());let h=[];try{h=await H.get("/api/sidebar/registered-items")||[]}catch{}n=T(n,h);const u=await W(i);u.length&&D(n,u),n=F(n,i);const[l,p]=await Promise.all([X().catch(()=>({})),O()]),y=s?` dm-admin-sidebar--${f(s)}`:"";function g(a,m="px"){if(a==null)return a;const o=String(a).trim();return/^\d+(\.\d+)?$/.test(o)?`${o}${m}`:o}let b="";if(r){const a=[];r.fontFamily&&a.push(`font-family: ${r.fontFamily}, sans-serif`),r.fontSize&&a.push(`font-size: ${g(r.fontSize)}`),r.fontWeight&&a.push(`font-weight: ${r.fontWeight}`),r.letterSpacing&&a.push(`letter-spacing: ${g(r.letterSpacing,"em")}`);const m=[];if(a.length&&m.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("; ")} }`),r.iconSize){const o=g(r.iconSize);m.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${o} !important; height: ${o} !important; }`)}m.length&&(b=`<style data-admin-sidebar-style>${m.join(" ")}</style>`)}const B=`<div class="dm-admin-sidebar-header"><span data-icon="layout"></span> ${f(p)}</div>`,R=`${b}<nav class="dm-admin-sidebar${y}">${B}${n.map(a=>z(a,l)).join("")}</nav>`,k=document.createRange();k.selectNodeContents(t);const I=k.createContextualFragment(R);t.replaceChildren(I),Domma.icons.scan(t);const x=Number.parseInt(S.get("sidebar.width"),10);Number.isFinite(x)&&x>=180&&x<=480&&(t.style.width=x+"px");const v=document.createElement("div");v.className="dm-admin-sidebar-handle",v.setAttribute("aria-label","Resize sidebar"),t.appendChild(v),t.querySelectorAll("details").forEach(a=>{a.addEventListener("toggle",function(){const m=this.getAttribute("data-state-key");m&&S.set(m,this.open);const o=this.querySelector(":scope > .sidebar-children");if(o)if(this.open){const A=o.scrollHeight;o.style.maxHeight="0",requestAnimationFrame(()=>{o.style.maxHeight=A+"px",o.addEventListener("transitionend",function q(){o.style.maxHeight="none",o.removeEventListener("transitionend",q)})})}else{const A=o.scrollHeight;o.style.maxHeight=A+"px",requestAnimationFrame(()=>{o.style.maxHeight="0"})}})});let w=!1,C=0,j=0;v.addEventListener("mousedown",a=>{w=!0,C=a.clientX,j=t.getBoundingClientRect().width,document.body.style.cursor="col-resize",document.body.style.userSelect="none",a.preventDefault()}),document.addEventListener("mousemove",a=>{if(!w)return;const m=Math.max(180,Math.min(480,j+(a.clientX-C)));t.style.width=m+"px"}),document.addEventListener("mouseup",()=>{w&&(w=!1,document.body.style.cursor="",document.body.style.userSelect="",S.set("sidebar.width",parseInt(t.style.width,10)))});function E(){const a=location.hash||"#/";$(t).find(".sidebar-link").removeClass("active"),$(t).find(`.sidebar-link[data-url="${a}"]`).addClass("active")}E(),M.subscribe("router:afterChange",E)}
1
+ import{api as c}from"../api.js";import{colourToCss as F}from"/public/js/menu-decor.mjs";import{groupPluginItems as W,stripItemByUrl as D,insertFoldersBeforeSystem as _,pruneEmptySynthesisedFolders as K,MANAGE_PLUGINS_URL as X}from"./sidebar-grouping.js";function f(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const G=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Menus",url:"#/menus",icon:"menu"}];function B(e,i){return i?!e||!e.length?!1:e.includes(i)?!0:["read","create","update","delete"].some(t=>e.includes(`${i}.${t}`)):!0}function z(e,i){const t=[];for(const n of e){if(n.hidden||n.permission&&!B(i,n.permission))continue;const s=Array.isArray(n.items)&&n.items.length?z(n.items,i):[];t.push({...n,items:s})}return t}function O(e,i){if(!i||!i.length)return e;const t=(n,s)=>{for(const d of n){if((d.text||"").toLowerCase()===s.toLowerCase())return d;if(Array.isArray(d.items)){const a=t(d.items,s);if(a)return a}}return null};for(const n of i)if(n.parent){const s=t(e,n.parent);s?(s.items=Array.isArray(s.items)?s.items:[],s.items.push(n.item)):e.push(n.item)}else e.push(n.item);return e}async function V(e){if(!B(e,"projects"))return[];let i=[];try{i=await c.projects.list()}catch{return[]}const t={};await Promise.all(i.map(async s=>{try{const d=await c.projects.artefacts(s.slug);t[s.slug]=d}catch{t[s.slug]={}}}));const n=[{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:"views",text:"Views",icon:"eye",path:"/views"},{key:"roles",text:"Roles",icon:"shield",path:"/roles"},{key:"users",text:"Users",icon:"users",path:"/users"}];return i.map(s=>{const a="#/projects/"+encodeURIComponent(s.slug),p=t[s.slug]||{},u=[{text:"Overview",url:a,icon:s.icon||"folder"}];for(const l of n){const h=p[l.key],g=Array.isArray(h)?h.length:0;g<=0||u.push({text:`${l.text} (${g})`,url:a+l.path,icon:l.icon})}return u.push({text:"Settings",url:a+"/settings",icon:"settings"}),{text:s.name||s.slug,icon:s.icon||"folder",items:u}})}function Y(e,i){for(const t of e)if((t.text||"").toLowerCase()==="projects"){t.items=Array.isArray(t.items)?t.items.slice():[],t.items.push(...i);return}}function J(e,i){if(!e.url||!i)return"";const t=i[e.url];return t==null||t<=0?"":`<span class="sidebar-badge">${f(String(t))}</span>`}function Q(e){const i=(e.text||"").replace(/\s*\(\d+\)\s*$/,"").toLowerCase().replace(/\s+/g,"-"),t=(e.url||"").match(/^#\/projects\/([^/]+)/);return`sidebar.expanded.${t?`project-${decodeURIComponent(t[1])}.`:""}${i}`}function I(e,i){if(e&&e.type==="separator")return'<hr class="sidebar-divider">';const t=e.icon?`<span data-icon="${f(e.icon)}"></span>`:"",n=f(e.text||""),s=e.url?f(e.url):"",d=Array.isArray(e.items)&&e.items.length>0,a=e.colour?F(e.colour):"",p=a?` style="color:${f(a)}"`:"";let u="";if(e.badge&&e.badge.text!=null&&e.badge.text!==""){const l=e.badge.variant?F(e.badge.variant):"";u=`<span class="dm-menu-badge"${l?` style="background:${f(l)};color:#fff"`:""}>${f(String(e.badge.text))}</span>`}if(d){const l=Q(e),h=S.get(l)!==!1,g=e.items.map(y=>I(y,i)).join("");return`<details data-state-key="${f(l)}"${h?" open":""}>
2
+ <summary${p}>${t} <span class="sidebar-text">${n}</span>${u}</summary>
3
+ <div class="sidebar-children">${g}</div>
4
+ </details>`}return`<a href="${s}" class="sidebar-link" data-url="${s}"${p}>${t} <span class="sidebar-text">${n}</span>${J(e,i)}${u}</a>`}async function Z(){const e=y=>y.then(b=>Array.isArray(b)?b.length:Array.isArray(b?.entries)?b.entries.length:0).catch(()=>0),[i,t,n,s,d,a,p,u,l,h,g]=await Promise.all([e(c.pages.list()),e(c.media.list()),e(c.collections.list()),e(c.forms.list()),e(c.views.list()),e(c.actions.list()),e(c.blocks.list()),e(c.components.list()),e(c.users.list()),e(c.plugins.list()),c.system?.notifications?.unreadCount?.().then(y=>y?.count??0).catch(()=>0)??Promise.resolve(0)]);return{"#/pages":i,"#/media":t,"#/collections":n,"#/forms":s,"#/views":d,"#/actions":a,"#/blocks":p,"#/components":u,"#/users":l,"#/plugins":h,"#/system/notifications":g}}async function ee(){try{const e=await c.settings.get();return e?.adminBrand?.title||e?.title||"Admin"}catch{return"Admin"}}export async function renderAdminSidebar({mount:e,permissions:i}){const t=$(e).get(0);if(!t)return;let n,s=null,d=null,a=null;try{const r=await c.menus.get("admin-sidebar");n=Array.isArray(r?.items)?r.items:null,s=r?.variant||null,d=r?.position||null,a=r?.style||null}catch{n=null}(!n||!n.length)&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),n=G.slice());let p=[];try{p=await H.get("/api/sidebar/registered-items")||[]}catch{}n=D(n,X);const{toolsFolder:u,pluginsFolder:l,parented:h}=W(p);n=O(n,h),n=_(n,[u,l]);const g=await V(i);g.length&&Y(n,g),n=z(n,i),n=K(n);const[y,b]=await Promise.all([Z().catch(()=>({})),ee()]),R=s?` dm-admin-sidebar--${f(s)}`:"";function w(r,m="px"){if(r==null)return r;const o=String(r).trim();return/^\d+(\.\d+)?$/.test(o)?`${o}${m}`:o}let C="";if(a){const r=[];a.fontFamily&&r.push(`font-family: ${a.fontFamily}, sans-serif`),a.fontSize&&r.push(`font-size: ${w(a.fontSize)}`),a.fontWeight&&r.push(`font-weight: ${a.fontWeight}`),a.letterSpacing&&r.push(`letter-spacing: ${w(a.letterSpacing,"em")}`);const m=[];if(r.length&&m.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 { ${r.join("; ")} }`),a.iconSize){const o=w(a.iconSize);m.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${o} !important; height: ${o} !important; }`)}m.length&&(C=`<style data-admin-sidebar-style>${m.join(" ")}</style>`)}const N=`<div class="dm-admin-sidebar-header"><span data-icon="layout"></span> ${f(b)}</div>`,U=`${C}<nav class="dm-admin-sidebar${R}">${N}${n.map(r=>I(r,y)).join("")}</nav>`,j=document.createRange();j.selectNodeContents(t);const q=j.createContextualFragment(U);t.replaceChildren(q),Domma.icons.scan(t);const x=Number.parseInt(S.get("sidebar.width"),10);Number.isFinite(x)&&x>=180&&x<=480&&(t.style.width=x+"px");const A=document.createElement("div");A.className="dm-admin-sidebar-handle",A.setAttribute("aria-label","Resize sidebar"),t.appendChild(A),t.querySelectorAll("details").forEach(r=>{r.addEventListener("toggle",function(){const m=this.getAttribute("data-state-key");m&&S.set(m,this.open);const o=this.querySelector(":scope > .sidebar-children");if(o)if(this.open){const k=o.scrollHeight;o.style.maxHeight="0",requestAnimationFrame(()=>{o.style.maxHeight=k+"px",o.addEventListener("transitionend",function T(){o.style.maxHeight="none",o.removeEventListener("transitionend",T)})})}else{const k=o.scrollHeight;o.style.maxHeight=k+"px",requestAnimationFrame(()=>{o.style.maxHeight="0"})}})});let v=!1,E=0,P=0;A.addEventListener("mousedown",r=>{v=!0,E=r.clientX,P=t.getBoundingClientRect().width,document.body.style.cursor="col-resize",document.body.style.userSelect="none",r.preventDefault()}),document.addEventListener("mousemove",r=>{if(!v)return;const m=Math.max(180,Math.min(480,P+(r.clientX-E)));t.style.width=m+"px"}),document.addEventListener("mouseup",()=>{v&&(v=!1,document.body.style.cursor="",document.body.style.userSelect="",S.set("sidebar.width",parseInt(t.style.width,10)))});function L(){const r=location.hash||"#/";$(t).find(".sidebar-link").removeClass("active"),$(t).find(`.sidebar-link[data-url="${r}"]`).addClass("active")}L(),M.subscribe("router:afterChange",L)}
@@ -139,12 +139,6 @@
139
139
  "icon": "layout",
140
140
  "permission": "layouts"
141
141
  },
142
- {
143
- "text": "Plugins",
144
- "url": "#/plugins",
145
- "icon": "package",
146
- "permission": "plugins"
147
- },
148
142
  {
149
143
  "text": "My Profile",
150
144
  "url": "#/my-profile",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.22.4",
3
+ "version": "0.22.6",
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",
@@ -53,7 +53,7 @@
53
53
  <div class="card">
54
54
  <div class="card-header d-flex justify-content-between align-items-center">
55
55
  <strong>Top pages</strong>
56
- <input id="filter-input" type="search" class="form-control form-control-sm" placeholder="Filter URL…" style="max-width: 240px;">
56
+ <input id="filter-input" type="search" class="form-input form-input-sm" placeholder="Filter URL…" style="max-width: 240px;">
57
57
  </div>
58
58
  <div class="card-body">
59
59
  <div id="analytics-table"></div>
@@ -64,7 +64,7 @@ const _loadedPlugins = {};
64
64
  * Core plugins — always loaded regardless of plugins.json enabled state.
65
65
  * These are considered first-class CMS features, not optional add-ons.
66
66
  */
67
- const CORE_PLUGINS = new Set();
67
+ const CORE_PLUGINS = new Set(['analytics']);
68
68
 
69
69
  /**
70
70
  * Scan the plugins/ directory and return all valid manifests.
@@ -217,6 +217,22 @@ export async function registerPlugins(fastify) {
217
217
  });
218
218
  loaded.push(manifest.name);
219
219
 
220
+ // Bridge plugin.json `admin.sidebar` entries into the sidebar
221
+ // registry so they render in the admin sidebar. The admin renderer
222
+ // reads plugin nav from /api/sidebar/registered-items; plugins
223
+ // declare their nav in the manifest rather than calling
224
+ // registerSidebarItem, so register it here on their behalf.
225
+ // Tag each item with the plugin's core status so the admin sidebar
226
+ // can auto-group it: core plugins → Tools folder, optional → Plugins.
227
+ const isCore = CORE_PLUGINS.has(manifest.name);
228
+ for (const item of manifest.admin?.sidebar || []) {
229
+ try {
230
+ registerSidebarItem({item: {...item, core: isCore}});
231
+ } catch (err) {
232
+ fastify.log.warn(`[plugins] sidebar item for "${manifest.name}" skipped: ${err.message}`);
233
+ }
234
+ }
235
+
220
236
  // Ensure plugin collections exist (idempotent — skips if already present)
221
237
  await setupPlugin(manifest.name, {collections: {getCollection, createCollection}})
222
238
  .catch(err => fastify.log.warn(`[plugins] Collection setup for "${manifest.name}" failed: ${err.message}`));
@@ -73,7 +73,7 @@ const SEED_ENTRIES = [
73
73
  permissions: [
74
74
  'pages', 'media', 'blocks', 'navigation', 'layouts',
75
75
  'collections', 'views', 'actions',
76
- 'users', 'settings', 'notifications'
76
+ 'users', 'settings', 'notifications', 'plugins'
77
77
  ],
78
78
  badgeClass: 'badge-warning'
79
79
  },