domma-cms 0.22.5 → 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.5",
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.
@@ -222,9 +222,12 @@ export async function registerPlugins(fastify) {
222
222
  // reads plugin nav from /api/sidebar/registered-items; plugins
223
223
  // declare their nav in the manifest rather than calling
224
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);
225
228
  for (const item of manifest.admin?.sidebar || []) {
226
229
  try {
227
- registerSidebarItem({item});
230
+ registerSidebarItem({item: {...item, core: isCore}});
228
231
  } catch (err) {
229
232
  fastify.log.warn(`[plugins] sidebar item for "${manifest.name}" skipped: ${err.message}`);
230
233
  }
@@ -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
  },