domma-cms 1.6.48 → 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 +1 -1
- package/admin/js/app.js +2 -2
- package/admin/js/lib/admin-brand.js +1 -0
- package/admin/js/lib/sidebar-renderer.js +6 -6
- package/admin/js/templates/docs/usage-site-settings.html +7 -0
- package/admin/js/templates/settings.html +2 -1
- package/package.json +1 -1
- package/server/routes/api/auth.js +3 -1
- package/server/services/adminBrand.js +32 -0
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"
|
|
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 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(!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{}}x()}}),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 T="cms_card_states",j=S.get(T)||{};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(T,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 x(){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=!
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}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
|
|
2
|
-
<summary${v}>${
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}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
|
|
5
|
-
<summary${v}${
|
|
6
|
-
<div class="sidebar-children">${
|
|
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)}))}
|
|
@@ -28,6 +28,13 @@
|
|
|
28
28
|
is none (a <code>favicon</code> in <code>config/site.json</code> takes first
|
|
29
29
|
place).</td>
|
|
30
30
|
</tr>
|
|
31
|
+
<tr>
|
|
32
|
+
<td>Branding</td>
|
|
33
|
+
<td>The public site's brand (name, logo or icon, link) and the admin's own. The
|
|
34
|
+
<strong>Admin name</strong> is shown in the admin's top bar, its sidebar and the
|
|
35
|
+
browser tab, for everyone who signs in; empty, it uses the site title. The admin logo
|
|
36
|
+
takes priority over the admin icon.</td>
|
|
37
|
+
</tr>
|
|
31
38
|
<tr>
|
|
32
39
|
<td>SEO</td>
|
|
33
40
|
<td>Default page title, title separator and default description. The full SEO Tool is
|
|
@@ -105,7 +105,8 @@
|
|
|
105
105
|
</div>
|
|
106
106
|
|
|
107
107
|
</div><p class="text-muted">The logo takes priority over the icon. Set the browser icon in the Favicon tab.</p></div>
|
|
108
|
-
<div class="stx-card"><div class="stx-card-title">Admin
|
|
108
|
+
<div class="stx-card"><div class="stx-card-title">Admin
|
|
109
|
+
<span data-help-title="Admin name and mark" data-help="The name and logo or icon at the top of the admin - in the top bar, the sidebar and the browser tab - for everyone who signs in. Leave the name empty to use the site title."></span></div><div class="stx-grid">
|
|
109
110
|
<div><label class="form-label" for="stx-adminBrandTitle">Admin name</label>
|
|
110
111
|
<input id="stx-adminBrandTitle" type="text" class="form-input" placeholder="Uses the site title when empty" data-model="adminBrandTitle.value">
|
|
111
112
|
</div>
|
package/package.json
CHANGED
|
@@ -24,6 +24,7 @@ import {config, getConfig} from '../../config.js';
|
|
|
24
24
|
import {getRegisteredSidebarItems, hooks} from '../../services/hooks.js';
|
|
25
25
|
import {getMenu} from '../../services/menus.js';
|
|
26
26
|
import {resolveAdminHome} from '../../services/adminHome.js';
|
|
27
|
+
import {adminBrandFor} from '../../services/adminBrand.js';
|
|
27
28
|
import {authenticate, checkVisibility} from '../../middleware/auth.js';
|
|
28
29
|
import {getAdminScope, getPermissionsForRoles, getRoleLevel} from '../../services/roles.js';
|
|
29
30
|
import {getEffectiveRegistry, GROUP_ORDER} from '../../services/permissionRegistry.js';
|
|
@@ -252,7 +253,8 @@ export async function authRoutes(fastify) {
|
|
|
252
253
|
});
|
|
253
254
|
// Dashboard Pro's "Open on my boards" - for a user the admin does not confine.
|
|
254
255
|
if (!adminScope && isDashboardPro() && (await readPrefs(request.user)).home) adminHome = '/dashboards';
|
|
255
|
-
|
|
256
|
+
// adminBrand: the admin's name and mark for the top bar, sidebar header and tab title.
|
|
257
|
+
return {permissions, adminScope, adminHome, adminBrand: adminBrandFor(getConfig('site'))};
|
|
256
258
|
});
|
|
257
259
|
|
|
258
260
|
// GET /api/auth/permissions-registry - returns the full permissions registry,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The admin's own name and mark - what the top bar, the sidebar header and the
|
|
3
|
+
* browser tab show. Site Settings › Branding › Admin name / logo / icon, with
|
|
4
|
+
* the site title standing in for an empty name, so a site called ACME Ltd says
|
|
5
|
+
* ACME Ltd in its admin without anyone setting anything.
|
|
6
|
+
*
|
|
7
|
+
* It travels with GET /api/auth/permissions, so every signed-in user sees it -
|
|
8
|
+
* reading Site Settings needs the settings permission, which a confined role
|
|
9
|
+
* (a job-board candidate) does not have.
|
|
10
|
+
*
|
|
11
|
+
* @module server/services/adminBrand
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** The fallback when a site has neither an admin name nor a title. */
|
|
15
|
+
export const DEFAULT_ADMIN_TITLE = 'Domma CMS';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {object} [site] - config/site.json
|
|
19
|
+
* @returns {{title: string, logo: string, icon: string}}
|
|
20
|
+
*/
|
|
21
|
+
export function adminBrandFor(site = {}) {
|
|
22
|
+
const own = site?.adminBrand && typeof site.adminBrand === 'object' ? site.adminBrand : {};
|
|
23
|
+
// Whitespace collapsed: a title reads back that way from the browser tab.
|
|
24
|
+
const str = (v) => (typeof v === 'string' ? v.replace(/[ \t\n\f\r]+/g, ' ').trim() : '');
|
|
25
|
+
const logo = str(own.logo);
|
|
26
|
+
return {
|
|
27
|
+
title: (str(own.title) || str(site?.title) || DEFAULT_ADMIN_TITLE).slice(0, 120),
|
|
28
|
+
// Same rule as the sidebar header: a site path or an http(s) address, nothing else.
|
|
29
|
+
logo: /^(?:\/(?!\/)|https?:\/\/)/i.test(logo) ? logo : '',
|
|
30
|
+
icon: /^[a-z0-9-]{1,40}$/.test(str(own.icon)) ? str(own.icon) : ''
|
|
31
|
+
};
|
|
32
|
+
}
|