domma-cms 1.6.47 → 1.6.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/admin/index.html CHANGED
@@ -35,7 +35,7 @@
35
35
  <header id="admin-topbar">
36
36
  <div class="topbar-brand">
37
37
  <span data-icon="layout"></span>
38
- <span class="topbar-brand-text">Domma CMS</span>
38
+ <span class="topbar-brand-text"></span>
39
39
  </div>
40
40
  <div class="topbar-user" id="topbar-user">
41
41
  <!-- Populated after auth: name + role badge -->
package/admin/js/app.js CHANGED
@@ -1,4 +1,4 @@
1
- import{views as G}from"./views/index.js";import{withPluginChrome as B}from"./lib/plugin-chrome.js";import{api as c,getUser as F,isAuthenticated as m,logout as z,sendToLogin as K}from"./api.js";import{isAuthScreen as C,loginHashFor as J}from"./lib/login-arrange.js";import{installHttpInterceptor as Y}from"./http-interceptor.js";import{installResizableSlideovers as Q}from"./lib/slideover-resizable.js";import{installActivityThrobber as X}from"./lib/activity-throbber.js";import{installUpdateNotice as Z}from"./lib/update-notice.js";import{isPathAllowed as tt,redirectFor as et}from"./lib/admin-scope.js";import{deliverDesktop as it,desktopActive as ot,notificationSettings as at}from"./lib/desktop-notify.js";import{avatarUrl as st}from"./lib/avatar.js";import{installCalculator as nt}from"./lib/calculator.js";import{takeOverviewPass as lt,withDashboardStrip as O}from"./lib/dashboards-strip.js";import{installHotkeys as rt}from"./lib/hotkeys-ui.js";$(()=>{Y(),nt(),Z(),X(),Q(),(async()=>{const{applyAdminTheme:t,refreshAdminTheme:i}=await import("./lib/admin-theme.js");m()?await i(c,{init:!0}):t({dommaTheme:"charcoal-dark"},{init:!0})})();let p=null,r="/",h=!1,w=null;function b(t){if(y[t])return E.toast(`${y[t]} is switched off on this site.`,{type:"info"}),"/";if(t.split("?")[0]==="/my-dashboard")return"/dashboards";const i=D[t]||et(p,t);return i||((t.split("?")[0]||"/")==="/"&&r&&r!=="/"&&!lt()?D[r]||r:null)}const D={},y={};R.use(async(t,i,e)=>{if(C(t.path))return e();if(!m()){K();return}const o=b(t.path);if(o){R.navigate(o);return}e()});async function g(){try{const t=await c.get("/auth/permissions");p=t.adminScope||null,r=t.adminHome||"/";const i=t.permissions||[];return h=tt(p,"/settings")&&i.some(e=>e==="settings"||e.startsWith("settings.")),w=i.some(e=>e==="notifications"||e.startsWith("notifications.")),i}catch{return p=null,r="/",h=!1,w=null,[]}}document.addEventListener("dm:admin-home-changed",()=>{g()});let N=null,P=!1;document.addEventListener("dm:sidebar-rebuild",()=>{if(N){P=!0;return}N=(async()=>{do{P=!1;try{await A(await g())}catch{}}while(P)})().finally(()=>{N=null})});async function A(t){const{renderAdminSidebar:i}=await import("./lib/sidebar-renderer.js");await i({mount:"#admin-sidebar",permissions:t,adminScope:p})}M.subscribe("router:afterChange",({to:t})=>{C(t.path)&&(u&&(clearInterval(u),u=null),p=null,r="/",h=!1,w=null)});const _=[{path:"/",view:"dashboard",title:"Dashboard - Domma CMS"},{path:"/dashboards",view:"dashboards",title:"Dashboards - Domma CMS"},{path:"/my-dashboard",view:"dashboards",title:"Dashboards - Domma CMS"},{path:"/pages",view:"pages",title:"Pages - Domma CMS"},{path:"/pages/new",view:"pageEditor",title:"New Page - Domma CMS"},{path:"/pages/edit/*",view:"pageEditor",title:"Edit Page - Domma CMS"},{path:"/media",view:"media",title:"Media - Domma CMS"},{path:"/menus",view:"menus",title:"Menus - Domma CMS"},{path:"/context-menus",view:"contextMenus",title:"Context Menus - Domma CMS"},{path:"/context-menus/new",view:"contextMenuEditor",title:"New Context Menu - Domma CMS"},{path:"/context-menus/edit/:slug",view:"contextMenuEditor",title:"Edit Context Menu - Domma CMS"},{path:"/menus/new",view:"menuEditor",title:"New Menu - Domma CMS"},{path:"/menus/edit/:slug",view:"menuEditor",title:"Edit Menu - Domma CMS"},{path:"/menu-locations",view:"menuLocations",title:"Menu Locations - Domma CMS"},{path:"/navigation",view:"menusRedirect",title:"Menus - Domma CMS"},{path:"/layouts",view:"layouts",title:"Layouts - Domma CMS"},{path:"/settings",view:"settings",title:"Settings - Domma CMS"},{path:"/theme",view:"theme",title:"Theme - Domma CMS"},{path:"/users",view:"users",title:"Users - Domma CMS"},{path:"/users/new",view:"userEditor",title:"New User - Domma CMS"},{path:"/users/edit/:id",view:"userEditor",title:"Edit User - Domma CMS"},{path:"/plugins",view:"plugins",title:"Plugins - Domma CMS"},{path:"/plugins/code/:name",view:"pluginCode",title:"Plugin Code - Domma CMS"},{path:"/documentation",view:"documentation",title:"Usage - Domma CMS"},{path:"/tutorials",view:"tutorials",title:"Tutorials - Domma CMS"},{path:"/tutorials/crud",view:"tutorialCrud",title:"Building a CRUD App - Domma CMS"},{path:"/tutorials/plugin",view:"tutorialPlugin",title:"Writing a Plugin - Domma CMS"},{path:"/tutorials/forms",view:"tutorialForms",title:"Form Follow-Up - Domma CMS"},{path:"/docs/components",view:"componentsReference",title:"Components Reference - Domma CMS"},{path:"/docs/components-howto",view:"componentsHowto",title:"Components How-To - Domma CMS"},{path:"/docs/components-walkthrough",view:"componentsWalkthrough",title:"Components Walkthrough - Domma CMS"},{path:"/docs/components-rules",view:"componentsRules",title:"Components Rules - Domma CMS"},{path:"/docs/usage/pages",view:"usagePages",title:"Pages - Usage - Domma CMS"},{path:"/docs/usage/media",view:"usageMedia",title:"Media - Usage - Domma CMS"},{path:"/docs/usage/navigation",view:"usageNavigation",title:"Navigation - Usage - Domma CMS"},{path:"/docs/usage/dconfig",view:"usageDconfig",title:"DConfig - Usage - Domma CMS"},{path:"/docs/usage/shortcodes",view:"usageShortcodes",title:"Shortcodes - Usage - Domma CMS"},{path:"/docs/usage/site-settings",view:"usageSiteSettings",title:"Site Settings - Usage - Domma CMS"},{path:"/docs/usage/plugins",view:"usagePlugins",title:"Plugins - Usage - Domma CMS"},{path:"/docs/usage/users-roles",view:"usageUsersRoles",title:"Users & Roles - Usage - Domma CMS"},{path:"/docs/usage/views",view:"usageViews",title:"Views - Usage - Domma CMS"},{path:"/docs/usage/actions",view:"usageActions",title:"Actions - Usage - Domma CMS"},{path:"/docs/usage/cta-shortcode",view:"usageCtaShortcode",title:"CTA Shortcode - Usage - Domma CMS"},{path:"/docs/usage/editions",view:"usageEditions",title:"Editions & Licences - Usage - Domma CMS"},{path:"/docs/usage/collections",view:"usageCollections",title:"Collections & Forms - Usage - Domma CMS"},{path:"/docs/usage/tools",view:"usageTools",title:"Built-in Tools - Usage - Domma CMS"},{path:"/docs/usage/dashboard",view:"usageDashboard",title:"Dashboard - Usage - Domma CMS"},{path:"/docs/plugins",view:"pluginGuides",title:"Plugin Guides - Domma CMS"},{path:"/docs/plugins/*",view:"pluginGuide",title:"Plugin Guide - Domma CMS"},{path:"/docs/api/authentication",view:"apiRefAuthentication",title:"Authentication - API - Domma CMS"},{path:"/docs/api/pages",view:"apiRefPages",title:"Pages - API - Domma CMS"},{path:"/docs/api/settings",view:"apiRefSettings",title:"Settings - API - Domma CMS"},{path:"/docs/api/layouts",view:"apiRefLayouts",title:"Layouts - API - Domma CMS"},{path:"/docs/api/navigation",view:"apiRefNavigation",title:"Navigation - API - Domma CMS"},{path:"/docs/api/media",view:"apiRefMedia",title:"Media - API - Domma CMS"},{path:"/docs/api/users",view:"apiRefUsers",title:"Users - API - Domma CMS"},{path:"/docs/api/plugins",view:"apiRefPlugins",title:"Plugins - API - Domma CMS"},{path:"/docs/api/collections",view:"apiRefCollections",title:"Collections - API - Domma CMS"},{path:"/docs/api/views",view:"apiRefViews",title:"Views API - Domma CMS"},{path:"/docs/api/actions",view:"apiRefActions",title:"Actions API - Domma CMS"},{path:"/docs/api/forms",view:"apiRefForms",title:"Forms API - Domma CMS"},{path:"/docs/api/scaffold",view:"apiRefScaffold",title:"Scaffold API - Domma CMS"},{path:"/docs/api/external",view:"apiRefExternal",title:"External API & Tokens - Domma CMS"},{path:"/docs/api/content",view:"apiRefContent",title:"Content API - Domma CMS"},{path:"/docs/api/builder",view:"apiRefBuilder",title:"API Builder - Domma CMS"},{path:"/api-reference",view:"apiReference",title:"API Reference - Domma CMS"},{path:"/collections",view:"collections",title:"Collections - Domma CMS"},{path:"/collections/new",view:"collectionEditor",title:"New Collection - Domma CMS"},{path:"/collections/edit/:slug",view:"collectionEditor",title:"Edit Collection - Domma CMS"},{path:"/collections/:slug/entries",view:"collectionEntries",title:"Entries - Domma CMS"},{path:"/forms",view:"forms",title:"Forms - Domma CMS"},{path:"/forms/new",view:"formEditor",title:"New Form - Domma CMS"},{path:"/forms/edit/:slug",view:"formEditor",title:"Edit Form - Domma CMS"},{path:"/forms/:slug/submissions",view:"formSubmissions",title:"Submissions - Domma CMS"},{path:"/views",view:"viewsList",title:"Views - Domma CMS"},{path:"/views/new",view:"viewEditor",title:"New View - Domma CMS"},{path:"/views/edit/:slug",view:"viewEditor",title:"Edit View - Domma CMS"},{path:"/views/:slug/preview",view:"viewPreview",title:"View Preview - Domma CMS"},{path:"/actions",view:"actionsList",title:"Actions - Domma CMS"},{path:"/actions/new",view:"actionEditor",title:"New Action - Domma CMS"},{path:"/actions/edit/:slug",view:"actionEditor",title:"Edit Action - Domma CMS"},{path:"/pro/docs",view:"proDocs",title:"Pro Documentation - Domma CMS"},{path:"/blocks",view:"blocks",title:"Blocks - Domma CMS"},{path:"/blocks/new",view:"blockEditor",title:"New Block - Domma CMS"},{path:"/blocks/edit/:name",view:"blockEditor",title:"Edit Block - Domma CMS"},{path:"/components",view:"components",title:"Components - Domma CMS"},{path:"/components/new",view:"componentEditor",title:"New Component - Domma CMS"},{path:"/components/edit/:name",view:"componentEditor",title:"Edit Component - Domma CMS"},{path:"/my-profile",view:"myProfile",title:"My Profile - Domma CMS"},{path:"/roles",view:"roles",title:"Roles & Permissions - Domma CMS"},{path:"/roles/edit/:id",view:"roleEditor",title:"Edit Role - Domma CMS"},{path:"/effects",view:"effects",title:"Effects - Domma CMS"},{path:"/search",view:"search",title:"Search - Domma CMS"},{path:"/content-api",view:"contentApi",title:"Content API - Domma CMS"},{path:"/notes",view:"notes",title:"Notes - Domma CMS"},{path:"/todo",view:"todo",title:"Todo - Domma CMS"},{path:"/calculator",view:"calculator",title:"Calculator - Domma CMS"},{path:"/analytics",view:"analytics",title:"Analytics - Domma CMS"},{path:"/seo",view:"seo",title:"SEO - Domma CMS"},{path:"/seo/:tab",view:"seo",title:"SEO - Domma CMS"},{path:"/contacts",view:"contacts",title:"Contacts - Domma CMS"},{path:"/calls/new",view:"callNew",title:"Call now - Domma CMS"},{path:"/system/notifications",view:"notifications",title:"Notifications - Domma CMS"},{path:"/api-tokens",view:"apiTokens",title:"API Tokens - Domma CMS"},{path:"/api-endpoints",view:"apiEndpoints",title:"API Builder - Domma CMS"},{path:"/api-endpoints/new",view:"apiEndpointEditor",title:"New API Endpoint - Domma CMS"},{path:"/api-endpoints/edit/:id",view:"apiEndpointEditor",title:"Edit API Endpoint - Domma CMS"},{path:"/projects",view:"projects",title:"Projects - Domma CMS"},{path:"/projects/new",view:"projects",title:"New Project - Domma CMS"},{path:"/projects/edit/:slug",view:"projects",title:"Edit Project - Domma CMS"},{path:"/projects/:slug/settings",view:"projectSettings",title:"Project settings - Domma CMS"},{path:"/projects/:slug/pages",view:"pages",title:"Project pages - Domma CMS"},{path:"/projects/:slug/collections",view:"collections",title:"Project collections - Domma CMS"},{path:"/projects/:slug/forms",view:"forms",title:"Project forms - Domma CMS"},{path:"/projects/:slug/actions",view:"actionsList",title:"Project actions - Domma CMS"},{path:"/projects/:slug/menus",view:"menus",title:"Project menus - Domma CMS"},{path:"/projects/:slug/blocks",view:"blocks",title:"Project blocks - Domma CMS"},{path:"/projects/:slug/components",view:"components",title:"Project components - Domma CMS"},{path:"/projects/:slug/views",view:"viewsList",title:"Project views - Domma CMS"},{path:"/projects/:slug/roles",view:"roles",title:"Project roles - Domma CMS"},{path:"/projects/:slug/users",view:"users",title:"Project users - Domma CMS"},{path:"/projects/:slug/apis",view:"apiEndpoints",title:"Project APIs - Domma CMS"},{path:"/projects/:slug",view:"projectDetail",title:"Project - Domma CMS"},{path:"/login",view:"login",title:"Sign in - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}},{path:"/reset-password",view:"login",title:"Reset Password - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}}];M.subscribe("router:afterChange",async({to:t,from:i})=>{if(!C(t.path)){if($("#admin-sidebar").show(),$("#admin-topbar").show(),C(i?.path)){$("#topbar-user-name").remove(),import("./lib/admin-theme.js").then(a=>a.refreshAdminTheme(c)),await I();const e=await g(),o=b(t.path);if(o&&R.navigate(o),A(e),at.read().signInToasts)try{const a=await c.system.notifications.list().catch(()=>[]),n=(Array.isArray(a)?a:[]).filter(s=>s.unread&&["warning","critical"].includes(s.data?.severity)).slice(0,3);for(const s of n){const f=d(s.data?.title||""),U=d((s.data?.body||"").slice(0,120));E.toast(`${f} - ${U}`,{type:s.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}T()}}),M.subscribe("router:afterChange",()=>{setTimeout(()=>{$(".btn-primary, .btn-danger").length&&Domma.effects.reveal(".btn-primary, .btn-danger",{animation:"fade",stagger:40,duration:300})},50)});const V=["#field-project","#menu-project","#collection-project","#endpoint-project","#action-project","#view-project","#block-project","#component-project","#role-project",'[name="project"]','[name="ownedByProject"]'];function W(t){return/^\/(pages|menus|context-menus|collections|forms|actions|views|blocks|components|users|roles|api-endpoints)\/(new|edit\/)/.test(t)}M.subscribe("router:afterChange",({to:t})=>{const i=t?String(t.path).split("?")[0]:"";if(!t||!W(i))return;const o=new URLSearchParams(location.hash.split("?")[1]||"").get("project");if(!o||!/\/new$/.test(i))return;let a=0;const n=60,s=1500,f=()=>{a+=n;for(const U of V){const v=document.querySelector(U);if(!v)continue;if(Array.from(v.options||[]).find(q=>q.value===o)){v.value=o,v.dispatchEvent(new Event("change",{bubbles:!0}));return}}a<s&&setTimeout(f,n)};setTimeout(f,n)});const x="cms_card_states",j=S.get(x)||{};M.subscribe("router:afterChange",()=>{setTimeout(()=>{$("#view-container .card-collapsible").each(function(){const t=$(this).find(".card-header h2, .card-header h3").first().text().trim();t&&j[t]==="collapsed"&&$(this).addClass("card-collapsed")})},200)}),$("#view-container").on("click",".card-collapsible .card-header",function(t){if($(t.target).closest("button, a").length)return;const i=$(this).closest(".card");i.toggleClass("card-collapsed");const e=$(this).find("h2, h3").first().text().trim();e&&(j[e]=i.hasClass("card-collapsed")?"collapsed":"open",S.set(x,j))}),rt(),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash.startsWith("#/login")||window.location.hash.startsWith("#/reset-password"))return;const i=document.querySelector("#view-container .view-header button.btn-primary");i&&(t.preventDefault(),i.click())});const l={...G},H={notes:{displayName:"Notes",icon:"edit-3"},todo:{displayName:"Todo",icon:"check-square"},calculator:{displayName:"Calculator",icon:"calculator"},analytics:{displayName:"Analytics",icon:"chart-bar"},seo:{displayName:"SEO",icon:"search"},contacts:{displayName:"Contacts",icon:"users"},projects:{displayName:"Projects",icon:"folder"},pages:{displayName:"Pages",icon:"file-text"},media:{displayName:"Media",icon:"image"},menus:{displayName:"Site menus",icon:"menu"},contextMenus:{displayName:"Context Menus",icon:"menu"},theme:{displayName:"Theme",icon:"droplet"},collections:{displayName:"Collections",icon:"database"},forms:{displayName:"Forms",icon:"layout"},viewsList:{displayName:"Views",icon:"eye"},actionsList:{displayName:"Actions",icon:"zap"},apiEndpoints:{displayName:"API Builder",icon:"code"},blocks:{displayName:"Blocks",icon:"box"},components:{displayName:"Components",icon:"component"},notifications:{displayName:"Notifications",icon:"bell"},roles:{displayName:"Roles",icon:"shield"},users:{displayName:"Users",icon:"users"},settings:{displayName:"Site Settings",icon:"settings"},effects:{displayName:"Effects",icon:"sparkles"},layouts:{displayName:"Layouts",icon:"layout"},plugins:{displayName:"Marketplace",icon:"store"},myProfile:{displayName:"My Profile",icon:"user"},apiTokens:{displayName:"API Tokens",icon:"key"},search:{displayName:"Search",icon:"search"},contentApi:{displayName:"Content API",icon:"file-text"},dashboard:{displayName:"Dashboard",icon:"home"},dashboards:{displayName:"Dashboards",icon:"home"},pageEditor:{displayName:"Pages",section:"Edit",icon:"file-text"},menuEditor:{displayName:"Site menus",section:"Edit",icon:"menu"},menuLocations:{displayName:"Site menus",section:"Locations",icon:"map-pin"},contextMenuEditor:{displayName:"Context Menus",section:"Edit",icon:"menu"},collectionEditor:{displayName:"Collections",section:"Edit",icon:"database"},collectionEntries:{displayName:"Collections",section:"Entries",icon:"database"},formEditor:{displayName:"Forms",section:"Edit",icon:"layout"},formSubmissions:{displayName:"Forms",section:"Submissions",icon:"inbox"},viewEditor:{displayName:"Views",section:"Edit",icon:"eye"},viewPreview:{displayName:"Views",section:"Preview",icon:"eye"},actionEditor:{displayName:"Actions",section:"Edit",icon:"zap"},apiEndpointEditor:{displayName:"API Builder",section:"Edit",icon:"code"},blockEditor:{displayName:"Blocks",section:"Edit",icon:"box"},componentEditor:{displayName:"Components",section:"Edit",icon:"component"},roleEditor:{displayName:"Roles",section:"Edit",icon:"shield"},userEditor:{displayName:"Users",section:"Edit",icon:"users"},projectDetail:{displayName:"Projects",section:"Overview",icon:"folder"},projectSettings:{displayName:"Projects",section:"Settings",icon:"folder"},pluginCode:{displayName:"Marketplace",section:"Code",icon:"code"}};for(const[t,i]of Object.entries(H))l[t]&&(l[t]=B(l[t],{plugin:t,licence:"Built in",...i}));l.dashboard=O(l.dashboard);const k=[..._];async function I(){if(m())try{const t=await c.plugins.adminConfig(),i=new Set(k.map(e=>e.path));for(const e of t.routes||[])i.has(e.path)||k.push(e);for(const[e,o]of Object.entries(t.views||{}))try{const a=await import(`/plugins/${o.entry}`);l[e]=B(a[o.exportName],(t.meta||{})[e])}catch{}l.dashboard=O(l.dashboard);for(const[e,{route:o}]of Object.entries(t.takeovers||{}))o&&(D[`/${e}`]=o);for(const[e,{enabled:o,displayName:a}]of Object.entries(t.tools||{}))o===!1&&(y[`/${e}`]=a);for(const{id:e,href:o}of t.css||[])if(!document.getElementById(e)){const a=document.createElement("link");a.id=e,a.rel="stylesheet",a.href=o,document.head.appendChild(a)}}catch{}}function T(){const t=F();if(!t||$("#topbar-user-name").length)return;const e={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role,o=st(t);$("#topbar-user").html(`
1
+ import{views as K}from"./views/index.js";import{withPluginChrome as F}from"./lib/plugin-chrome.js";import{api as c,getUser as O,isAuthenticated as m,logout as J,sendToLogin as Y}from"./api.js";import{isAuthScreen as b,loginHashFor as Q}from"./lib/login-arrange.js";import{installHttpInterceptor as X}from"./http-interceptor.js";import{installResizableSlideovers as Z}from"./lib/slideover-resizable.js";import{installActivityThrobber as tt}from"./lib/activity-throbber.js";import{installUpdateNotice as et}from"./lib/update-notice.js";import{isPathAllowed as it,redirectFor as ot}from"./lib/admin-scope.js";import{deliverDesktop as at,desktopActive as st,notificationSettings as nt}from"./lib/desktop-notify.js";import{avatarUrl as lt}from"./lib/avatar.js";import{installCalculator as rt}from"./lib/calculator.js";import{takeOverviewPass as ct,withDashboardStrip as _}from"./lib/dashboards-strip.js";import{installHotkeys as mt}from"./lib/hotkeys-ui.js";import{applyAdminBrand as V}from"./lib/admin-brand.js";$(()=>{X(),rt(),et(),tt(),Z(),(async()=>{const{applyAdminTheme:t,refreshAdminTheme:i}=await import("./lib/admin-theme.js");m()?await i(c,{init:!0}):t({dommaTheme:"charcoal-dark"},{init:!0})})();let p=null,h=null,r="/",w=!1,g=null;function D(t){if(N[t])return E.toast(`${N[t]} is switched off on this site.`,{type:"info"}),"/";if(t.split("?")[0]==="/my-dashboard")return"/dashboards";const i=y[t]||ot(p,t);return i||((t.split("?")[0]||"/")==="/"&&r&&r!=="/"&&!ct()?y[r]||r:null)}const y={},N={};R.use(async(t,i,e)=>{if(b(t.path))return e();if(!m()){Y();return}const o=D(t.path);if(o){R.navigate(o);return}e()});async function f(){try{const t=await c.get("/auth/permissions");p=t.adminScope||null,h=t.adminBrand||null,V(h),r=t.adminHome||"/";const i=t.permissions||[];return w=it(p,"/settings")&&i.some(e=>e==="settings"||e.startsWith("settings.")),g=i.some(e=>e==="notifications"||e.startsWith("notifications.")),i}catch{return p=null,r="/",h||V(null),w=!1,g=null,[]}}document.addEventListener("dm:admin-home-changed",()=>{f()});let P=null,A=!1;document.addEventListener("dm:sidebar-rebuild",()=>{if(P){A=!0;return}P=(async()=>{do{A=!1;try{await j(await f())}catch{}}while(A)})().finally(()=>{P=null})});async function j(t){const{renderAdminSidebar:i}=await import("./lib/sidebar-renderer.js");await i({mount:"#admin-sidebar",permissions:t,adminScope:p,brand:h})}M.subscribe("router:afterChange",({to:t})=>{b(t.path)&&(u&&(clearInterval(u),u=null),p=null,r="/",w=!1,g=null)});const W=[{path:"/",view:"dashboard",title:"Dashboard - Domma CMS"},{path:"/dashboards",view:"dashboards",title:"Dashboards - Domma CMS"},{path:"/my-dashboard",view:"dashboards",title:"Dashboards - Domma CMS"},{path:"/pages",view:"pages",title:"Pages - Domma CMS"},{path:"/pages/new",view:"pageEditor",title:"New Page - Domma CMS"},{path:"/pages/edit/*",view:"pageEditor",title:"Edit Page - Domma CMS"},{path:"/media",view:"media",title:"Media - Domma CMS"},{path:"/menus",view:"menus",title:"Menus - Domma CMS"},{path:"/context-menus",view:"contextMenus",title:"Context Menus - Domma CMS"},{path:"/context-menus/new",view:"contextMenuEditor",title:"New Context Menu - Domma CMS"},{path:"/context-menus/edit/:slug",view:"contextMenuEditor",title:"Edit Context Menu - Domma CMS"},{path:"/menus/new",view:"menuEditor",title:"New Menu - Domma CMS"},{path:"/menus/edit/:slug",view:"menuEditor",title:"Edit Menu - Domma CMS"},{path:"/menu-locations",view:"menuLocations",title:"Menu Locations - Domma CMS"},{path:"/navigation",view:"menusRedirect",title:"Menus - Domma CMS"},{path:"/layouts",view:"layouts",title:"Layouts - Domma CMS"},{path:"/settings",view:"settings",title:"Settings - Domma CMS"},{path:"/theme",view:"theme",title:"Theme - Domma CMS"},{path:"/users",view:"users",title:"Users - Domma CMS"},{path:"/users/new",view:"userEditor",title:"New User - Domma CMS"},{path:"/users/edit/:id",view:"userEditor",title:"Edit User - Domma CMS"},{path:"/plugins",view:"plugins",title:"Plugins - Domma CMS"},{path:"/plugins/code/:name",view:"pluginCode",title:"Plugin Code - Domma CMS"},{path:"/documentation",view:"documentation",title:"Usage - Domma CMS"},{path:"/tutorials",view:"tutorials",title:"Tutorials - Domma CMS"},{path:"/tutorials/crud",view:"tutorialCrud",title:"Building a CRUD App - Domma CMS"},{path:"/tutorials/plugin",view:"tutorialPlugin",title:"Writing a Tool - Domma CMS"},{path:"/tutorials/forms",view:"tutorialForms",title:"Form Follow-Up - Domma CMS"},{path:"/docs/components",view:"componentsReference",title:"Components Reference - Domma CMS"},{path:"/docs/components-howto",view:"componentsHowto",title:"Components How-To - Domma CMS"},{path:"/docs/components-walkthrough",view:"componentsWalkthrough",title:"Components Walkthrough - Domma CMS"},{path:"/docs/components-rules",view:"componentsRules",title:"Components Rules - Domma CMS"},{path:"/docs/usage/pages",view:"usagePages",title:"Pages - Usage - Domma CMS"},{path:"/docs/usage/media",view:"usageMedia",title:"Media - Usage - Domma CMS"},{path:"/docs/usage/navigation",view:"usageNavigation",title:"Navigation - Usage - Domma CMS"},{path:"/docs/usage/dconfig",view:"usageDconfig",title:"DConfig - Usage - Domma CMS"},{path:"/docs/usage/shortcodes",view:"usageShortcodes",title:"Shortcodes - Usage - Domma CMS"},{path:"/docs/usage/site-settings",view:"usageSiteSettings",title:"Site Settings - Usage - Domma CMS"},{path:"/docs/usage/plugins",view:"usagePlugins",title:"Plugins - Usage - Domma CMS"},{path:"/docs/usage/users-roles",view:"usageUsersRoles",title:"Users & Roles - Usage - Domma CMS"},{path:"/docs/usage/views",view:"usageViews",title:"Views - Usage - Domma CMS"},{path:"/docs/usage/actions",view:"usageActions",title:"Actions - Usage - Domma CMS"},{path:"/docs/usage/cta-shortcode",view:"usageCtaShortcode",title:"CTA Shortcode - Usage - Domma CMS"},{path:"/docs/usage/editions",view:"usageEditions",title:"Editions & Licences - Usage - Domma CMS"},{path:"/docs/usage/collections",view:"usageCollections",title:"Collections & Forms - Usage - Domma CMS"},{path:"/docs/usage/blocks",view:"usageBlocks",title:"Blocks - Usage - Domma CMS"},{path:"/docs/usage/tools",view:"usageTools",title:"Built-in Tools - Usage - Domma CMS"},{path:"/docs/usage/dashboard",view:"usageDashboard",title:"Dashboard - Usage - Domma CMS"},{path:"/docs/plugins",view:"pluginGuides",title:"Plugin Guides - Domma CMS"},{path:"/docs/plugins/*",view:"pluginGuide",title:"Plugin Guide - Domma CMS"},{path:"/docs/api/authentication",view:"apiRefAuthentication",title:"Authentication - API - Domma CMS"},{path:"/docs/api/pages",view:"apiRefPages",title:"Pages - API - Domma CMS"},{path:"/docs/api/settings",view:"apiRefSettings",title:"Settings - API - Domma CMS"},{path:"/docs/api/layouts",view:"apiRefLayouts",title:"Layouts - API - Domma CMS"},{path:"/docs/api/navigation",view:"apiRefNavigation",title:"Navigation - API - Domma CMS"},{path:"/docs/api/media",view:"apiRefMedia",title:"Media - API - Domma CMS"},{path:"/docs/api/users",view:"apiRefUsers",title:"Users - API - Domma CMS"},{path:"/docs/api/plugins",view:"apiRefPlugins",title:"Plugins - API - Domma CMS"},{path:"/docs/api/collections",view:"apiRefCollections",title:"Collections - API - Domma CMS"},{path:"/docs/api/views",view:"apiRefViews",title:"Views API - Domma CMS"},{path:"/docs/api/actions",view:"apiRefActions",title:"Actions API - Domma CMS"},{path:"/docs/api/forms",view:"apiRefForms",title:"Forms API - Domma CMS"},{path:"/docs/api/scaffold",view:"apiRefScaffold",title:"Scaffold API - Domma CMS"},{path:"/docs/api/external",view:"apiRefExternal",title:"External API & Tokens - Domma CMS"},{path:"/docs/api/content",view:"apiRefContent",title:"Content API - Domma CMS"},{path:"/docs/api/builder",view:"apiRefBuilder",title:"API Builder - Domma CMS"},{path:"/api-reference",view:"apiReference",title:"API Reference - Domma CMS"},{path:"/collections",view:"collections",title:"Collections - Domma CMS"},{path:"/collections/new",view:"collectionEditor",title:"New Collection - Domma CMS"},{path:"/collections/edit/:slug",view:"collectionEditor",title:"Edit Collection - Domma CMS"},{path:"/collections/:slug/entries",view:"collectionEntries",title:"Entries - Domma CMS"},{path:"/forms",view:"forms",title:"Forms - Domma CMS"},{path:"/forms/new",view:"formEditor",title:"New Form - Domma CMS"},{path:"/forms/edit/:slug",view:"formEditor",title:"Edit Form - Domma CMS"},{path:"/forms/:slug/submissions",view:"formSubmissions",title:"Submissions - Domma CMS"},{path:"/views",view:"viewsList",title:"Views - Domma CMS"},{path:"/views/new",view:"viewEditor",title:"New View - Domma CMS"},{path:"/views/edit/:slug",view:"viewEditor",title:"Edit View - Domma CMS"},{path:"/views/:slug/preview",view:"viewPreview",title:"View Preview - Domma CMS"},{path:"/actions",view:"actionsList",title:"Actions - Domma CMS"},{path:"/actions/new",view:"actionEditor",title:"New Action - Domma CMS"},{path:"/actions/edit/:slug",view:"actionEditor",title:"Edit Action - Domma CMS"},{path:"/pro/docs",view:"proDocs",title:"Pro Documentation - Domma CMS"},{path:"/blocks",view:"blocks",title:"Blocks - Domma CMS"},{path:"/blocks/new",view:"blockEditor",title:"New Block - Domma CMS"},{path:"/blocks/edit/:name",view:"blockEditor",title:"Edit Block - Domma CMS"},{path:"/components",view:"components",title:"Components - Domma CMS"},{path:"/components/new",view:"componentEditor",title:"New Component - Domma CMS"},{path:"/components/edit/:name",view:"componentEditor",title:"Edit Component - Domma CMS"},{path:"/my-profile",view:"myProfile",title:"My Profile - Domma CMS"},{path:"/roles",view:"roles",title:"Roles & Permissions - Domma CMS"},{path:"/roles/edit/:id",view:"roleEditor",title:"Edit Role - Domma CMS"},{path:"/effects",view:"effects",title:"Effects - Domma CMS"},{path:"/search",view:"search",title:"Search - Domma CMS"},{path:"/content-api",view:"contentApi",title:"Content API - Domma CMS"},{path:"/notes",view:"notes",title:"Notes - Domma CMS"},{path:"/todo",view:"todo",title:"Todo - Domma CMS"},{path:"/calculator",view:"calculator",title:"Calculator - Domma CMS"},{path:"/analytics",view:"analytics",title:"Analytics - Domma CMS"},{path:"/seo",view:"seo",title:"SEO - Domma CMS"},{path:"/seo/:tab",view:"seo",title:"SEO - Domma CMS"},{path:"/contacts",view:"contacts",title:"Contacts - Domma CMS"},{path:"/calls/new",view:"callNew",title:"Call now - Domma CMS"},{path:"/system/notifications",view:"notifications",title:"Notifications - Domma CMS"},{path:"/api-tokens",view:"apiTokens",title:"API Tokens - Domma CMS"},{path:"/api-endpoints",view:"apiEndpoints",title:"API Builder - Domma CMS"},{path:"/api-endpoints/new",view:"apiEndpointEditor",title:"New API Endpoint - Domma CMS"},{path:"/api-endpoints/edit/:id",view:"apiEndpointEditor",title:"Edit API Endpoint - Domma CMS"},{path:"/projects",view:"projects",title:"Projects - Domma CMS"},{path:"/projects/new",view:"projects",title:"New Project - Domma CMS"},{path:"/projects/edit/:slug",view:"projects",title:"Edit Project - Domma CMS"},{path:"/projects/:slug/settings",view:"projectSettings",title:"Project settings - Domma CMS"},{path:"/projects/:slug/pages",view:"pages",title:"Project pages - Domma CMS"},{path:"/projects/:slug/collections",view:"collections",title:"Project collections - Domma CMS"},{path:"/projects/:slug/forms",view:"forms",title:"Project forms - Domma CMS"},{path:"/projects/:slug/actions",view:"actionsList",title:"Project actions - Domma CMS"},{path:"/projects/:slug/menus",view:"menus",title:"Project menus - Domma CMS"},{path:"/projects/:slug/blocks",view:"blocks",title:"Project blocks - Domma CMS"},{path:"/projects/:slug/components",view:"components",title:"Project components - Domma CMS"},{path:"/projects/:slug/views",view:"viewsList",title:"Project views - Domma CMS"},{path:"/projects/:slug/roles",view:"roles",title:"Project roles - Domma CMS"},{path:"/projects/:slug/users",view:"users",title:"Project users - Domma CMS"},{path:"/projects/:slug/apis",view:"apiEndpoints",title:"Project APIs - Domma CMS"},{path:"/projects/:slug",view:"projectDetail",title:"Project - Domma CMS"},{path:"/login",view:"login",title:"Sign in - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}},{path:"/reset-password",view:"login",title:"Reset Password - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}}];M.subscribe("router:afterChange",async({to:t,from:i})=>{if(!b(t.path)){if($("#admin-sidebar").show(),$("#admin-topbar").show(),b(i?.path)){$("#topbar-user-name").remove(),import("./lib/admin-theme.js").then(a=>a.refreshAdminTheme(c)),await U();const e=await f(),o=D(t.path);if(o&&R.navigate(o),j(e),nt.read().signInToasts)try{const a=await c.system.notifications.list().catch(()=>[]),n=(Array.isArray(a)?a:[]).filter(s=>s.unread&&["warning","critical"].includes(s.data?.severity)).slice(0,3);for(const s of n){const v=d(s.data?.title||""),T=d((s.data?.body||"").slice(0,120));E.toast(`${v} - ${T}`,{type:s.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}L()}}),M.subscribe("router:afterChange",()=>{setTimeout(()=>{$(".btn-primary, .btn-danger").length&&Domma.effects.reveal(".btn-primary, .btn-danger",{animation:"fade",stagger:40,duration:300})},50)});const H=["#field-project","#menu-project","#collection-project","#endpoint-project","#action-project","#view-project","#block-project","#component-project","#role-project",'[name="project"]','[name="ownedByProject"]'];function q(t){return/^\/(pages|menus|context-menus|collections|forms|actions|views|blocks|components|users|roles|api-endpoints)\/(new|edit\/)/.test(t)}M.subscribe("router:afterChange",({to:t})=>{const i=t?String(t.path).split("?")[0]:"";if(!t||!q(i))return;const o=new URLSearchParams(location.hash.split("?")[1]||"").get("project");if(!o||!/\/new$/.test(i))return;let a=0;const n=60,s=1500,v=()=>{a+=n;for(const T of H){const C=document.querySelector(T);if(!C)continue;if(Array.from(C.options||[]).find(z=>z.value===o)){C.value=o,C.dispatchEvent(new Event("change",{bubbles:!0}));return}}a<s&&setTimeout(v,n)};setTimeout(v,n)});const x="cms_card_states",k=S.get(x)||{};M.subscribe("router:afterChange",()=>{setTimeout(()=>{$("#view-container .card-collapsible").each(function(){const t=$(this).find(".card-header h2, .card-header h3").first().text().trim();t&&k[t]==="collapsed"&&$(this).addClass("card-collapsed")})},200)}),$("#view-container").on("click",".card-collapsible .card-header",function(t){if($(t.target).closest("button, a").length)return;const i=$(this).closest(".card");i.toggleClass("card-collapsed");const e=$(this).find("h2, h3").first().text().trim();e&&(k[e]=i.hasClass("card-collapsed")?"collapsed":"open",S.set(x,k))}),mt(),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash.startsWith("#/login")||window.location.hash.startsWith("#/reset-password"))return;const i=document.querySelector("#view-container .view-header button.btn-primary");i&&(t.preventDefault(),i.click())});const l={...K},G={notes:{displayName:"Notes",icon:"edit-3"},todo:{displayName:"Todo",icon:"check-square"},calculator:{displayName:"Calculator",icon:"calculator"},analytics:{displayName:"Analytics",icon:"chart-bar"},seo:{displayName:"SEO",icon:"search"},contacts:{displayName:"Contacts",icon:"users"},projects:{displayName:"Projects",icon:"folder"},pages:{displayName:"Pages",icon:"file-text"},media:{displayName:"Media",icon:"image"},menus:{displayName:"Site menus",icon:"menu"},contextMenus:{displayName:"Context Menus",icon:"menu"},theme:{displayName:"Theme",icon:"droplet"},collections:{displayName:"Collections",icon:"database"},forms:{displayName:"Forms",icon:"layout"},viewsList:{displayName:"Views",icon:"eye"},actionsList:{displayName:"Actions",icon:"zap"},apiEndpoints:{displayName:"API Builder",icon:"code"},blocks:{displayName:"Blocks",icon:"box"},components:{displayName:"Components",icon:"component"},notifications:{displayName:"Notifications",icon:"bell"},roles:{displayName:"Roles",icon:"shield"},users:{displayName:"Users",icon:"users"},settings:{displayName:"Site Settings",icon:"settings"},effects:{displayName:"Effects",icon:"sparkles"},layouts:{displayName:"Layouts",icon:"layout"},plugins:{displayName:"Marketplace",icon:"store"},myProfile:{displayName:"My Profile",icon:"user"},apiTokens:{displayName:"API Tokens",icon:"key"},search:{displayName:"Search",icon:"search"},contentApi:{displayName:"Content API",icon:"file-text"},dashboard:{displayName:"Dashboard",icon:"home"},dashboards:{displayName:"Dashboards",icon:"home"},pageEditor:{displayName:"Pages",section:"Edit",icon:"file-text"},menuEditor:{displayName:"Site menus",section:"Edit",icon:"menu"},menuLocations:{displayName:"Site menus",section:"Locations",icon:"map-pin"},contextMenuEditor:{displayName:"Context Menus",section:"Edit",icon:"menu"},collectionEditor:{displayName:"Collections",section:"Edit",icon:"database"},collectionEntries:{displayName:"Collections",section:"Entries",icon:"database"},formEditor:{displayName:"Forms",section:"Edit",icon:"layout"},formSubmissions:{displayName:"Forms",section:"Submissions",icon:"inbox"},viewEditor:{displayName:"Views",section:"Edit",icon:"eye"},viewPreview:{displayName:"Views",section:"Preview",icon:"eye"},actionEditor:{displayName:"Actions",section:"Edit",icon:"zap"},apiEndpointEditor:{displayName:"API Builder",section:"Edit",icon:"code"},blockEditor:{displayName:"Blocks",section:"Edit",icon:"box"},componentEditor:{displayName:"Components",section:"Edit",icon:"component"},roleEditor:{displayName:"Roles",section:"Edit",icon:"shield"},userEditor:{displayName:"Users",section:"Edit",icon:"users"},projectDetail:{displayName:"Projects",section:"Overview",icon:"folder"},projectSettings:{displayName:"Projects",section:"Settings",icon:"folder"},pluginCode:{displayName:"Marketplace",section:"Code",icon:"code"}};for(const[t,i]of Object.entries(G))l[t]&&(l[t]=F(l[t],{plugin:t,licence:"Built in",...i}));l.dashboard=_(l.dashboard);const I=[...W];async function U(){if(m())try{const t=await c.plugins.adminConfig(),i=new Set(I.map(e=>e.path));for(const e of t.routes||[])i.has(e.path)||I.push(e);for(const[e,o]of Object.entries(t.views||{}))try{const a=await import(`/plugins/${o.entry}`);l[e]=F(a[o.exportName],(t.meta||{})[e])}catch{}l.dashboard=_(l.dashboard);for(const[e,{route:o}]of Object.entries(t.takeovers||{}))o&&(y[`/${e}`]=o);for(const[e,{enabled:o,displayName:a}]of Object.entries(t.tools||{}))o===!1&&(N[`/${e}`]=a);for(const{id:e,href:o}of t.css||[])if(!document.getElementById(e)){const a=document.createElement("link");a.id=e,a.rel="stylesheet",a.href=o,document.head.appendChild(a)}}catch{}}function L(){const t=O();if(!t||$("#topbar-user-name").length)return;const e={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role,o=lt(t);$("#topbar-user").html(`
2
2
  ${o?`<img id="topbar-avatar" class="topbar-avatar" src="${d(o)}" alt="">`:""}
3
3
  <span id="topbar-user-name" class="topbar-user-name">${d(t.name)}</span>
4
4
  <span class="topbar-role-badge topbar-role-badge--${d(t.role)}">${e}</span>
@@ -20,4 +20,4 @@ import{views as G}from"./views/index.js";import{withPluginChrome as B}from"./lib
20
20
  <span data-icon="log-out"></span>
21
21
  <span>Sign out</span>
22
22
  </a>
23
- `);const a=$("#topbar-bell").get(0);a&&(a.hidden=!0);const n=$("#topbar-settings").get(0);n&&(n.hidden=!h),$("#topbar-logout-btn").on("click",s=>{s.preventDefault(),z()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"}),L(),u||(u=setInterval(L,6e4))}function d(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}let u=null;async function L(){if(!(!m()||w===!1))try{const{count:t}=await c.system.notifications.unreadCount();$("#topbar-bell").removeAttr("hidden");const i=$("#topbar-bell-badge");t>0?i.text(t>99?"99+":String(t)).removeAttr("hidden"):i.attr("hidden","").text(""),t>0&&ot()&&it(await c.system.notifications.list().catch(()=>[]))}catch{}}(async()=>{if(!m()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash.startsWith("#/login")||(window.location.hash=J(window.location.hash));else if(await I(),F()){const a=await g(),n=(window.location.hash||"#/").slice(1)||"/",s=b(n);s&&(window.location.hash=`#${s}`),await A(a),T()}const t=R._extractParams.bind(R);R._extractParams=function(o,a){const n=a.indexOf("?");if(n!==-1&&(a=a.slice(0,n)),o.endsWith("/*")){const s=o.slice(0,-2);return a.startsWith(s+"/")?{}:null}return t(o,a)};const i=R._renderNotFound.bind(R);let e="";R._renderNotFound=async function(o){return m()&&e!==o&&(e=o,await I(),R._matchRoute(o).route)?(e="",R._handleRouteChange()):(e="",m()&&$("#admin-sidebar").get(0)?.style.display==="none"?(E.toast("That link goes nowhere - here is your home page.",{type:"warning"}),R.navigate("/")):i(o))},R.init({container:"#view-container",routes:k,views:l,default:"/",transitions:{enter:"fadeIn",leave:"fadeOut",duration:150}})})()});
23
+ `);const a=$("#topbar-bell").get(0);a&&(a.hidden=!0);const n=$("#topbar-settings").get(0);n&&(n.hidden=!w),$("#topbar-logout-btn").on("click",s=>{s.preventDefault(),J()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"}),B(),u||(u=setInterval(B,6e4))}function d(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}let u=null;async function B(){if(!(!m()||g===!1))try{const{count:t}=await c.system.notifications.unreadCount();$("#topbar-bell").removeAttr("hidden");const i=$("#topbar-bell-badge");t>0?i.text(t>99?"99+":String(t)).removeAttr("hidden"):i.attr("hidden","").text(""),t>0&&st()&&at(await c.system.notifications.list().catch(()=>[]))}catch{}}(async()=>{if(!m()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash.startsWith("#/login")||(window.location.hash=Q(window.location.hash));else if(await U(),O()){const a=await f(),n=(window.location.hash||"#/").slice(1)||"/",s=D(n);s&&(window.location.hash=`#${s}`),await j(a),L()}const t=R._extractParams.bind(R);R._extractParams=function(o,a){const n=a.indexOf("?");if(n!==-1&&(a=a.slice(0,n)),o.endsWith("/*")){const s=o.slice(0,-2);return a.startsWith(s+"/")?{}:null}return t(o,a)};const i=R._renderNotFound.bind(R);let e="";R._renderNotFound=async function(o){return m()&&e!==o&&(e=o,await U(),R._matchRoute(o).route)?(e="",R._handleRouteChange()):(e="",m()&&$("#admin-sidebar").get(0)?.style.display==="none"?(E.toast("That link goes nowhere - here is your home page.",{type:"warning"}),R.navigate("/")):i(o))},R.init({container:"#view-container",routes:I,views:l,default:"/",transitions:{enter:"fadeIn",leave:"fadeOut",duration:150}})})()});
@@ -0,0 +1 @@
1
+ export const PRODUCT_NAME="Domma CMS";let n=null,s="",u=null;export function currentAdminBrand(){return n}export function brandedTabTitle(t,c,i=""){const o=l=>String(l||"").replace(/[ \t\n\f\r]+/g," ").trim(),e=o(c?.title),r=o(t);if(!e)return String(t||"");const m=l=>l&&(r===l||r.endsWith(` - ${l}`)),a=[e,o(i),PRODUCT_NAME].filter(m).sort((l,f)=>f.length-l.length)[0];return!a||a===e?String(t||""):r===a?e:r.slice(0,-a.length)+e}export function applyAdminBrand(t){const c=t&&t.title?t:{title:PRODUCT_NAME};n&&n.title!==c.title&&(s=n.title,u=null),n=c;const i=document.querySelector("#admin-topbar .topbar-brand");if(i){const o=String(n.logo||"");let e;/^(?:\/(?!\/)|https?:\/\/)/i.test(o)?(e=document.createElement("img"),e.src=o,e.alt="",e.className="topbar-brand-logo"):(e=document.createElement("span"),e.setAttribute("data-icon",n.icon||"layout"));const r=document.createElement("span");r.className="topbar-brand-text",r.textContent=n.title,i.replaceChildren(e,r);try{I.scan(i)}catch{}}p(),retitleTab()}export function retitleTab(){if(!n||u!==null&&document.title===u)return;const t=brandedTabTitle(document.title,n,s);t!==document.title&&(document.title=t,u=document.title)}let d=!1;function p(){if(d||typeof MutationObserver>"u")return;let t=document.querySelector("head > title");t||(t=document.createElement("title"),document.head.appendChild(t)),new MutationObserver(retitleTab).observe(t,{childList:!0,characterData:!0,subtree:!0}),d=!0}
@@ -1,7 +1,7 @@
1
- import{api as g,getUser as B}from"../api.js";import{isPathAllowed as pe}from"./admin-scope.js";import{colourToCss as Y,applyFloat as he,applyHighlight as ge,itemHighlightAttrs as be}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{placeTools as ye,ensureLockedTools as ve,managePluginsItem as xe,stripItemByUrl as $e,insertFoldersBeforeSystem as we,pruneEmptyToolsFolder as ke,sidebarStateKey as J,canAny as U,filterByPermission as Se,MANAGE_PLUGINS_URL as Q}from"./sidebar-grouping.js";import{arrangeButtonMarkup as qe,canArrange as Ce,startArrange as Ae}from"./sidebar-arrange.js";import{collapseButtonMarkup as Fe,wireSidebarSize as Le,COLLAPSED_WIDTH as Ee}from"./sidebar-size.js";import{FREQUENT_MAX as ee,addFrequent as Te,frequentFolder as Be,moveFrequent as te,readFrequent as W,readFrequentHidden as He,removeFrequent as Me,saveFrequent as je,syncFrequent as Ne,writeFrequent as Pe,writeFrequentHidden as ne}from"./sidebar-frequent.js";import{hotkeyFor as se,hotkeyTitle as Re,setHotkeyTree as _e}from"./hotkeys-ui.js";import{hotkeyLabel as Ie}from"./hotkeys.js";function f(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const Oe=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Site menus",url:"#/menus",icon:"menu",permission:"menus"}];async function Ue(e){if(!U(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:"Site 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 ze(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 oe(e,n){const s=ae.includes(n.tone)?n.tone:"info",t=n.label||String(n.count);n.details?P.set(e,n.details):P.delete(e);const i=!n.inventory&&F.some(o=>o.url===e&&!o.inventory)?" data-live":"";return`<span class="sidebar-badge sidebar-badge--${s}" data-count-for="${f(e)}"${i} data-badge-label="${f(t)}" aria-label="${f(t)}">${f(String(n.count))}</span>`}function ie(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 m=Number(r.textContent)||0;m&&(t+=m,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(),P.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),P.set(a,{title:u,items:o})}}const P=new Map;function We(e,n){if(!e.url||!n)return"";const s=n[e.url];return!s||!(s.count>0)&&!s.showZero?"":oe(e.url,s)}const ae=["info","attention","danger","live"],De={"#/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 D(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:ae.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 re(e,n,s=null,{inFrequent:t=!1}={}){if(e&&e.type==="separator")return'<hr class="sidebar-divider">';if(e&&e.type==="theme-switcher")return"";if(e&&e.type==="spacer"){const b=["xs","sm","md","lg","xl","flex"].includes(e.size)?e.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${f(b)}" aria-hidden="true"></div>`}const i=String(e.text||"?").trim().charAt(0).toUpperCase()||"?",o=e.icon?`<span data-icon="${f(e.icon)}"></span>`:`<span class="sidebar-initial" aria-hidden="true">${f(i)}</span>`,a=f(e.text||""),d=e.url?f(e.url):"",u=Array.isArray(e.items)&&e.items.length>0,c=e.colour?Y(e.colour):"",r=be(e),m=[r.hover&&` data-menu-item-hover="${f(r.hover)}"`,r.active&&` data-menu-item-active="${f(r.active)}"`].filter(Boolean).join(""),y=[c&&`color:${f(c)}`,r.accent&&`--dm-menu-highlight:${f(r.accent)}`].filter(Boolean).join(";"),v=y?` style="${y}"`:"",k=e.url?Re(e.url,e.text||"",{inFrequent:t}):"",w=k?` title="${f(k)}"`:"";let L="";if(e.badge&&e.badge.text!=null&&e.badge.text!==""){const b=e.badge.variant?Y(e.badge.variant):"";L=`<span class="dm-menu-badge"${b?` style="background:${f(b)};color:var(--dm-text-inverse,#fff)"`:""}>${f(String(e.badge.text))}</span>`}if(e.frequent&&e.empty){const b=J(e,s),A=S.get(b)!==!1;return`<details data-state-key="${f(b)}"${A?" open":""} data-frequent data-frequent-empty>
2
- <summary${v}>${o} <span class="sidebar-text">${a}</span></summary>
1
+ import{api as g,getUser as T}from"../api.js";import{isPathAllowed as he}from"./admin-scope.js";import{colourToCss as J,applyFloat as ge,applyHighlight as be,itemHighlightAttrs as ye}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{placeTools as ve,ensureLockedTools as xe,managePluginsItem as $e,stripItemByUrl as we,insertFoldersBeforeSystem as Se,pruneEmptyToolsFolder as ke,sidebarStateKey as Q,canAny as z,filterByPermission as qe,MANAGE_PLUGINS_URL as ee}from"./sidebar-grouping.js";import{arrangeButtonMarkup as Ce,canArrange as Ae,startArrange as Fe}from"./sidebar-arrange.js";import{collapseButtonMarkup as Le,wireSidebarSize as Ee,COLLAPSED_WIDTH as Me}from"./sidebar-size.js";import{FREQUENT_MAX as te,addFrequent as Te,frequentFolder as Be,moveFrequent as ne,readFrequent as G,readFrequentHidden as He,removeFrequent as je,saveFrequent as Ne,syncFrequent as Pe,writeFrequent as Re,writeFrequentHidden as se}from"./sidebar-frequent.js";import{hotkeyFor as oe,hotkeyTitle as _e,setHotkeyTree as Ie}from"./hotkeys-ui.js";import{hotkeyLabel as Oe}from"./hotkeys.js";function f(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const Ue=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Site menus",url:"#/menus",icon:"menu",permission:"menus"}];async function ze(e){if(!z(e,"projects"))return[];let t=[];try{t=await g.projects.listWithCounts()}catch{return[]}if(!Array.isArray(t))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:"Site 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 t.map(o=>{const d="#/projects/"+encodeURIComponent(o.slug),i=o.counts||{},c=[{text:"Overview",url:d,icon:o.icon||"folder"}];for(const r of s){const a=Number(i[r.key])||0;a<=0||c.push({text:`${r.text} (${a})`,url:d+r.path,icon:r.icon})}c.push({text:"Settings",url:d+"/settings",icon:"settings"});const u=o.enabled===!1;return{text:o.name||o.slug,key:`project.${o.slug}`,icon:o.icon||"folder",items:c,...u&&{colour:"neutral",badge:{text:"off"}}}})}function We(e,t){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(...t);return}}function ie(e,t){const s=re.includes(t.tone)?t.tone:"info",o=t.label||String(t.count);t.details?R.set(e,t.details):R.delete(e);const n=!t.inventory&&F.some(d=>d.url===e&&!d.inventory)?" data-live":"";return`<span class="sidebar-badge sidebar-badge--${s}" data-count-for="${f(e)}"${n} data-badge-label="${f(o)}" aria-label="${f(o)}">${f(String(t.count))}</span>`}function ae(e){for(const t of e.querySelectorAll("details")){const s=t.querySelector(":scope > summary");if(!s)continue;let o=0,n=!1;const d=[];for(const a of t.querySelectorAll(":scope > .sidebar-children .sidebar-badge[data-live]")){const m=Number(a.textContent)||0;m&&(o+=m,n||(n=a.classList.contains("sidebar-badge--danger")),d.push({text:a.closest("a")?.querySelector(".sidebar-text")?.textContent.trim()??"",meta:a.dataset.badgeLabel,tone:a.classList.contains("sidebar-badge--danger")?"danger":void 0}))}const i=`folder:${t.dataset.stateKey||s.textContent.trim()}`;let c=s.querySelector(":scope > .sidebar-badge--sum");if(!o){c?.remove(),R.delete(i);continue}c||(c=document.createElement("span"),s.querySelector(".sidebar-text")?.after(c));const u=s.querySelector(".sidebar-text")?.textContent.trim()||"This folder",r=d.map(a=>`${a.text}: ${a.meta}`).join(" \xB7 ");c.className=`sidebar-badge sidebar-badge--sum sidebar-badge--${n?"danger":"info"}`,c.dataset.countFor=i,c.dataset.badgeLabel=r,c.setAttribute("aria-label",r),c.textContent=String(o),R.set(i,{title:u,items:d})}}const R=new Map;function De(e,t){if(!e.url||!t)return"";const s=t[e.url];return!s||!(s.count>0)&&!s.showZero?"":ie(e.url,s)}const re=["info","attention","danger","live"],Ge={"#/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 K(e,{fallbackLabel:t="",tone:s="info"}={}){const o=Number(typeof e=="object"&&e!==null?e.count:e);if(!Number.isFinite(o)||o<0)return null;const n=typeof e?.label=="string"&&e.label||(t?`${o} - ${t}`:String(o)),d={count:Math.round(o),label:n,tone:re.includes(e?.tone)?e.tone:s};return e?.details&&typeof e.details=="object"&&(d.details=e.details),e?.inventory===!0&&(d.inventory=!0),e?.showZero===!0&&(d.showZero=!0),d}function le(e,t,s=null,{inFrequent:o=!1}={}){if(e&&e.type==="separator")return'<hr class="sidebar-divider">';if(e&&e.type==="theme-switcher")return"";if(e&&e.type==="spacer"){const y=["xs","sm","md","lg","xl","flex"].includes(e.size)?e.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${f(y)}" aria-hidden="true"></div>`}const n=String(e.text||"?").trim().charAt(0).toUpperCase()||"?",d=e.icon?`<span data-icon="${f(e.icon)}"></span>`:`<span class="sidebar-initial" aria-hidden="true">${f(n)}</span>`,i=f(e.text||""),c=e.url?f(e.url):"",u=Array.isArray(e.items)&&e.items.length>0,r=e.colour?J(e.colour):"",a=ye(e),m=[a.hover&&` data-menu-item-hover="${f(a.hover)}"`,a.active&&` data-menu-item-active="${f(a.active)}"`].filter(Boolean).join(""),b=[r&&`color:${f(r)}`,a.accent&&`--dm-menu-highlight:${f(a.accent)}`].filter(Boolean).join(";"),v=b?` style="${b}"`:"",w=e.url?_e(e.url,e.text||"",{inFrequent:o}):"",k=w?` title="${f(w)}"`:"";let A="";if(e.badge&&e.badge.text!=null&&e.badge.text!==""){const y=e.badge.variant?J(e.badge.variant):"";A=`<span class="dm-menu-badge"${y?` style="background:${f(y)};color:var(--dm-text-inverse,#fff)"`:""}>${f(String(e.badge.text))}</span>`}if(e.frequent&&e.empty){const y=Q(e,s),C=S.get(y)!==!1;return`<details data-state-key="${f(y)}"${C?" open":""} data-frequent data-frequent-empty>
2
+ <summary${v}>${d} <span class="sidebar-text">${i}</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=J(e,s),A=S.get(b)!==!1,R=e.items.map(T=>re(T,n,b,{inFrequent:!!e.frequent})).join(""),j=e.frequent?' data-frequent title="Your Frequent Tools - right-click any Tool to add or remove it"':"";return`<details data-state-key="${f(b)}"${A?" open":""}${j}>
5
- <summary${v}${w}>${o} <span class="sidebar-text">${a}</span>${L}</summary>
6
- <div class="sidebar-children">${R}</div>
7
- </details>`}return`<a href="${d}" class="sidebar-link" data-url="${d}"${v}${m}${w}>${o} <span class="sidebar-text">${a}</span>${We(e,n)}${L}</a>`}async function Ge(e=[],n=[],s=null){const t=p=>p.then(x=>Array.isArray(x)?x.length:Array.isArray(x?.entries)?x.entries.length:0).catch(()=>0);F=Xe(e,s);const i=new Set(F.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,x)=>U(n,p)&&!i.has(o[p])?x():Promise.resolve(0),[d,u,c,r,m,y,v,k,w,L,b,A,R,j]=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(F.map(async p=>{const x=await le(p);x&&(T[p.url]=x)}));const X={"#/pages":d,"#/media":u,"#/menus":c,"#/context-menus":r,"#/theme":m,"#/collections":y,"#/forms":v,"#/views":k,"#/actions":w,"#/blocks":L,"#/components":b,"#/users":A,"#/plugins":R,"#/system/notifications":j},I={};for(const[p,x]of Object.entries(X)){if(i.has(p))continue;const[O,z]=De[p]??["item","items"],q=D(x);q&&(I[p]={...q,label:`${q.count} ${q.count===1?O:z}`})}return{...I,...T}}let F=[];const Ke="dm:sidebar-count",Ve=6e4;function Xe(e=[],n=null){const s=[],t=i=>{if(i&&!(n&&typeof i.url=="string"&&i.url.startsWith("#/")&&!pe(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 le(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 D(await H.get(t),{fallbackLabel:e.countLabel||e.text})}catch{return null}}function ce(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(oe(n,s)).firstElementChild;o?o.replaceWith(d):i.querySelector(".sidebar-text")?.after(d)}}G.set(n,s),ie(e)}}const G=new Map;async function K(e,n=null){const s=n?F.filter(t=>t.url===n):F;await Promise.all(s.map(async t=>{const i=await le(t),o=G.get(t.url);JSON.stringify(o??null)!==JSON.stringify(i)&&ce(e,t.url,i)}))}function Ze(e){e.dataset.liveCounts||(e.dataset.liveCounts="1",window.addEventListener(Ke,n=>{const s=n.detail||{};if(s.url)if(s.count!==void 0){const t=F.find(i=>i.url===s.url);ce(e,s.url,D(s,{fallbackLabel:t?.countLabel||t?.text||""}))}else K(e,s.url)}),setInterval(()=>{!document.hidden&&document.body.contains(e)&&K(e)},Ve),document.addEventListener("visibilitychange",()=>{document.hidden||K(e)}),Ye(e))}function de(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 Ye(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 m=c.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=c=>{const r=c?.dataset.badgeLabel;if(!r)return i();o(c,m=>de(m,r,P.get(c.dataset.countFor)))},d=c=>{const r=c.querySelector(":scope > .sidebar-text")?.textContent.trim()||"",m=c.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");o(c,y=>{const v=m&&P.get(m.dataset.countFor);if(v)return de(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 w=document.createElement("p");w.className="dm-badge-pop-sub",w.textContent=m.dataset.badgeLabel,y.append(k,w)})},u=c=>{const r=c.closest?.("a.sidebar-link, summary");return r&&e.contains(r)?r:null};e.addEventListener("mouseover",c=>{if(t()){const m=u(c.target);m&&!m.contains(c.relatedTarget)&&d(m);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 m=r?.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");m?a(m):i()}),e.addEventListener("focusout",i),e.addEventListener("click",i),window.addEventListener("scroll",i,!0),window.addEventListener("hashchange",i)}async function Je(e=[]){if(!U(e,"settings"))return{title:"Admin"};try{const n=await g.settings.get();return{...n?.adminBrand,title:n?.adminBrand?.title||n?.title||"Admin"}}catch{return{title:"Admin"}}}export async function renderAdminSidebar({mount:e,permissions:n,adminScope:s=null}){const t=$(e).get(0);if(!t)return;const i=Ne(g,B()).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=Oe.slice());let m=[];try{m=await H.get("/api/sidebar/registered-items")||[]}catch{}o=$e(o,Q),o=ye(o,m.filter(l=>(l?.item??l)?.url!==Q)),o=ve(o),o=we(o,[xe()]);const y=await Ue(n);y.length&&ze(o,y),o=Se(o,n,s),o=ke(o),await i;const v=Be(o,W(B()),{hidden:He(B())});v&&(o=[v,...o]),_e(o);const[k,w]=await Promise.all([Ge(m.filter(l=>U(n,l?.item?.permission)),n,s).catch(()=>({})),Je(n)]),L=a?` dm-admin-sidebar--${f(a)}`:"";function b(l,C="px"){if(l==null)return l;const h=String(l).trim();return/^\d+(\.\d+)?$/.test(h)?`${h}${C}`:h}let A="";if(u){const l=[];u.fontFamily&&l.push(`font-family: ${u.fontFamily}, sans-serif`),u.fontSize&&l.push(`font-size: ${b(u.fontSize)}`),u.fontWeight&&l.push(`font-weight: ${u.fontWeight}`),u.letterSpacing&&l.push(`letter-spacing: ${b(u.letterSpacing,"em")}`);const C=[];if(l.length&&C.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=b(u.iconSize);C.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${h} !important; height: ${h} !important; }`)}C.length&&(A=`<style data-admin-sidebar-style>${C.join(" ")}</style>`)}const R=Ce(n)?qe():"",j=String(w.logo||"").trim(),T=/^(?:\/(?!\/)|https?:\/\/)/i.test(j)?j:"",I=`<div class="dm-admin-sidebar-header">${T?`<img src="${f(T)}" alt="" style="width:28px;height:28px;object-fit:contain;flex-shrink:0;">`:`<span data-icon="${f(w.icon||"layout")}"></span>`}<span class="dm-admin-sidebar-title">${f(w.title)}</span>${R}${Fe()}</div>`,p=o.some(l=>l&&l.type==="spacer"&&l.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",x=`${A}<nav class="dm-admin-sidebar${L}${p}">${I}${o.map(l=>re(l,k)).join("")}</nav>`,O=document.createRange();O.selectNodeContents(t);const z=O.createContextualFragment(x);t.replaceChildren(z),Domma.icons.scan(t),t.querySelector(".dm-admin-sidebar-arrange")?.addEventListener("click",()=>Ae(t));for(const l of F)G.set(l.url,k[l.url]??null);ie(t),Ze(t),t.removeAttribute("data-menu-hover"),t.removeAttribute("data-menu-active"),t.style.removeProperty("--dm-menu-highlight"),ge(t,{highlight:r});const q=document.querySelector(".dashboard-wrapper");["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(l=>{t.style[l]=""}),t.classList.remove("dm-menu-floating"),q&&(q.style.paddingLeft="",q.style.paddingRight=""),t.__dmGutter=()=>{if(d!=="floating"||!q)return;const l=c&&c.anchor||"TL",C=Number.isFinite(Number(c?.offsetX))?Number(c.offsetX):16,h=t.classList.contains("is-collapsed")?Ee:parseInt(t.style.width,10)||t.getBoundingClientRect().width||260,N=Math.round(h+C*2)+"px";l==="R"||l==="TR"||l==="BR"?q.style.paddingRight=N:q.style.paddingLeft=N},d==="floating"&&he(t,{position:"floating",float:c||{}}),t.querySelectorAll("details").forEach(l=>{l.addEventListener("toggle",function(){const C=this.getAttribute("data-state-key");C&&S.set(C,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 fe(){h.style.maxHeight="none",h.removeEventListener("transitionend",fe)})})}else{const N=h.scrollHeight;h.style.maxHeight=N+"px",requestAnimationFrame(()=>{h.style.maxHeight="0"})}})}),Le(t),nt(t),t.dataset.routeWired||(t.dataset.routeWired="1",M.subscribe("router:afterChange",()=>t.__dmSetActive?.()));function Z(){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")}Z(),t.__dmSetActive=Z}function ue(){try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}}function V(e){return`${location.origin}${location.pathname}${e}`}function _(e,n){const s=B(),t=e(W(s));if(!Pe(s,t)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}t.length&&ne(s,!1),me(s),n&&E.toast(n,{type:"success",duration:2500}),ue()}function me(e){je(g,e).catch(()=>E.toast("Frequent is saved in this browser only - the server could not keep it.",{type:"warning"}))}function Qe(e){if(!ne(B(),e)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}me(B()),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}),ue()}function et(e){const n=e.dataset.url;if(!n)return[];const s=e.querySelector(".sidebar-text")?.textContent.trim()||n,t=W(B()),i=t.includes(n),o=!!e.closest("details[data-frequent]"),a=t.indexOf(n),d=!i&&t.length>=ee;return[{header:s},{label:"Open",icon:"arrow-right",...se(n)&&{shortcut:Ie(se(n))},action:()=>{location.hash=n}},{label:"Open in a new tab",icon:"external-link",action:()=>window.open(V(n),"_blank","noopener")},{type:"divider"},i?{label:"Remove from Frequent",icon:"star",action:()=>_(u=>Me(u,n),`${s} removed from Frequent.`)}:{label:d?`Frequent is full (${ee})`:"Add to Frequent",icon:"star",disabled:d,action:()=>_(u=>Te(u,n),`${s} added to Frequent.`)},...o?[{label:"Move up",icon:"arrow-up",disabled:a<=0,action:()=>_(u=>te(u,n,-1))},{label:"Move down",icon:"arrow-down",disabled:a<0||a>=t.length-1,action:()=>_(u=>te(u,n,1))}]:[],{type:"divider"},{label:"Copy link",icon:"link",action:async()=>{try{await navigator.clipboard.writeText(V(n)),E.toast("Link copied.",{type:"success",duration:2e3})}catch{E.toast(V(n),{type:"info",duration:8e3})}}}]}function tt(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:()=>Qe(!0)}]:s?.hasAttribute("data-frequent")?[{type:"divider"},{label:"Clear Frequent",icon:"x",action:()=>_(()=>[],"Frequent cleared.")}]:[]]}function nt(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")?tt(n,e):et(n)}))}
4
+ </details>`}if(u){const y=Q(e,s),C=S.get(y)!==!1,B=e.items.map(L=>le(L,t,y,{inFrequent:!!e.frequent})).join(""),_=e.frequent?' data-frequent title="Your Frequent Tools - right-click any Tool to add or remove it"':"";return`<details data-state-key="${f(y)}"${C?" open":""}${_}>
5
+ <summary${v}${k}>${d} <span class="sidebar-text">${i}</span>${A}</summary>
6
+ <div class="sidebar-children">${B}</div>
7
+ </details>`}return`<a href="${c}" class="sidebar-link" data-url="${c}"${v}${m}${k}>${d} <span class="sidebar-text">${i}</span>${De(e,t)}${A}</a>`}async function Ke(e=[],t=[],s=null){const o=p=>p.then(x=>Array.isArray(x)?x.length:Array.isArray(x?.entries)?x.entries.length:0).catch(()=>0);F=Ze(e,s);const n=new Set(F.map(p=>p.url)),d={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"},i=(p,x)=>z(t,p)&&!n.has(d[p])?x():Promise.resolve(0),[c,u,r,a,m,b,v,w,k,A,y,C,B,_]=await Promise.all([i("pages",()=>o(g.pages.list())),i("media",()=>o(g.media.list())),i("menus",()=>o(g.menus.list())),i("context-menus",()=>o(g.contextMenus.list())),i("theme",()=>g.theme.get().then(p=>(p?.overrides||[]).length).catch(()=>0)),i("collections",()=>o(g.collections.list())),i("forms",()=>o(g.forms.list())),i("views",()=>o(g.views.list())),i("actions",()=>o(g.actions.list())),i("blocks",()=>o(g.blocks.list())),i("components",()=>o(g.components.list())),i("users",()=>o(g.users.list())),i("plugins",()=>o(g.plugins.list())),i("notifications",()=>g.system?.notifications?.unreadCount?.().then(p=>p?.count??0).catch(()=>0)??Promise.resolve(0))]),L={};await Promise.all(F.map(async p=>{const x=await ce(p);x&&(L[p.url]=x)}));const O={"#/pages":c,"#/media":u,"#/menus":r,"#/context-menus":a,"#/theme":m,"#/collections":b,"#/forms":v,"#/views":w,"#/actions":k,"#/blocks":A,"#/components":y,"#/users":C,"#/plugins":B,"#/system/notifications":_},W={};for(const[p,x]of Object.entries(O)){if(n.has(p))continue;const[D,U]=Ge[p]??["item","items"],j=K(x);j&&(W[p]={...j,label:`${j.count} ${j.count===1?D:U}`})}return{...W,...L}}let F=[];const Ve="dm:sidebar-count",Xe=6e4;function Ze(e=[],t=null){const s=[],o=n=>{if(n&&!(t&&typeof n.url=="string"&&n.url.startsWith("#/")&&!he(t,n.url.slice(1)))){n.url&&typeof n.countUrl=="string"&&n.countUrl&&s.push({url:n.url,countUrl:n.countUrl,text:n.text||"",countLabel:n.countLabel||"",inventory:n.inventory===!0});for(const d of[...n.children||[],...n.items||[]])o(d)}};for(const n of e??[])o(n?.item??n);return s}async function ce(e){try{const t=new Date,s=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`,o=e.countUrl+(e.countUrl.includes("?")?"&":"?")+`today=${s}`;return K(await H.get(o),{fallbackLabel:e.countLabel||e.text})}catch{return null}}function de(e,t,s){const o=[...e.querySelectorAll("a.sidebar-link")].filter(n=>n.dataset.url===t);if(o.length){for(const n of o){const d=n.querySelector(".sidebar-badge[data-count-for]");if(!s||!(s.count>0)&&!s.showZero)d?.remove();else{const i=document.createRange();i.selectNodeContents(n);const c=i.createContextualFragment(ie(t,s)).firstElementChild;d?d.replaceWith(c):n.querySelector(".sidebar-text")?.after(c)}}V.set(t,s),ae(e)}}const V=new Map;async function X(e,t=null){const s=t?F.filter(o=>o.url===t):F;await Promise.all(s.map(async o=>{const n=await ce(o),d=V.get(o.url);JSON.stringify(d??null)!==JSON.stringify(n)&&de(e,o.url,n)}))}function Ye(e){e.dataset.liveCounts||(e.dataset.liveCounts="1",window.addEventListener(Ve,t=>{const s=t.detail||{};if(s.url)if(s.count!==void 0){const o=F.find(n=>n.url===s.url);de(e,s.url,K(s,{fallbackLabel:o?.countLabel||o?.text||""}))}else X(e,s.url)}),setInterval(()=>{!document.hidden&&document.body.contains(e)&&X(e)},Xe),document.addEventListener("visibilitychange",()=>{document.hidden||X(e)}),Je(e))}function ue(e,t,s){const o=(i,c,u)=>{const r=document.createElement(i);return c&&(r.className=c),u!=null&&(r.textContent=String(u)),r};if(!s){e.textContent=t;return}e.appendChild(o("strong","dm-badge-pop-title",s.title||t)),(s.rows?.length||!s.items?.length)&&e.appendChild(o("p","dm-badge-pop-sub",t));const n=Array.isArray(s.rows)?s.rows.slice(0,6):[];if(n.length){const i=o("dl","dm-badge-pop-rows");for(const c of n)i.appendChild(o("dt",null,c.label)),i.appendChild(o("dd",c.tone==="danger"?"is-danger":null,c.value));e.appendChild(i)}const d=Array.isArray(s.items)?s.items.slice(0,6):[];if(d.length){const i=o("ul","dm-badge-pop-items");for(const c of d){const u=o("li",c.tone==="danger"?"is-danger":null);u.appendChild(o("span","dm-badge-pop-text",c.text)),c.meta&&u.appendChild(o("span","dm-badge-pop-meta",c.meta)),i.appendChild(u)}e.appendChild(i)}else s.empty&&e.appendChild(o("p","dm-badge-pop-empty",s.empty))}function Je(e){let t=null,s=null;const o=()=>e.classList.contains("is-collapsed"),n=()=>{clearTimeout(s),t?.remove(),t=null},d=(r,a)=>{n(),s=setTimeout(()=>{t=document.createElement("div"),t.className="dm-help-pop dm-badge-pop",t.setAttribute("role","tooltip"),a(t),document.body.appendChild(t);const m=r.getBoundingClientRect(),b=t.getBoundingClientRect(),v=Math.min(m.right+10,innerWidth-b.width-8);t.style.left=`${Math.max(8,v)}px`,t.style.top=`${Math.max(8,Math.min(m.top+m.height/2-b.height/2,innerHeight-b.height-8))}px`},150)},i=r=>{const a=r?.dataset.badgeLabel;if(!a)return n();d(r,m=>ue(m,a,R.get(r.dataset.countFor)))},c=r=>{const a=r.querySelector(":scope > .sidebar-text")?.textContent.trim()||"",m=r.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");d(r,b=>{const v=m&&R.get(m.dataset.countFor);if(v)return ue(b,m.dataset.badgeLabel,{...v,title:v.title||a});if(!m){b.textContent=a;return}const w=document.createElement("strong");w.className="dm-badge-pop-title",w.textContent=a;const k=document.createElement("p");k.className="dm-badge-pop-sub",k.textContent=m.dataset.badgeLabel,b.append(w,k)})},u=r=>{const a=r.closest?.("a.sidebar-link, summary");return a&&e.contains(a)?a:null};e.addEventListener("mouseover",r=>{if(o()){const m=u(r.target);m&&!m.contains(r.relatedTarget)&&c(m);return}const a=r.target.closest?.(".sidebar-badge[data-badge-label]");a&&i(a)}),e.addEventListener("mouseout",r=>{if(o()){const a=u(r.target);a&&!a.contains(r.relatedTarget)&&n();return}r.target.closest?.(".sidebar-badge[data-badge-label]")&&n()}),e.addEventListener("focusin",r=>{const a=r.target.closest?.("a, summary");if(o()&&a&&e.contains(a)&&a.querySelector(":scope > .sidebar-text"))return c(a);const m=a?.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");m?i(m):n()}),e.addEventListener("focusout",n),e.addEventListener("click",n),window.addEventListener("scroll",n,!0),window.addEventListener("hashchange",n)}async function Qe(e=[],t=null){if(t?.title)return t;if(!z(e,"settings"))return{title:"Domma CMS"};try{const s=await g.settings.get();return{...s?.adminBrand,title:s?.adminBrand?.title||s?.title||"Domma CMS"}}catch{return{title:"Domma CMS"}}}export async function renderAdminSidebar({mount:e,permissions:t,adminScope:s=null,brand:o=null}){const n=$(e).get(0);if(!n)return;const d=Pe(g,T()).catch(()=>{});let i,c=null,u=null,r=null,a=null,m=null;try{const l=await g.get("/sidebar/menu");i=Array.isArray(l?.items)?l.items:null,c=l?.variant||null,u=l?.position||null,r=l?.style||null,a=l?.float||null,m=l?.highlight||null}catch{i=null}(!i||!i.some(l=>l&&(!l.hidden||l.restricted)))&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),i=Ue.slice());let b=[];try{b=await H.get("/api/sidebar/registered-items")||[]}catch{}i=we(i,ee),i=ve(i,b.filter(l=>(l?.item??l)?.url!==ee)),i=xe(i),i=Se(i,[$e()]);const v=await ze(t);v.length&&We(i,v),i=qe(i,t,s),i=ke(i),await d;const w=Be(i,G(T()),{hidden:He(T())});w&&(i=[w,...i]),Ie(i);const[k,A]=await Promise.all([Ke(b.filter(l=>z(t,l?.item?.permission)),t,s).catch(()=>({})),Qe(t,o)]),y=c?` dm-admin-sidebar--${f(c)}`:"";function C(l,q="px"){if(l==null)return l;const h=String(l).trim();return/^\d+(\.\d+)?$/.test(h)?`${h}${q}`:h}let B="";if(r){const l=[];r.fontFamily&&l.push(`font-family: ${r.fontFamily}, sans-serif`),r.fontSize&&l.push(`font-size: ${C(r.fontSize)}`),r.fontWeight&&l.push(`font-weight: ${r.fontWeight}`),r.letterSpacing&&l.push(`letter-spacing: ${C(r.letterSpacing,"em")}`);const q=[];if(l.length&&q.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("; ")} }`),r.iconSize){const h=C(r.iconSize);q.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${h} !important; height: ${h} !important; }`)}q.length&&(B=`<style data-admin-sidebar-style>${q.join(" ")}</style>`)}const _=Ae(t)?Ce():"",L=String(A.logo||"").trim(),O=/^(?:\/(?!\/)|https?:\/\/)/i.test(L)?L:"",p=`<div class="dm-admin-sidebar-header">${O?`<img src="${f(O)}" alt="" style="width:28px;height:28px;object-fit:contain;flex-shrink:0;">`:`<span data-icon="${f(A.icon||"layout")}"></span>`}<span class="dm-admin-sidebar-title">${f(A.title)}</span>${_}${Le()}</div>`,x=i.some(l=>l&&l.type==="spacer"&&l.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",D=`${B}<nav class="dm-admin-sidebar${y}${x}">${p}${i.map(l=>le(l,k)).join("")}</nav>`,U=document.createRange();U.selectNodeContents(n);const j=U.createContextualFragment(D);n.replaceChildren(j),Domma.icons.scan(n),n.querySelector(".dm-admin-sidebar-arrange")?.addEventListener("click",()=>Fe(n));for(const l of F)V.set(l.url,k[l.url]??null);ae(n),Ye(n),n.removeAttribute("data-menu-hover"),n.removeAttribute("data-menu-active"),n.style.removeProperty("--dm-menu-highlight"),be(n,{highlight:m});const N=document.querySelector(".dashboard-wrapper");["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(l=>{n.style[l]=""}),n.classList.remove("dm-menu-floating"),N&&(N.style.paddingLeft="",N.style.paddingRight=""),n.__dmGutter=()=>{if(u!=="floating"||!N)return;const l=a&&a.anchor||"TL",q=Number.isFinite(Number(a?.offsetX))?Number(a.offsetX):16,h=n.classList.contains("is-collapsed")?Me:parseInt(n.style.width,10)||n.getBoundingClientRect().width||260,P=Math.round(h+q*2)+"px";l==="R"||l==="TR"||l==="BR"?N.style.paddingRight=P:N.style.paddingLeft=P},u==="floating"&&ge(n,{position:"floating",float:a||{}}),n.querySelectorAll("details").forEach(l=>{l.addEventListener("toggle",function(){const q=this.getAttribute("data-state-key");q&&S.set(q,this.open);const h=this.querySelector(":scope > .sidebar-children");if(h)if(this.open){const P=h.scrollHeight;h.style.maxHeight="0",requestAnimationFrame(()=>{h.style.maxHeight=P+"px",h.addEventListener("transitionend",function pe(){h.style.maxHeight="none",h.removeEventListener("transitionend",pe)})})}else{const P=h.scrollHeight;h.style.maxHeight=P+"px",requestAnimationFrame(()=>{h.style.maxHeight="0"})}})}),Ee(n),st(n),n.dataset.routeWired||(n.dataset.routeWired="1",M.subscribe("router:afterChange",()=>n.__dmSetActive?.()));function Y(){const l=/^#\/(dashboards|my-dashboard)\b/.test(location.hash)?"#/":location.hash||"#/";$(n).find(".sidebar-link").removeClass("active"),$(n).find(`.sidebar-link[data-url="${l}"]`).addClass("active")}Y(),n.__dmSetActive=Y}function me(){try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}}function Z(e){return`${location.origin}${location.pathname}${e}`}function I(e,t){const s=T(),o=e(G(s));if(!Re(s,o)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}o.length&&se(s,!1),fe(s),t&&E.toast(t,{type:"success",duration:2500}),me()}function fe(e){Ne(g,e).catch(()=>E.toast("Frequent is saved in this browser only - the server could not keep it.",{type:"warning"}))}function et(e){if(!se(T(),e)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}fe(T()),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}),me()}function tt(e){const t=e.dataset.url;if(!t)return[];const s=e.querySelector(".sidebar-text")?.textContent.trim()||t,o=G(T()),n=o.includes(t),d=!!e.closest("details[data-frequent]"),i=o.indexOf(t),c=!n&&o.length>=te;return[{header:s},{label:"Open",icon:"arrow-right",...oe(t)&&{shortcut:Oe(oe(t))},action:()=>{location.hash=t}},{label:"Open in a new tab",icon:"external-link",action:()=>window.open(Z(t),"_blank","noopener")},{type:"divider"},n?{label:"Remove from Frequent",icon:"star",action:()=>I(u=>je(u,t),`${s} removed from Frequent.`)}:{label:c?`Frequent is full (${te})`:"Add to Frequent",icon:"star",disabled:c,action:()=>I(u=>Te(u,t),`${s} added to Frequent.`)},...d?[{label:"Move up",icon:"arrow-up",disabled:i<=0,action:()=>I(u=>ne(u,t,-1))},{label:"Move down",icon:"arrow-down",disabled:i<0||i>=o.length-1,action:()=>I(u=>ne(u,t,1))}]:[],{type:"divider"},{label:"Copy link",icon:"link",action:async()=>{try{await navigator.clipboard.writeText(Z(t)),E.toast("Link copied.",{type:"success",duration:2e3})}catch{E.toast(Z(t),{type:"info",duration:8e3})}}}]}function nt(e,t){const s=e.closest("details"),o=e.querySelector(".sidebar-text")?.textContent.trim()||"Folder",n=d=>t.querySelectorAll("details").forEach(i=>{i.open!==d&&(i.open=d)});return[{header:o},{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:()=>n(!0)},{label:"Close every folder",icon:"chevrons-up",action:()=>n(!1)},...s?.hasAttribute("data-frequent-empty")?[{type:"divider"},{label:"Hide Frequent until I add a Tool",icon:"eye-off",action:()=>et(!0)}]:s?.hasAttribute("data-frequent")?[{type:"divider"},{label:"Clear Frequent",icon:"x",action:()=>I(()=>[],"Frequent cleared.")}]:[]]}function st(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:t=>t.matches("summary")?nt(t,e):tt(t)}))}
@@ -4,7 +4,7 @@
4
4
  <div class="blx">
5
5
  <p class="blx-note">Reusable HTML templates - one look for a collection's entries, a card, a call to action - placed on a
6
6
  page with a shortcode.
7
- <span data-help-title="Blocks" data-help="A block is an HTML template with a placeholder for each field (the field name in double curly braces), and optionally its own CSS. Use it to show collection entries ([collection … display=&quot;block&quot; block=&quot;name&quot;]), on its own ([block template=&quot;name&quot; …]), or by its name as a short tag ([name … /]). Each block's comment at the top says what it expects; right-click a block to copy its shortcode."></span></p>
7
+ <span data-help-title="Blocks" data-help="A block is an HTML template with a placeholder for each field (the field name in double curly braces), and optionally its own CSS. Use it to show collection entries ([collection … display=&quot;block&quot; block=&quot;name&quot;]), on its own ([block template=&quot;name&quot; …]), or by its name as a short tag ([name … /]). Each block's comment at the top says what it expects; right-click a block to copy its shortcode. The book button above opens the full Blocks guide."></span></p>
8
8
 
9
9
  <!-- Hidden file picker driven by the Import banner action -->
10
10
  <input class="blx-import-file" type="file" accept=".dmblock.json,application/json" hidden>
@@ -367,7 +367,7 @@
367
367
 
368
368
  <hr>
369
369
  <p class="text-muted" style="font-size:.9rem;">
370
- Next: <a href="#/tutorials/plugin">Writing a Plugin</a>
370
+ Next: <a href="#/tutorials/plugin">Writing a Tool</a>
371
371
  </p>
372
372
 
373
373
  </div>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="document"></span> Writing a Plugin</h1>
2
+ <h1><span data-icon="document"></span> Writing a Tool</h1>
3
3
  <a href="#/tutorials" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> All tutorials</a>
4
4
  </div>
5
5
 
@@ -7,12 +7,18 @@
7
7
  <div class="col-12">
8
8
  <div class="docs-body">
9
9
 
10
- <p>Plugins live in the <code>plugins/</code> directory. Each plugin is a self-contained folder with
11
- three required files and optional <code>admin/</code>, <code>public/</code> and <code>docs/</code>
12
- subdirectories.</p>
10
+ <p>A <strong>Tool</strong> is a screen in the admin with its own place in the sidebar: Notes, Invoices,
11
+ Booking and Email are all Tools. The ones you add yourself are <strong>plugins</strong>. That is the older
12
+ name, and it is still used for the folder they live in, the API under <code>/api/plugins/</code> and the
13
+ files you share. Once a plugin is switched on it is a Tool like any other: it files under
14
+ <strong>Tools</strong> in the sidebar, gets the same banner, and an admin can move it, hide it or give it a
15
+ hotkey.</p>
16
+ <p>Each Tool is a self-contained folder in <code>plugins/</code>, with three required files and optional
17
+ <code>admin/</code>, <code>public/</code> and <code>docs/</code> subdirectories.</p>
13
18
  <p>The quickest start is <strong>New plugin</strong> in the banner of <a href="#/plugins">System &gt;
14
19
  Plugins</a> (the Marketplace screen). It copies the built-in template into <code>plugins/&lt;slug&gt;/</code>
15
- with a server entry, config defaults, a wired admin view and a <code>CLAUDE.md</code>, switched on. Restart
20
+ with a server entry, config defaults, a wired admin view, a starter user guide and a <code>CLAUDE.md</code>,
21
+ switched on. Restart
16
22
  the server to activate it. The rest of this page explains what those files do.</p>
17
23
 
18
24
  <h3>Directory structure</h3>
@@ -81,7 +87,9 @@
81
87
  <tr>
82
88
  <td><code>admin.sidebar</code></td>
83
89
  <td>array</td>
84
- <td>Sidebar items to add to the admin panel.</td>
90
+ <td>The Tool's sidebar entries. Each may name its default <code>folder</code> (<code>tools</code>
91
+ unless you say otherwise), a <code>group</code> inside it, a <code>hotkey</code> and a
92
+ <code>countUrl</code> for its badge - see section 5.</td>
85
93
  </tr>
86
94
  <tr>
87
95
  <td><code>admin.routes</code></td>
@@ -252,7 +260,94 @@ export const myPluginView = {
252
260
 
253
261
  <hr>
254
262
 
255
- <h3>5. Injection snippets (optional)</h3>
263
+ <h3>5. Making it feel like a Tool</h3>
264
+ <p>These are what make a screen look and behave like the built-in Tools. None is required, and each one is
265
+ small.</p>
266
+
267
+ <h4>Its place in the sidebar</h4>
268
+ <pre class="code-block"><code>"sidebar": [
269
+ {
270
+ "text": "Invoices",
271
+ "url": "#/plugins/invoices",
272
+ "icon": "file-text",
273
+ "folder": "tools",
274
+ "group": "Business",
275
+ "hotkey": "i",
276
+ "permission": "invoices",
277
+ "countUrl": "/api/plugins/invoices/_count"
278
+ }
279
+ ]</code></pre>
280
+ <ul>
281
+ <li><code>folder</code> is where the Tool starts: <code>overview</code>, <code>content</code>,
282
+ <code>data</code>, <code>tools</code> (the default), <code>system</code> or <code>documentation</code>.
283
+ <code>group</code> puts it in a sub-folder by name, made if it is not there. An admin can move the Tool
284
+ anywhere in <strong>Content &gt; Site menus &gt; Admin sidebar</strong>, and their choice wins.</li>
285
+ <li><code>hotkey</code> is one letter: <kbd>G</kbd> then that letter opens the Tool. The built-in Tools claim
286
+ their letters first, and the first Tool to ask for a free one gets it.</li>
287
+ <li>A Tool is known by its <code>url</code>, so give each entry its own.</li>
288
+ </ul>
289
+
290
+ <h4>A badge with a popover</h4>
291
+ <p>A <code>countUrl</code> on the entry draws a count badge beside it. The route answers
292
+ <code>{count, label, tone, details}</code>. <code>tone</code> is <code>danger</code> (red, something is late),
293
+ <code>live</code> (a pulsing dot, happening now) or <code>info</code>. <code>details</code> fills the popover
294
+ shown on hover: <code>{title, rows: [{label, value}], items: [{text, meta}], empty}</code>, all plain
295
+ text. Counts refresh every minute. When your screen changes the number, tell the sidebar straight away with
296
+ <code>sidebarCount(url)</code> from the tool kit (below). With Dashboard Pro, the same answer is
297
+ offered as a Dashboard widget.</p>
298
+ <pre class="code-block"><code>fastify.get('/_count', { preHandler: [authenticate] }, async () =&gt; {
299
+ const overdue = await countOverdue();
300
+ return {
301
+ count: overdue,
302
+ label: `${overdue} overdue`,
303
+ tone: overdue ? 'danger' : 'info',
304
+ details: { title: 'Invoices', rows: [{ label: 'Overdue', value: String(overdue) }], empty: 'Nothing overdue.' }
305
+ };
306
+ });</code></pre>
307
+
308
+ <h4>The banner, help and the tool kit</h4>
309
+ <p>Every Tool screen sits under a banner showing its name, version, date and licence, drawn by the admin. Put
310
+ screen-level buttons (New, Import, a settings cog) <strong>in</strong> the banner with
311
+ <code>addBannerAction</code>. Add a (?) beside anything that needs explaining with a <code>data-help</code>
312
+ attribute (plain text) and <code>helpPopovers</code>. <code>tool-kit.js</code> has the rest:
313
+ <code>openMenuAt</code> for a ⋮ button that opens a row's right-click menu, <code>viewLifetime</code> to
314
+ tidy up listeners when the user leaves, <code>onceClosed</code> to remove a slideover once it closes, and
315
+ <code>sidebarCount</code>.</p>
316
+ <p>Load these <strong>at run time</strong> with <code>import()</code>, never a static import. The admin is
317
+ replaced on every CMS update, so your Tool must keep working, with less, on a CMS that lacks one of them:</p>
318
+ <pre class="code-block"><code>async onMount($container) {
319
+ try {
320
+ const chrome = await import('/admin/js/lib/plugin-chrome.js');
321
+ chrome.addBannerAction?.($container, { icon: 'plus', title: 'New invoice', onClick: () =&gt; openNew() });
322
+ const help = await import('/admin/js/lib/help-popover.js');
323
+ help.helpPopovers($container, { position: 'bottom' });
324
+ } catch {
325
+ // an older admin: put the button in your own toolbar instead
326
+ }
327
+ }</code></pre>
328
+ <p>Right-click menus come from <code>E.contextMenu</code> on your rows, and screens stay readable in every theme
329
+ if your CSS uses only the theme's <code>--dm-*</code> variables.</p>
330
+
331
+ <h4>Needing other Tools</h4>
332
+ <p><code>requires</code> lists Tools yours cannot work without: it is not loaded while one of them is off.
333
+ <code>uses</code> lists the ones it can do without, with what is lost
334
+ (<code>{"contacts": "Invite a contact group"}</code>). When an admin switches one of those off, the
335
+ Marketplace shows them that text first.</p>
336
+
337
+ <h4>Data and blocks</h4>
338
+ <p>A Tool that keeps records can ship collections (<code>collections/&lt;slug&gt;/schema.json</code>, created the
339
+ first time it is switched on), forms and pages. To give your public pages a design site owners can change, use
340
+ a <a href="#/docs/usage/blocks">block</a>. Create it in <code>onEnable</code>, which runs every time the Tool
341
+ is switched on, so only when it is not there yet - otherwise you would undo the site owner's changes in
342
+ <strong>Data &gt; Blocks</strong>:</p>
343
+ <pre class="code-block"><code>export async function onEnable({ services }) {
344
+ const exists = await services.blocks.getBlock('invoice-card').then(() =&gt; true, () =&gt; false);
345
+ if (!exists) await services.blocks.saveBlock('invoice-card', INVOICE_CARD_HTML, { css: INVOICE_CARD_CSS });
346
+ }</code></pre>
347
+
348
+ <hr>
349
+
350
+ <h3>6. Injection snippets (optional)</h3>
256
351
  <p>HTML snippets declared in <code>inject.head</code>, <code>inject.headLate</code> and
257
352
  <code>inject.bodyEnd</code> are read from the plugin's folder and inserted into every public page. Use
258
353
  this for analytics scripts, stylesheets, or widgets. They are read once at start-up.</p>
@@ -272,15 +367,16 @@ export const myPluginView = {
272
367
 
273
368
  <hr>
274
369
 
275
- <h3>6. User guide (optional)</h3>
370
+ <h3>7. User guide</h3>
276
371
  <p>Markdown files in the plugin's <code>docs/</code> folder become pages under <strong>Documentation &gt;
277
- Plugins</strong> while the plugin is loaded, shown only to users who can use the plugin.
372
+ Plugins</strong> while the Tool is on, shown only to users who can use it.
278
373
  <code>guide.md</code> is the first page; further files become tabs. Start with <code>##</code> sections,
279
- not a top-level heading, and write for site owners.</p>
374
+ not a top-level heading, and write for site owners: what it does, getting started, one section per
375
+ screen, settings, permissions. Update it in the same change as the feature.</p>
280
376
 
281
377
  <hr>
282
378
 
283
- <h3>7. Switching it on and testing</h3>
379
+ <h3>8. Switching it on and testing</h3>
284
380
  <ol>
285
381
  <li>Create the <code>plugins/my-plugin/</code> directory with all three required files (or use
286
382
  <strong>New plugin</strong>, which does this and switches it on).</li>
@@ -294,6 +390,13 @@ export const myPluginView = {
294
390
  <li>Verify your endpoint: <code>GET /api/plugins/my-plugin/hello</code></li>
295
391
  </ol>
296
392
 
393
+ <h3>9. Sharing it</h3>
394
+ <p>On the Marketplace screen, right-click your Tool and choose <strong>Export .dcmsplugin</strong>. On another
395
+ site, <strong>Install from file</strong> in the Marketplace banner takes that file. Installing a newer version
396
+ of a Tool that is already there updates it in place, keeping its <code>data/</code>, and if anything goes
397
+ wrong the old version is put back. An unsigned file, or an older version than the one installed, asks for
398
+ your confirmation first.</p>
399
+
297
400
  <p class="text-muted" style="font-size:.9rem">Tip: use <code>npm run dev</code> during development -
298
401
  the server restarts automatically on file changes. Server-side files (<code>plugin.js</code>,
299
402
  <code>config.js</code>, <code>plugin.json</code>, snippets) only apply after a restart; the in-admin code