domma-cms 0.75.0 → 0.76.0
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/js/app.js +4 -4
- package/admin/js/lib/api-tokens-arrange.js +3 -0
- package/admin/js/lib/effects-arrange.js +3 -0
- package/admin/js/lib/layouts-arrange.js +1 -0
- package/admin/js/lib/my-profile-arrange.js +1 -0
- package/admin/js/lib/notifications-arrange.js +1 -0
- package/admin/js/lib/plugins-arrange.js +2 -0
- package/admin/js/lib/roles-arrange.js +1 -0
- package/admin/js/lib/search-settings-arrange.js +1 -0
- package/admin/js/lib/sidebar-renderer.js +2 -2
- package/admin/js/lib/site-settings-arrange.js +1 -0
- package/admin/js/lib/users-arrange.js +1 -0
- package/admin/js/templates/api-tokens.html +83 -10
- package/admin/js/templates/effects.html +155 -72
- package/admin/js/templates/layouts.html +126 -43
- package/admin/js/templates/my-profile.html +100 -12
- package/admin/js/templates/notifications.html +70 -10
- package/admin/js/templates/plugins.html +100 -28
- package/admin/js/templates/roles.html +78 -7
- package/admin/js/templates/search.html +193 -216
- package/admin/js/templates/settings.html +404 -472
- package/admin/js/templates/users.html +69 -9
- package/admin/js/views/api-tokens.js +62 -8
- package/admin/js/views/effects.js +31 -1
- package/admin/js/views/layouts.js +81 -1
- package/admin/js/views/my-profile.js +1 -1
- package/admin/js/views/notifications.js +18 -1
- package/admin/js/views/plugins.js +97 -39
- package/admin/js/views/roles.js +49 -4
- package/admin/js/views/search.js +7 -1
- package/admin/js/views/settings.js +13 -3
- package/admin/js/views/users.js +13 -4
- package/package.json +1 -1
- package/plugins/_lib/admin/mail/compose-window.js +97 -40
- package/plugins/_lib/admin/mail/contacts.js +4 -1
- package/plugins/_lib/admin/mail/diagnostics-section.js +4 -1
- package/plugins/_lib/admin/mail/folder-tree.js +1 -1
- package/plugins/_lib/admin/mail/image-senders-section.js +69 -90
- package/plugins/_lib/admin/mail/mail.css +1 -1
- package/plugins/_lib/admin/mail/mail.html +1 -1
- package/plugins/_lib/admin/mail/reader-view.js +303 -44
- package/plugins/_lib/admin/mail/when-closed.js +26 -0
- package/plugins/_lib/mail/readRoutes.js +43 -1
- package/plugins/_lib/mail/unreadBadge.js +42 -0
- package/plugins/free-tier.lock.json +5 -5
- package/plugins/mail-reader/CLAUDE.md +1 -0
- package/plugins/mail-reader/plugin.js +3 -1
- package/plugins/mail-reader/plugin.json +5 -4
- package/server/routes/api/api-tokens.js +56 -0
- package/server/routes/api/auth.js +7 -1
- package/server/routes/api/collections.js +13 -1
- package/server/routes/api/effects.js +68 -13
- package/server/routes/api/layouts.js +93 -2
- package/server/routes/api/notifications.js +140 -14
- package/server/routes/api/plugins.js +135 -4
- package/server/routes/api/roles.js +111 -0
- package/server/routes/api/search.js +38 -4
- package/server/routes/api/settings.js +52 -0
- package/server/routes/api/users.js +53 -1
- package/server/server.js +12 -0
- package/server/services/plugins.js +7 -0
- package/server/services/roles.js +3 -0
- package/server/services/search.js +58 -0
- package/server/services/users.js +14 -0
- package/admin/js/templates/plugin-marketplace.html +0 -29
- package/admin/js/views/plugin-marketplace.js +0 -1
package/admin/js/app.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{views as O}from"./views/index.js";import{withPluginChrome as T}from"./lib/plugin-chrome.js";import{api as c,getUser as x,isAuthenticated as d,logout as F}from"./api.js";import{installHttpInterceptor as H}from"./http-interceptor.js";import{installResizableSlideovers as q}from"./lib/slideover-resizable.js";import{installActivityThrobber as K}from"./lib/activity-throbber.js";import{isPathAllowed as z,redirectFor as J}from"./lib/admin-scope.js";$(()=>{H(),K(),q(),(async()=>{const{applyAdminTheme:t,refreshAdminTheme:e}=await import("./lib/admin-theme.js");d()?await e(c,{init:!0}):t({dommaTheme:"charcoal-dark"},{init:!0})})();let l=null,r="/",u=!1;function v(t){const e=C[t]||J(l,t);return e||((t.split("?")[0]||"/")==="/"&&r&&r!=="/"?C[r]||r:null)}const C={};R.use(async(t,e,o)=>{if(t.path==="/login"||t.path==="/reset-password")return o();if(!d()){R.navigate("/login");return}const i=v(t.path);if(i){R.navigate(i);return}o()});async function h(){try{const t=await c.get("/auth/permissions");l=t.adminScope||null,r=t.adminHome||"/";const e=t.permissions||[];return u=z(l,"/settings")&&e.some(o=>o==="settings"||o.startsWith("settings.")),e}catch{return l=null,r="/",u=!1,[]}}document.addEventListener("dm:admin-home-changed",()=>{h()});let D=null,b=!1;document.addEventListener("dm:sidebar-rebuild",()=>{if(D){b=!0;return}D=(async()=>{do{b=!1;try{await y(await h())}catch{}}while(b)})().finally(()=>{D=null})});async function y(t){const{renderAdminSidebar:e}=await import("./lib/sidebar-renderer.js");await e({mount:"#admin-sidebar",permissions:t,adminScope:l})}M.subscribe("router:afterChange",({to:t})=>{(t.path==="/login"||t.path==="/reset-password")&&(p&&(clearInterval(p),p=null),l=null,r="/",u=!1)});const L=[{path:"/",view:"dashboard",title:"Dashboard - 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/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:"/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:"/notes",view:"notes",title:"Notes - Domma CMS"},{path:"/todo",view:"todo",title:"Todo - Domma CMS"},{path:"/analytics",view:"analytics",title:"Analytics - Domma CMS"},{path:"/contacts",view:"contacts",title:"Contacts - 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:e})=>{if(!(t.path==="/login"||t.path==="/reset-password")){if($("#admin-sidebar").show(),$("#admin-topbar").show(),e?.path==="/login"||e?.path==="/reset-password"){$("#topbar-user-name").remove(),import("./lib/admin-theme.js").then(a=>a.refreshAdminTheme(c)),await k();const o=await h(),i=v(t.path);i&&R.navigate(i),y(o);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 g=w(s.data?.title||""),A=w((s.data?.body||"").slice(0,120));E.toast(`${g} - ${A}`,{type:s.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}I()}}),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 B=["#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 e=t?String(t.path).split("?")[0]:"";if(!t||!W(e))return;const i=new URLSearchParams(location.hash.split("?")[1]||"").get("project");if(!i||!/\/new$/.test(e))return;let a=0;const n=60,s=1500,g=()=>{a+=n;for(const A of B){const f=document.querySelector(A);if(!f)continue;if(Array.from(f.options||[]).find(V=>V.value===i)){f.value=i,f.dispatchEvent(new Event("change",{bubbles:!0}));return}}a<s&&setTimeout(g,n)};setTimeout(g,n)});const N="cms_card_states",P=S.get(N)||{};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&&P[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 e=$(this).closest(".card");e.toggleClass("card-collapsed");const o=$(this).find("h2, h3").first().text().trim();o&&(P[o]=e.hasClass("card-collapsed")?"collapsed":"open",S.set(N,P))}),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash==="#/login"||window.location.hash.startsWith("#/reset-password"))return;const e=document.querySelector("#view-container .view-header button.btn-primary");e&&(t.preventDefault(),e.click())});const m={...O},_={notes:{displayName:"Notes",icon:"edit-3"},todo:{displayName:"Todo",icon:"check-square"},analytics:{displayName:"Analytics",icon:"chart-bar"},contacts:{displayName:"Contacts",icon:"users"},projects:{displayName:"Projects",icon:"folder"},pages:{displayName:"Pages",icon:"file-text"},media:{displayName:"Media",icon:"image"},menus:{displayName:"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"}};for(const[t,e]of Object.entries(_))m[t]&&(m[t]=T(m[t],{plugin:t,licence:"Built in",...e}));const j=[...L];async function k(){if(d())try{const t=await c.plugins.adminConfig();t.routes?.length&&j.push(...t.routes);for(const[e,o]of Object.entries(t.views||{}))try{const i=await import(`/plugins/${o.entry}`);m[e]=T(i[o.exportName],(t.meta||{})[e])}catch{}for(const[e,{route:o}]of Object.entries(t.takeovers||{}))o&&(C[`/${e}`]=o);for(const{id:e,href:o}of t.css||[])if(!document.getElementById(e)){const i=document.createElement("link");i.id=e,i.rel="stylesheet",i.href=o,document.head.appendChild(i)}}catch{}}function I(){const t=x();if(!t||$("#topbar-user-name").length)return;const o={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role;$("#topbar-user").html(`
|
|
2
|
-
<span id="topbar-user-name" class="topbar-user-name">${
|
|
3
|
-
<span class="topbar-role-badge topbar-role-badge--${
|
|
1
|
+
import{views as F}from"./views/index.js";import{withPluginChrome as x}from"./lib/plugin-chrome.js";import{api as c,getUser as L,isAuthenticated as d,logout as H}from"./api.js";import{installHttpInterceptor as q}from"./http-interceptor.js";import{installResizableSlideovers as K}from"./lib/slideover-resizable.js";import{installActivityThrobber as z}from"./lib/activity-throbber.js";import{isPathAllowed as J,redirectFor as Y}from"./lib/admin-scope.js";$(()=>{q(),z(),K(),(async()=>{const{applyAdminTheme:t,refreshAdminTheme:e}=await import("./lib/admin-theme.js");d()?await e(c,{init:!0}):t({dommaTheme:"charcoal-dark"},{init:!0})})();let l=null,r="/",u=!1,h=null;function C(t){const e=D[t]||Y(l,t);return e||((t.split("?")[0]||"/")==="/"&&r&&r!=="/"?D[r]||r:null)}const D={};R.use(async(t,e,o)=>{if(t.path==="/login"||t.path==="/reset-password")return o();if(!d()){R.navigate("/login");return}const i=C(t.path);if(i){R.navigate(i);return}o()});async function w(){try{const t=await c.get("/auth/permissions");l=t.adminScope||null,r=t.adminHome||"/";const e=t.permissions||[];return u=J(l,"/settings")&&e.some(o=>o==="settings"||o.startsWith("settings.")),h=e.some(o=>o==="notifications"||o.startsWith("notifications.")),e}catch{return l=null,r="/",u=!1,h=null,[]}}document.addEventListener("dm:admin-home-changed",()=>{w()});let b=null,y=!1;document.addEventListener("dm:sidebar-rebuild",()=>{if(b){y=!0;return}b=(async()=>{do{y=!1;try{await P(await w())}catch{}}while(y)})().finally(()=>{b=null})});async function P(t){const{renderAdminSidebar:e}=await import("./lib/sidebar-renderer.js");await e({mount:"#admin-sidebar",permissions:t,adminScope:l})}M.subscribe("router:afterChange",({to:t})=>{(t.path==="/login"||t.path==="/reset-password")&&(p&&(clearInterval(p),p=null),l=null,r="/",u=!1,h=null)});const B=[{path:"/",view:"dashboard",title:"Dashboard - 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/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:"/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:"/notes",view:"notes",title:"Notes - Domma CMS"},{path:"/todo",view:"todo",title:"Todo - Domma CMS"},{path:"/analytics",view:"analytics",title:"Analytics - Domma CMS"},{path:"/contacts",view:"contacts",title:"Contacts - 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:e})=>{if(!(t.path==="/login"||t.path==="/reset-password")){if($("#admin-sidebar").show(),$("#admin-topbar").show(),e?.path==="/login"||e?.path==="/reset-password"){$("#topbar-user-name").remove(),import("./lib/admin-theme.js").then(a=>a.refreshAdminTheme(c)),await I();const o=await w(),i=C(t.path);i&&R.navigate(i),P(o);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=g(s.data?.title||""),A=g((s.data?.body||"").slice(0,120));E.toast(`${f} - ${A}`,{type:s.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}U()}}),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 W=["#field-project","#menu-project","#collection-project","#endpoint-project","#action-project","#view-project","#block-project","#component-project","#role-project",'[name="project"]','[name="ownedByProject"]'];function _(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 e=t?String(t.path).split("?")[0]:"";if(!t||!_(e))return;const i=new URLSearchParams(location.hash.split("?")[1]||"").get("project");if(!i||!/\/new$/.test(e))return;let a=0;const n=60,s=1500,f=()=>{a+=n;for(const A of W){const v=document.querySelector(A);if(!v)continue;if(Array.from(v.options||[]).find(O=>O.value===i)){v.value=i,v.dispatchEvent(new Event("change",{bubbles:!0}));return}}a<s&&setTimeout(f,n)};setTimeout(f,n)});const k="cms_card_states",j=S.get(k)||{};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 e=$(this).closest(".card");e.toggleClass("card-collapsed");const o=$(this).find("h2, h3").first().text().trim();o&&(j[o]=e.hasClass("card-collapsed")?"collapsed":"open",S.set(k,j))}),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash==="#/login"||window.location.hash.startsWith("#/reset-password"))return;const e=document.querySelector("#view-container .view-header button.btn-primary");e&&(t.preventDefault(),e.click())});const m={...F},V={notes:{displayName:"Notes",icon:"edit-3"},todo:{displayName:"Todo",icon:"check-square"},analytics:{displayName:"Analytics",icon:"chart-bar"},contacts:{displayName:"Contacts",icon:"users"},projects:{displayName:"Projects",icon:"folder"},pages:{displayName:"Pages",icon:"file-text"},media:{displayName:"Media",icon:"image"},menus:{displayName:"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"}};for(const[t,e]of Object.entries(V))m[t]&&(m[t]=x(m[t],{plugin:t,licence:"Built in",...e}));const N=[...B];async function I(){if(d())try{const t=await c.plugins.adminConfig();t.routes?.length&&N.push(...t.routes);for(const[e,o]of Object.entries(t.views||{}))try{const i=await import(`/plugins/${o.entry}`);m[e]=x(i[o.exportName],(t.meta||{})[e])}catch{}for(const[e,{route:o}]of Object.entries(t.takeovers||{}))o&&(D[`/${e}`]=o);for(const{id:e,href:o}of t.css||[])if(!document.getElementById(e)){const i=document.createElement("link");i.id=e,i.rel="stylesheet",i.href=o,document.head.appendChild(i)}}catch{}}function U(){const t=L();if(!t||$("#topbar-user-name").length)return;const o={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role;$("#topbar-user").html(`
|
|
2
|
+
<span id="topbar-user-name" class="topbar-user-name">${g(t.name)}</span>
|
|
3
|
+
<span class="topbar-role-badge topbar-role-badge--${g(t.role)}">${o}</span>
|
|
4
4
|
`),$("#topbar-actions").html(`
|
|
5
5
|
<a href="#/system/notifications" id="topbar-bell" class="topbar-action-link" data-tooltip="Notifications" data-tooltip-placement="bottom" hidden>
|
|
6
6
|
<span data-icon="bell"></span>
|
|
@@ -18,4 +18,4 @@ import{views as O}from"./views/index.js";import{withPluginChrome as T}from"./lib
|
|
|
18
18
|
<span data-icon="log-out"></span>
|
|
19
19
|
<span>Sign out</span>
|
|
20
20
|
</a>
|
|
21
|
-
`);const i=$("#topbar-bell").get(0);i&&(i.hidden=!0);const a=$("#topbar-settings").get(0);a&&(a.hidden=!u),$("#topbar-logout-btn").on("click",n=>{n.preventDefault(),
|
|
21
|
+
`);const i=$("#topbar-bell").get(0);i&&(i.hidden=!0);const a=$("#topbar-settings").get(0);a&&(a.hidden=!u),$("#topbar-logout-btn").on("click",n=>{n.preventDefault(),H()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"}),T(),p||(p=setInterval(T,6e4))}function g(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}let p=null;async function T(){if(!(!d()||h===!1))try{const{count:t}=await c.system.notifications.unreadCount();$("#topbar-bell").removeAttr("hidden");const e=$("#topbar-bell-badge");t>0?e.text(t>99?"99+":String(t)).removeAttr("hidden"):e.attr("hidden","").text("")}catch{}}(async()=>{if(!d()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash="#/login";else if(await I(),L()){const a=await w(),n=(window.location.hash||"#/").slice(1)||"/",s=C(n);s&&(window.location.hash=`#${s}`),await P(a),U()}R.init({container:"#view-container",routes:N,views:m,default:"/",transitions:{enter:"fadeIn",leave:"fadeOut",duration:150}});const t=R._extractParams.bind(R);R._extractParams=function(i,a){const n=a.indexOf("?");if(n!==-1&&(a=a.slice(0,n)),i.endsWith("/*")){const s=i.slice(0,-2);return a.startsWith(s+"/")?{}:null}return t(i,a)};const e=(window.location.hash||"#/").slice(1)||"/";N.filter(i=>i.path.endsWith("/*")).some(i=>e.startsWith(i.path.slice(0,-2)+"/"))&&R._handleRouteChange()})()});
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export const VERBS=["create","read","update","delete"],FILTERS=["all","active","expiring","expired","disabled"],SOON_DAYS=14;const x=864e5,d=(e,t,r=`${t}s`)=>`${e} ${e===1?t:r}`;export function tokenState(e,t=Date.now()){const r=e?.expiresAt?Date.parse(e.expiresAt):NaN,n=Number.isFinite(r)?Math.ceil((r-t)/864e5):null;return Number.isFinite(r)&&r<=t?{state:"expired",days:n}:e?.enabled===!1?{state:"disabled",days:n}:n!==null&&n<=14?{state:"expiring",days:n}:{state:"active",days:n}}export function expiresIn(e){return e==null?"":e<=0?"today":e===1?"tomorrow":`in ${e} days`}export function ago(e,t=Date.now()){const r=Date.parse(e);if(!Number.isFinite(r))return"";const n=Math.max(0,Math.round((t-r)/1e3));return n<60?"just now":n<3600?`${Math.round(n/60)} min ago`:n<86400?`${d(Math.round(n/3600),"hour")} ago`:n<86400*30?`${d(Math.round(n/86400),"day")} ago`:new Date(r).toISOString().slice(0,10)}export function scopesToText(e){return(Array.isArray(e)?e:[]).map(t=>Array.isArray(t.verbs)&&t.verbs.length?`${t.collection}: ${t.verbs.join(", ")}`:t.collection).join(`
|
|
2
|
+
`)}export function parseScopes(e){const t=[],r=new Set;for(const n of String(e??"").split(`
|
|
3
|
+
`)){const i=n.trim();if(!i)continue;const s=i.indexOf(":"),a=(s<0?i:i.slice(0,s)).trim(),o=s<0?"":i.slice(s+1).trim();if(!a)return{error:`"${i}" names no collection.`};if(r.has(a))return{error:`"${a}" is listed twice.`};r.add(a);const c={collection:a};if(o){const p=o.split(",").map(l=>l.trim().toLowerCase()).filter(Boolean),u=p.find(l=>!VERBS.includes(l));if(u)return{error:`"${u}" is not a verb - use ${VERBS.join(", ")}.`};c.verbs=[...new Set(p)]}t.push(c)}return{scopes:t}}export function scopesSummary(e){return!Array.isArray(e)||!e.length?"every collection in the project":e.map(t=>`${t.collection}: ${Array.isArray(t.verbs)&&t.verbs.length?t.verbs.join(", "):"all"}`).join(" \xB7 ")}export function arrangeTokens(e,{filter:t="all",project:r="",query:n=""}={},i=Date.now()){const s=String(n).trim().toLowerCase(),a={expired:0,expiring:1,active:2,disabled:3};return(e||[]).map(o=>({t:o,s:tokenState(o,i)})).filter(({s:o})=>t==="all"||o.state===t).filter(({t:o})=>!r||o.project===r).filter(({t:o})=>!s||[o.name,o.project,o.tokenHint,o.createdBy,scopesSummary(o.scopes)].some(c=>String(c||"").toLowerCase().includes(s))).sort((o,c)=>a[o.s.state]-a[c.s.state]||(o.s.state==="expiring"?o.s.days-c.s.days:0)||String(o.t.name||"").localeCompare(String(c.t.name||""))).map(({t:o})=>o)}export function countStates(e,t=Date.now()){const r={all:0,active:0,expiring:0,expired:0,disabled:0};for(const n of e||[])r.all++,r[tokenState(n,t).state]++;return r}const f={active:["Active","is-ok"],disabled:["Off",""],expired:["Expired","is-danger"],expiring:["","is-warn"]};export function tokenRow(e,t=Date.now()){const{state:r,days:n}=tokenState(e,t),[i,s]=f[r],a=e.expiresAt?r==="expired"?`expired ${ago(e.expiresAt,t)}`:`expires ${expiresIn(n)}`:"never expires";return{id:e.id,name:e.name||"(unnamed)",hint:`dcms_\u2026${e.tokenHint||"????"}`,project:e.project||"",meta:[scopesSummary(e.scopes),a,e.createdBy?`by ${e.createdBy}`:""].filter(Boolean).join(" \xB7 "),used:e.lastUsedAt?`used ${ago(e.lastUsedAt,t)}`:"never used",stateLabel:r==="expiring"?`Expires ${expiresIn(n)}`:i,stateCls:s,rowCls:r==="disabled"||r==="expired"?"is-off":""}}export function toLocalInput(e){const t=Date.parse(e);if(!Number.isFinite(t))return"";const r=new Date(t),n=i=>String(i).padStart(2,"0");return`${r.getFullYear()}-${n(r.getMonth()+1)}-${n(r.getDate())}T${n(r.getHours())}:${n(r.getMinutes())}`}export function checkToken(e,{isNew:t,projects:r=[],now:n=Date.now()}={}){const i=e||{},s={},a=String(i.name??"").trim();a||(s.name="A token needs a name - who or what will use it."),t&&!String(i.project||"")?s.project="Choose the project it works in.":t&&r.length&&!r.includes(i.project)&&(s.project="Choose one of your projects.");const{scopes:o,error:c}=parseScopes(i.scopes);c&&(s.scopes=c);let p=null;if(String(i.expires||"").trim()){const l=Date.parse(i.expires);Number.isFinite(l)?(p=new Date(l).toISOString(),t&&l<=n&&(s.expires="That is already past - a new token would be dead on arrival.")):s.expires="Not a date."}const u=t?{name:a,project:i.project,scopes:o||[],expiresAt:p}:{name:a,enabled:i.enabled!==!1,scopes:o||[],expiresAt:p};return{errors:s,payload:u}}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export const ANIMATIONS=["fade","slide-up","slide-down","zoom","flip"],DEFAULT_SETTINGS={respectMotion:!0,defaultDuration:600,defaultAnimation:"fade",defaultThreshold:.1},DURATION_MAX=1e4;export function formFromSettings(t){const e=t&&typeof t=="object"?t:{};return{respectMotion:e.respectMotion!==!1,defaultDuration:String(Number.isFinite(Number(e.defaultDuration))&&e.defaultDuration!==""&&e.defaultDuration!=null?e.defaultDuration:DEFAULT_SETTINGS.defaultDuration),defaultAnimation:ANIMATIONS.includes(e.defaultAnimation)?e.defaultAnimation:DEFAULT_SETTINGS.defaultAnimation,defaultThreshold:String(Number.isFinite(Number(e.defaultThreshold))&&e.defaultThreshold!==""&&e.defaultThreshold!=null?e.defaultThreshold:DEFAULT_SETTINGS.defaultThreshold)}}export function checkSettings(t){const e=t||{},o={},r=Number(String(e.defaultDuration??"").trim());(String(e.defaultDuration??"").trim()===""||!Number.isInteger(r)||r<1||r>DURATION_MAX)&&(o.defaultDuration=`A whole number of milliseconds, 1 to ${DURATION_MAX}.`);const n=Number(String(e.defaultThreshold??"").trim());return(String(e.defaultThreshold??"").trim()===""||!Number.isFinite(n)||n<0||n>1)&&(o.defaultThreshold="A number from 0 (as soon as any of it shows) to 1 (only when all of it shows)."),ANIMATIONS.includes(e.defaultAnimation)||(o.defaultAnimation="Pick one of the listed animations."),{errors:o,payload:{respectMotion:e.respectMotion!==!1,defaultDuration:Number.isInteger(r)?r:DEFAULT_SETTINGS.defaultDuration,defaultAnimation:ANIMATIONS.includes(e.defaultAnimation)?e.defaultAnimation:DEFAULT_SETTINGS.defaultAnimation,defaultThreshold:Number.isFinite(n)?n:DEFAULT_SETTINGS.defaultThreshold}}}export function settingsSnapshot(t){const e=t||{};return JSON.stringify([e.respectMotion!==!1,String(e.defaultDuration??"").trim(),String(e.defaultAnimation??""),String(e.defaultThreshold??"").trim()])}export function changedFromDefaults(t){return Object.keys(DEFAULT_SETTINGS).filter(e=>t?.[e]!==DEFAULT_SETTINGS[e])}export function categoriesOf(t){const e=[];for(const o of t||[])o.category&&!e.includes(o.category)&&e.push(o.category);return e}export function arrangeEffects(t,{category:e="all",query:o=""}={}){const r=String(o).trim().toLowerCase();return(t||[]).filter(n=>e==="all"||n.category===e).filter(n=>!r||[n.name,n.label,n.description,...(n.attrs||[]).map(i=>i.name)].some(i=>String(i||"").toLowerCase().includes(r)))}const a=(t,e)=>t.type==="checkbox"?e?"true":"false":String(e).replace(/"/g,""");export function exampleShortcode(t,{full:e=!1}={}){if(!t?.name)return"";const o=e?(t.attrs||[]).filter(n=>n.default!==void 0&&n.default!=="").map(n=>`${n.name}="${a(n,n.default)}"`):[],r=o.length?` ${o.join(" ")}`:"";return t.selfClosing?`[${t.name}${r} /]`:`[${t.name}${r}]
|
|
2
|
+
Content
|
|
3
|
+
[/${t.name}]`}export function attrRow(t){let e;t.type==="select"?e=(t.options||[]).map(r=>Array.isArray(r)?r[0]:r).join(" \xB7 "):t.type==="checkbox"?e="true / false":t.type==="colour"?e="a colour (#rrggbb)":t.type==="number"?e=`a number${[t.min,t.max].every(n=>n!==void 0)?` ${t.min} to ${t.max}`:""}`:e="text";const o=t.default===void 0||t.default===""?"-":t.type==="checkbox"?t.default?"true":"false":String(t.default);return{name:t.name,label:t.label||t.name,takes:e,def:o,hint:t.hint||""}}export function effectRow(t){const e=(t.hosts||[]).length?`works inside ${t.hosts.map(r=>`[${r}]`).join(" ")}`:"on its own",o=(t.attrs||[]).length;return{name:t.name,label:t.label||t.name,tag:`[${t.name}]`,description:t.description||"",meta:[`${o} setting${o===1?"":"s"}`,e,t.selfClosing?"self-closing":"wraps content"].join(" \xB7 "),category:t.category||"",fullPage:t.previewable===!1}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const WIDTHS=["narrow","normal","wide","full"],WIDTH_LABELS={narrow:"Narrow",normal:"Normal",wide:"Wide",full:"Full width"},WIDTH_HINTS={narrow:"768px - a reading column",normal:"1100px",wide:"75% of the window, up to 2000px",full:"edge to edge"},WIDTH_SHARE={narrow:"40%",normal:"62%",wide:"82%",full:"100%"},FILTERS=["all","builtin","custom"];const u=/^[a-zA-Z0-9#(),.\s%/-]+$/;export function layoutKey(e){return String(e??"").toLowerCase().trim().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"")}const d=(e,r,n=`${r}s`)=>`${e} ${e===1?r:n}`;export function chromeText(e){const r=[e.navbar!==!1&&"navbar",e.footer!==!1&&"footer",e.sidebar===!0&&"sidebar"].filter(Boolean);return r.length?r.join(", "):"no navbar or footer"}export function arrangeLayouts(e,r={},{filter:n="all",query:o=""}={}){const s=String(o).trim().toLowerCase(),l=Object.entries(e||{}).map(([t,a])=>({...a,key:t})),i=l.filter(t=>t.builtin),c=l.filter(t=>!t.builtin).sort((t,a)=>String(t.label||t.key).localeCompare(String(a.label||a.key)));return[...i,...c].filter(t=>n==="builtin"?t.builtin:n==="custom"?!t.builtin:!0).filter(t=>!s||[t.key,t.label,t.description,t.class].some(a=>String(a||"").toLowerCase().includes(s))).map(t=>layoutRow(t,(r.byKey||{})[t.key]||[]))}export function layoutRow(e,r=[]){const n=WIDTHS.includes(e.width)?e.width:"normal";return{key:e.key,label:e.label||e.key,meta:[e.key,e.description,chromeText(e)].filter(Boolean).join(" \xB7 "),builtin:e.builtin===!0,widthLabel:WIDTH_LABELS[n],used:r.length,usedText:r.length?d(r.length,"page"):"Unused",usedTone:r.length?"is-info":"is-muted",hasNav:e.navbar!==!1,hasFoot:e.footer!==!1,hasSide:e.sidebar===!0,share:WIDTH_SHARE[n],bg:e.bgColor&&u.test(e.bgColor)?e.bgColor:""}}export function formFromPreset(e={}){return{label:e.label||"",description:e.description||"",width:WIDTHS.includes(e.width)?e.width:"normal",navbar:e.navbar!==!1,footer:e.footer!==!1,sidebar:e.sidebar===!0,useBg:!!e.bgColor,bgColor:e.bgColor&&/^#[0-9a-f]{6}$/i.test(e.bgColor)?e.bgColor:e.bgColor||"#ffffff",bgImage:e.bgImage||"",cssClass:e.class||""}}export function checkLayout(e,{isNew:r,taken:n=[]}={}){const o=e||{},s={},l=String(o.label??"").trim(),i=r?layoutKey(l):"";l?l.length>60?s.label="Up to 60 characters.":r&&!i?s.label="Needs at least one letter or number.":r&&n.includes(i)&&(s.label=`"${i}" is already a layout - choose another name.`):s.label="A layout needs a name.";const c=o.useBg?String(o.bgColor||"").trim():"";c&&!u.test(c)&&(s.bgColor="Not a colour the server accepts.");const t=String(o.bgImage??"").trim(),a=String(o.cssClass??"").trim();/[^a-zA-Z0-9\s_-]/.test(a)&&(s.cssClass="Letters, numbers, spaces, hyphens and underscores only.");const b={label:l,description:String(o.description??"").trim().slice(0,200),width:WIDTHS.includes(o.width)?o.width:"normal",navbar:o.navbar!==!1,footer:o.footer!==!1,sidebar:o.sidebar===!0,bgColor:c,bgImage:t,class:a};return{errors:s,payload:b,key:i}}export function checkOptions(e){const r={},n=String(e?.spacerSize??"").trim(),o=Number(n);(n===""||!Number.isInteger(o)||o<0||o>500)&&(r.spacerSize="A whole number of pixels, 0 to 500.");const s=String(e?.spacerClass??"").trim();return/[^a-zA-Z0-9\s_-]/.test(s)&&(r.spacerClass="Letters, numbers, spaces, hyphens and underscores only."),{errors:r,payload:{spacerSize:Number.isInteger(o)?o:8,spacerClass:s}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PASSWORD_MIN=8;const m=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;export function fieldKind(t){return{email:"email",tel:"tel",number:"number",url:"url",date:"date",time:"time",textarea:"textarea",select:"select",radio:"select",checkbox:"checkbox","checkbox-group":"multi",hidden:"hidden"}[t]||"text"}export function fieldOptions(t){return(Array.isArray(t?.options)?t.options:[]).map(e=>e&&typeof e=="object"?{value:String(e.value??e.label??""),label:String(e.label??e.value??"")}:{value:String(e),label:String(e)}).filter(e=>e.value!=="")}export function profileValue(t,e){const r=fieldKind(t?.type);return r==="checkbox"?e===!0||e==="true"||e===1:r==="multi"?Array.isArray(e)?e.map(String):e==null||e===""?[]:[String(e)]:e==null?"":String(e)}export function formFromMe(t,e=[]){return{name:String(t?.name??""),email:String(t?.email??""),password:"",confirm:"",profile:Object.fromEntries((e||[]).filter(r=>r?.name).map(r=>[r.name,profileValue(r,t?.profile?.[r.name])]))}}export function draftOf(t){const{password:e,confirm:r,...n}=t||{};return n}export function checkForm(t,e=[],{saved:r}={}){const n={},i=String(t?.name??"").trim(),l=String(t?.email??"").trim();i.length<2&&(n.name="Your name needs at least 2 characters."),m.test(l)||(n.email="That does not look like an email address.");const o=String(t?.password??""),a=String(t?.confirm??"");o&&o.length<8?n.password="At least 8 characters.":o&&o!==a?n.confirm="The two passwords are not the same.":!o&&a&&(n.password="Type the new password here too.");const p=!r||JSON.stringify(t?.profile||{})!==JSON.stringify(r.profile||{});for(const s of p?e||[]:[]){if(!s?.name||!s.required||fieldKind(s.type)==="hidden")continue;const c=t?.profile?.[s.name];(Array.isArray(c)?!c.length:fieldKind(s.type)==="checkbox"?c!==!0:String(c??"").trim()==="")&&(n[`profile.${s.name}`]=`${s.label||s.name} is required.`)}return n}export function snapshot(t){const e=t||{};return JSON.stringify({name:String(e.name??"").trim(),email:String(e.email??"").trim(),password:!!e.password,confirm:!!e.confirm,profile:e.profile||{}})}export function buildPayload(t,e,r=[]){const n={},i=[],l=String(e?.name??"").trim(),o=String(e?.email??"").trim();return l!==String(t?.name??"").trim()&&(n.name=l,i.push("name")),o!==String(t?.email??"").trim()&&(n.email=o,i.push("email")),e?.password&&(n.password=String(e.password),i.push("password")),JSON.stringify(e?.profile||{})!==JSON.stringify(t?.profile||{})&&(n.profile=Object.fromEntries((r||[]).filter(a=>a?.name).map(a=>[a.name,e.profile?.[a.name]??profileValue(a,void 0)])),i.push("profile")),i.length?{payload:n,parts:i}:null}export function initials(t){const e=String(t??"").trim().split(/\s+/).filter(Boolean);return e.length?(e[0][0]+(e.length>1?e[e.length-1][0]:"")).toUpperCase():"?"}export function accountFacts(t){const e=[t?.role,...Array.isArray(t?.additionalRoles)?t.additionalRoles:[]].filter(Boolean),r=Array.isArray(t?.projects)?t.projects:[];return{roles:e.length?e:["-"],access:r.length?`Projects: ${r.join(", ")}`:"Every project",active:t?.isActive!==!1}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SEVERITIES=["critical","warning","info","success"],FILTERS=["all","new",...SEVERITIES],SEVERITY_TONE={critical:"danger",warning:"warn",success:"ok",info:"info"};const s=(e,i,r=`${i}s`)=>`${e} ${e===1?i:r}`;export function severityOf(e){const i=e?.data?.severity;return SEVERITIES.includes(i)?i:"info"}export function ago(e,i=Date.now()){const r=new Date(e).getTime();if(!Number.isFinite(r))return"";const t=Math.max(0,Math.round((i-r)/1e3));return t<60?"just now":t<3600?`${Math.round(t/60)} min ago`:t<86400?`${s(Math.round(t/3600),"hour")} ago`:t<86400*14?`${s(Math.round(t/86400),"day")} ago`:new Date(r).toISOString().slice(0,10)}export function arrangeNotifications(e,{filter:i="all",query:r=""}={}){const t=String(r).trim().toLowerCase();return[...e||[]].filter(n=>i==="all"?!0:i==="new"?n.isNew:severityOf(n)===i).filter(n=>!t||[n.data?.title,n.data?.body,n.data?.link].some(o=>String(o||"").toLowerCase().includes(t))).sort((n,o)=>String(o.data?.createdAt||"").localeCompare(String(n.data?.createdAt||"")))}export function filterCounts(e){const i={all:0,new:0,critical:0,warning:0,info:0,success:0};for(const r of e||[])i.all++,r.isNew&&i.new++,i[severityOf(r)]++;return i}export function notificationRow(e,{now:i=Date.now(),expanded:r=""}={}){const t=e?.data||{},n=severityOf(e),o=String(t.body||""),a=o.replace(/\s+/g," ").trim();return{id:String(e.id),title:String(t.title||"(no title)"),severity:n,severityLabel:n[0].toUpperCase()+n.slice(1),tone:SEVERITY_TONE[n],meta:[t.createdAt?ago(t.createdAt,i):"",t.source==="manager"?"from the manager":"",/^https?:\/\//i.test(String(t.link||""))?"has a link":"",t.expiresAt&&Date.parse(t.expiresAt)>i?`until ${String(t.expiresAt).slice(0,10)}`:""].filter(Boolean).join(" \xB7 "),preview:a.length>140?`${a.slice(0,139)}\u2026`:a,body:o,link:/^https?:\/\//i.test(String(t.link||""))?String(t.link):"",isNew:!!e.isNew,unread:!!e.unread,open:r===String(e.id),rowCls:[e.unread?"is-unread":"",r===String(e.id)?"is-open":"",`is-${n}`].filter(Boolean).join(" ")}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export const FILTERS=["all","on","off","attention"],BROWSE_FILTERS=["all","installed","available","updates"],BLOCKING=new Set(["expired","unlicensed"]);const c={grace:{text:"Renew",tone:"warn",title:"The rental term has ended - still running during the grace period"},"support-ended":{text:"Support ended",tone:"muted",title:"Owned outright and still running, but no longer entitled to updates"},expired:{text:"Expired",tone:"danger",title:"The rental term and grace period have ended - this plugin is not running"},unlicensed:{text:"Unlicensed",tone:"danger",title:"No valid entitlement for this site - this plugin is not running"}};export function compareVersions(e,a){const i=l=>String(l??"").split(".").map(r=>parseInt(r,10)||0),t=i(e),n=i(a);for(let l=0;l<Math.max(t.length,n.length);l++){const r=(t[l]??0)-(n[l]??0);if(r)return r>0?1:-1}return 0}export function pluginStatus(e){return e.core?{key:"core",text:"Built in",tone:"info",running:!0}:e.enabled&&BLOCKING.has(e.entitlement)?{key:"blocked",text:"Not running",tone:"danger",running:!1}:e.enabled&&e.supersededBy?{key:"replaced",text:"Replaced",tone:"muted",running:!1}:e.enabled?{key:"on",text:"On",tone:"ok",running:!0}:{key:"off",text:"Off",tone:"muted",running:!1}}export function updateFor(e,a){const i=a?.get?.(e.name);return i&&compareVersions(i,e.version)>0?i:""}export function needsAttention(e,a){return pluginStatus(e).key==="blocked"||e.entitlement==="grace"||!!updateFor(e,a)}export function fmtReleased(e){if(!e||!/^\d{4}-\d{2}-\d{2}/.test(String(e)))return"";const a=new Date(e);if(Number.isNaN(a.getTime()))return"";const i=t=>String(t).padStart(2,"0");return`${i(a.getDate())}.${i(a.getMonth()+1)}.${a.getFullYear()}`}export function arrangePlugins(e,{filter:a="all",query:i="",latest:t=new Map}={}){const n=String(i).trim().toLowerCase(),l={all:()=>!0,on:s=>pluginStatus(s).running,off:s=>!pluginStatus(s).running,attention:s=>needsAttention(s,t)}[a]||(()=>!0),r=s=>needsAttention(s,t)?0:pluginStatus(s).running?1:2;return[...e||[]].filter(l).filter(s=>!n||[s.name,s.displayName,s.description,s.author].some(o=>String(o||"").toLowerCase().includes(n))).sort((s,o)=>r(s)-r(o)||String(s.displayName||s.name).localeCompare(String(o.displayName||o.name)))}export function pluginRow(e,a=new Map){const i=pluginStatus(e),t=c[e.entitlement]||null,n=updateFor(e,a),l=fmtReleased(e.date);return{name:e.name,displayName:e.displayName||e.name,icon:e.icon||"package",meta:[`v${e.version}`,e.author?`by ${e.author}`:"",l?`released ${l}`:"",e.supersededBy?`replaced by ${e.supersededBy}`:""].filter(Boolean).join(" \xB7 "),description:e.description||"",statusText:i.text,statusCls:`is-${i.tone}`,licensed:e.closedSource===!0,entText:t?.text||"",entCls:t?`is-${t.tone}`:"",entTitle:t?.title||"",update:n,updateText:n?`Update to ${n}`:"",configurable:settingsFields(e).length>0,rowCls:[i.running?"":"is-off",needsAttention(e,a)?"is-attention":""].filter(Boolean).join(" ")}}export function formatPrice(e){if(e?.tier!=="paid")return"Free";const a=e.terms?.managed?.monthly;return a==null||!Number.isFinite(Number(a))?"Paid":`${e.terms?.currency==="GBP"?"\xA3":`${e.terms?.currency||""} `}${(Number(a)/100).toFixed(2)}/mo`}export function catalogueAction(e){return e.installed&&e.updatable&&e.entitled?"update":e.installed?"installed":e.entitled?"install":"locked"}export function arrangeCatalogue(e,{filter:a="all",query:i=""}={}){const t=String(i).trim().toLowerCase(),n={all:()=>!0,installed:r=>r.installed,available:r=>!r.installed&&r.entitled,updates:r=>catalogueAction(r)==="update"}[a]||(()=>!0),l={update:0,install:1,installed:2,locked:3};return[...e||[]].filter(n).filter(r=>!t||[r.slug,r.displayName,r.description].some(s=>String(s||"").toLowerCase().includes(t))).sort((r,s)=>l[catalogueAction(r)]-l[catalogueAction(s)]||String(r.displayName||r.slug).localeCompare(String(s.displayName||s.slug)))}export function catalogueRow(e){const a=catalogueAction(e),i=e.entitled&&e.tier==="paid";return{slug:e.slug,displayName:e.displayName||e.slug,icon:e.icon||"package",meta:[e.installed&&e.installedVersion&&e.installedVersion!==e.version?`v${e.installedVersion} \u2192 v${e.version}`:`v${e.version}`,e.installed?"installed":""].filter(Boolean).join(" \xB7 "),description:e.description||"",price:formatPrice(e),licText:i?e.licenceStatus==="grace"?"Renew":"Licensed":"",licCls:e.licenceStatus==="grace"?"is-warn":"is-ok",action:a,actionText:{update:"Update",install:"Install",installed:"Installed",locked:e.licenceStatus==="expired"?"Licence expired":"Not licensed"}[a],rowCls:a==="locked"?"is-off":a==="update"?"is-attention":""}}export const isSecretKey=e=>/pass(word)?|secret|token|api[-_]?key|private|credential|uri$|dsn/i.test(String(e));export function labelFor(e){const a=String(e).replace(/[_-]+/g," ").replace(/([a-z0-9])([A-Z])/g,"$1 $2").trim().toLowerCase();return a?a[0].toUpperCase()+a.slice(1):String(e)}export function settingsFields(e){const a=e?.settings||{},i={...e?.effectiveSettings||{},...a};return Array.isArray(e?.settingsSchema)&&e.settingsSchema.length?e.settingsSchema.filter(t=>t&&t.key).map(t=>{const n=t.type==="select"?"select":t.type==="number"?"number":t.type==="boolean"||t.type==="checkbox"?"checkbox":t.type==="textarea"?"textarea":"text",l=i[t.key]??t.default??(n==="checkbox"?!1:"");return{key:t.key,label:t.label||labelFor(t.key),type:n,value:n==="checkbox"?l===!0:String(l??""),options:(t.options||[]).map(r=>typeof r=="object"?{value:String(r.value),label:String(r.label??r.value)}:{value:String(r),label:String(r)}),min:t.min,max:t.max,help:t.help||"",secret:t.secret===!0||isSecretKey(t.key)}}):Object.entries(i).map(([t,n])=>{const l=isSecretKey(t);if(typeof n=="boolean")return{key:t,label:labelFor(t),type:"checkbox",value:n,secret:l};if(typeof n=="number")return{key:t,label:labelFor(t),type:"number",value:String(n),secret:l};if(n!==null&&typeof n=="object")return{key:t,label:labelFor(t),type:"json",value:JSON.stringify(n,null,2),secret:l};const r=String(n??"");return{key:t,label:labelFor(t),type:r.includes(`
|
|
2
|
+
`)||r.length>80?"textarea":"text",value:r,secret:l}})}export function readSettings(e,a){const i={},t={};for(const n of e){const l=a[n.key];if(n.type==="checkbox"){!!l!=!!n.value&&(t[n.key]=!!l);continue}const r=String(l??"");if(r!==String(n.value))if(n.type==="number"){const s=Number(r);if(r.trim()===""||!Number.isFinite(s)){i[n.key]="A number, please.";continue}if(n.min!=null&&s<n.min){i[n.key]=`At least ${n.min}.`;continue}if(n.max!=null&&s>n.max){i[n.key]=`At most ${n.max}.`;continue}t[n.key]=s}else if(n.type==="json")try{t[n.key]=JSON.parse(r)}catch{i[n.key]="Not valid JSON."}else t[n.key]=r}return{errors:i,changed:t}}export function slugifyPlugin(e){return String(e??"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^[^a-z]+/,"").replace(/-+$/g,"").slice(0,40).replace(/-+$/,"")}export function checkScaffold(e,a=[]){const i=r=>String(e?.[r]??"").trim(),t={displayName:i("displayName"),slug:i("slug"),description:i("description"),author:i("author"),icon:i("icon")||"box"},n={},l=/[<>"\\]|\*\//;t.displayName?t.displayName.length>60&&(n.displayName="60 characters at most."):n.displayName="A plugin needs a name.",/^[a-z][a-z0-9-]{1,39}$/.test(t.slug)?a.includes(t.slug)&&(n.slug=`"${t.slug}" is already installed.`):n.slug="2-40 characters: lowercase letters, digits and hyphens, starting with a letter.",t.description.length>300&&(n.description="300 characters at most."),t.author.length>80&&(n.author="80 characters at most."),/^[a-z][a-z0-9-]{0,39}$/.test(t.icon)||(n.icon="A lowercase icon name (letters, digits, hyphens).");for(const r of["displayName","description","author"])!n[r]&&l.test(t[r])&&(n[r]='No <, >, ", backslash or */.');return{errors:n,payload:t}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const BASE_ROLES=["super-admin","admin","user"],FILTERS=["all","built-in","custom","plugins"],NAME_RE=/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/,BADGE_OPTIONS=[{value:"badge-danger",label:"Red (Admin)"},{value:"badge-warning",label:"Orange (Manager)"},{value:"badge-info",label:"Blue (Editor)"},{value:"badge-secondary",label:"Grey (Subscriber)"},{value:"badge-success",label:"Green"},{value:"badge-primary",label:"Primary"}];const s=(e,l,t=`${l}s`)=>`${e} ${e===1?l:t}`;export function slugify(e){return String(e??"").toLowerCase().trim().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"").replace(/-{2,}/g,"-")}export function permSummary(e){if(!Array.isArray(e)||!e.length)return"no permissions";const l=new Set(e.map(a=>String(a).split(".")[0])),t=e.every(a=>!String(a).includes("."));return`${s(l.size,"resource")} (${t?"full":"partial"})`}export function scopeText(e){const l=Array.isArray(e?.allow)?e.allow.filter(Boolean):[];return l.length?l.length===1?`Admin: ${l[0]} only`:`Admin: ${l.length} screens only`:""}export function roleKind(e){return e?.plugin?"plugins":BASE_ROLES.includes(e?.name)||e?.level===0?"built-in":"custom"}export function deleteBlock(e){return e?.level===0?"The root role holds every permission and cannot be deleted":BASE_ROLES.includes(e?.name)?"A built-in role cannot be deleted":e?.plugin?`Belongs to the ${e.plugin} plugin - it goes when the plugin is uninstalled`:""}export function arrangeRoles(e,{filter:l="all",query:t="",holders:a={}}={}){const r=String(t).trim().toLowerCase();return(e||[]).map(n=>({id:n.id,...n.data||{}})).filter(n=>n.name).filter(n=>l==="all"||roleKind(n)===l).filter(n=>!r||[n.name,n.label,n.plugin].some(i=>String(i||"").toLowerCase().includes(r))).sort((n,i)=>(n.level??99)-(i.level??99)||String(n.name).localeCompare(String(i.name))).map(n=>roleRow(n,a))}export function roleRow(e,l={}){const t=l[e.name]??null,a=e.level===0;return{id:e.id,name:e.name,label:e.label||e.name,badgeClass:/^badge-[a-z]+$/.test(e.badgeClass||"")?e.badgeClass:"badge-secondary",level:String(e.level??""),levelText:a?"Level 0 \xB7 everything":`Level ${e.level??"?"}`,meta:a?"every permission, plugins included":permSummary(e.permissions),holdersText:t===null?"":s(t,"user"),unheld:t===0&&!a,plugin:e.plugin||"",scope:scopeText(e.admin),builtIn:roleKind(e)==="built-in",project:e.meta?.project||""}}export function filterCounts(e){const l={all:0,"built-in":0,custom:0,plugins:0};for(const t of e||[]){const a=t.data||{};a.name&&(l.all++,l[roleKind(a)]++)}return l}export function checkNewRole(e,{taken:l=[],project:t=""}={}){const a=e||{},r={},n=String(a.name??"").trim(),i=String(a.label??"").trim(),o=Number.parseInt(a.level,10);NAME_RE.test(n)?l.includes(n)&&(r.name=`"${n}" is already a role.`):r.name="Lowercase letters, numbers and hyphens; no hyphen at either end.",i||(r.label="A role needs a label."),(!Number.isFinite(o)||o<1)&&(r.level="Level 1 or more - level 0 is the root role's.");const u=BADGE_OPTIONS.some(c=>c.value===a.badgeClass)?a.badgeClass:"badge-secondary";return{errors:r,payload:{name:n,label:i,level:Number.isFinite(o)?o:1,permissions:[],badgeClass:u,...t&&t!=="core"&&{meta:{project:t}}}}}export function duplicateOf(e,l=[]){let t=`${e.name}-copy`;for(let o=2;l.includes(t);o++)t=`${e.name}-copy-${o}`;const{id:a,plugin:r,grantedBy:n,...i}=e;return{...i,name:t,label:`${e.label||e.name} (copy)`,level:Math.max(1,Number(e.level)||1)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const LIMITS={maxResults:[1,50],minQueryLength:[1,5],debounceMs:[100,1e3],offsetX:[0,500],offsetY:[0,500],iconSize:[32,96]},DEFAULTS={enabled:!0,placeholder:"Search pages...",keyboardShortcut:!0,maxResults:10,minQueryLength:2,debounceMs:300,displayMode:"navbar",position:"bottom-right",offsetX:32,offsetY:32,iconOnly:!0,iconSize:52,iconRadius:26},POSITIONS=["top-left","top-right","left-center","right-center","bottom-left","bottom-right"],POSITION_LABEL={"top-left":"Top left","top-right":"Top right","left-center":"Left side","right-center":"Right side","bottom-left":"Bottom left","bottom-right":"Bottom right"};const u={maxResults:"Max results",minQueryLength:"Minimum query length",debounceMs:"Debounce delay",offsetX:"Horizontal offset",offsetY:"Vertical offset",iconSize:"Icon size"};export const maxRadius=e=>Math.floor((Number(e)||DEFAULTS.iconSize)/2);export function radiusForSize(e,t,o){const n=Number(e)||0,s=maxRadius(o);return n>=maxRadius(t)||n>s?s:n}const r=e=>{const t=typeof e=="number"?e:parseInt(e,10);return Number.isFinite(t)?Math.round(t):null},a=(e,[t,o])=>Math.min(o,Math.max(t,e));export function formFromSettings(e){const t={...DEFAULTS,...e&&typeof e=="object"?e:{}};return{enabled:t.enabled!==!1,placeholder:String(t.placeholder??DEFAULTS.placeholder),keyboardShortcut:t.keyboardShortcut!==!1,maxResults:String(t.maxResults),minQueryLength:String(t.minQueryLength),debounceMs:String(t.debounceMs),displayMode:t.displayMode==="floating"?"floating":"navbar",position:POSITIONS.includes(t.position)?t.position:DEFAULTS.position,offsetX:String(t.offsetX),offsetY:String(t.offsetY),iconOnly:t.iconOnly!==!1,iconSize:String(t.iconSize),iconRadius:String(t.iconRadius)}}export function payloadFromForm(e){const t={...formFromSettings(null),...e||{}},o=i=>{const l=r(t[i]);return l===null?DEFAULTS[i]:a(l,LIMITS[i])},n=o("iconSize"),s=r(t.iconRadius);return{enabled:t.enabled!==!1,placeholder:String(t.placeholder??"").trim().slice(0,120)||DEFAULTS.placeholder,keyboardShortcut:t.keyboardShortcut!==!1,maxResults:o("maxResults"),minQueryLength:o("minQueryLength"),debounceMs:o("debounceMs"),displayMode:t.displayMode==="floating"?"floating":"navbar",position:POSITIONS.includes(t.position)?t.position:DEFAULTS.position,offsetX:o("offsetX"),offsetY:o("offsetY"),iconOnly:t.iconOnly!==!1,iconSize:n,iconRadius:s===null?Math.min(DEFAULTS.iconRadius,maxRadius(n)):a(s,[0,maxRadius(n)])}}export const snapshot=e=>JSON.stringify(payloadFromForm(e));export function clampNotes(e){const t=[];for(const[o,n]of Object.entries(LIMITS)){const s=String(e?.[o]??"").trim(),i=r(s);s===""||i===null?t.push(`${u[o]} is empty - it will be saved as ${DEFAULTS[o]}.`):(i<n[0]||i>n[1])&&t.push(`${u[o]} is ${n[0]}-${n[1]} - ${i} will be saved as ${a(i,n)}.`)}return t}export function liveChips(e,t=null){const o={...DEFAULTS,...e||{}};if(!o.enabled)return[{text:"Off - nothing is added to public pages",tone:"warn"}];const n=[{text:"On",tone:"ok"}];return n.push({text:o.displayMode==="floating"?`Floating button, ${(POSITION_LABEL[o.position]||o.position).toLowerCase()}`:"Icon in the navigation bar",tone:""}),o.keyboardShortcut&&n.push({text:"Ctrl+K / \u2318K",tone:""}),n.push({text:`Up to ${o.maxResults} results`,tone:""}),t&&n.push({text:`${t.searchable} page${t.searchable===1?"":"s"} searchable`,tone:""}),n}export function previewState(e,t){const o=String(e??"").trim(),n=r(t)??DEFAULTS.minQueryLength;return o?o.length<n?{run:!0,note:`Visitors need at least ${n} characters - on the site this would not search yet.`}:{run:!0,note:""}:{run:!1,note:"Type a word or two - results appear as you type."}}export function resultRow(e,t){return{id:`${t}|${e.url}`,url:String(e.url||"/"),title:String(e.title||"Untitled"),snippet:String(e.snippet||e.description||""),meta:[e.url,(e.tags||[]).length?`tags: ${(e.tags||[]).join(", ")}`:""].filter(Boolean).join(" \xB7 "),score:`score ${Number(e.score)||0}`,rank:String(t+1)}}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as p}from"../api.js";import{isPathAllowed as
|
|
1
|
+
import{api as p}from"../api.js";import{isPathAllowed as te}from"./admin-scope.js";import{colourToCss as X,applyFloat as ne,applyHighlight as se,itemHighlightAttrs as ie}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{placeTools as oe,ensureLockedTools as ae,managePluginsItem as re,stripItemByUrl as le,insertFoldersBeforeSystem as ce,pruneEmptyToolsFolder as de,MANAGE_PLUGINS_URL as K}from"./sidebar-grouping.js";function h(t){return String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}const ue=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Menus",url:"#/menus",icon:"menu",permission:"menus"}];function N(t,s){return s?!t||!t.length?!1:t.includes(s)?!0:["read","create","update","delete"].some(n=>t.includes(`${s}.${n}`)):!0}function V(t,s,n=null){const e=[];for(const i of t){if(i.hidden||i.permission&&!N(s,i.permission))continue;const o=typeof i.url=="string"?i.url:"";if(n&&o.startsWith("#/")&&!te(n,o.slice(1)))continue;const l=Array.isArray(i.items)&&i.items.length>0,a=l?V(i.items,s,n):[];l&&!a.length&&!o||e.push({...i,items:a})}return e}async function me(t){if(!N(t,"projects"))return[];let s=[];try{s=await p.projects.listWithCounts()}catch{return[]}if(!Array.isArray(s))return[];const n=[{key:"pages",text:"Pages",icon:"file-text",path:"/pages"},{key:"collections",text:"Collections",icon:"database",path:"/collections"},{key:"forms",text:"Forms",icon:"layout",path:"/forms"},{key:"actions",text:"Actions",icon:"zap",path:"/actions"},{key:"menus",text:"Menus",icon:"menu",path:"/menus"},{key:"blocks",text:"Blocks",icon:"box",path:"/blocks"},{key:"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 s.map(e=>{const o="#/projects/"+encodeURIComponent(e.slug),l=e.counts||{},a=[{text:"Overview",url:o,icon:e.icon||"folder"}];for(const u of n){const m=Number(l[u.key])||0;m<=0||a.push({text:`${u.text} (${m})`,url:o+u.path,icon:u.icon})}a.push({text:"Settings",url:o+"/settings",icon:"settings"});const c=e.enabled===!1;return{text:e.name||e.slug,icon:e.icon||"folder",items:a,...c&&{colour:"neutral",badge:{text:"off"}}}})}function fe(t,s){for(const n of t)if(!n.hidden&&(n.key==="projects"||!n.key&&(n.text||"").toLowerCase()==="projects")){n.items=Array.isArray(n.items)?n.items.slice():[],n.items.push(...s);return}}function G(t,s){const n=Y.includes(s.tone)?s.tone:"info",e=s.label||String(s.count);s.details?P.set(t,s.details):P.delete(t);const i=C.some(o=>o.url===t&&!o.inventory)?" data-live":"";return`<span class="sidebar-badge sidebar-badge--${n}" data-count-for="${h(t)}"${i} data-badge-label="${h(e)}" aria-label="${h(e)}">${h(String(s.count))}</span>`}function J(t){for(const s of t.querySelectorAll("details")){const n=s.querySelector(":scope > summary");if(!n)continue;let e=0,i=!1;const o=[];for(const m of s.querySelectorAll(":scope > .sidebar-children .sidebar-badge[data-live]")){const v=Number(m.textContent)||0;v&&(e+=v,i||(i=m.classList.contains("sidebar-badge--danger")),o.push({text:m.closest("a")?.querySelector(".sidebar-text")?.textContent.trim()??"",meta:m.dataset.badgeLabel,tone:m.classList.contains("sidebar-badge--danger")?"danger":void 0}))}const l=`folder:${s.dataset.stateKey||n.textContent.trim()}`;let a=n.querySelector(":scope > .sidebar-badge--sum");if(!e){a?.remove(),P.delete(l);continue}a||(a=document.createElement("span"),n.querySelector(".sidebar-text")?.after(a));const c=n.querySelector(".sidebar-text")?.textContent.trim()||"This folder",u=o.map(m=>`${m.text}: ${m.meta}`).join(" \xB7 ");a.className=`sidebar-badge sidebar-badge--sum sidebar-badge--${i?"danger":"info"}`,a.dataset.countFor=l,a.dataset.badgeLabel=u,a.setAttribute("aria-label",u),a.textContent=String(e),P.set(l,{title:c,items:o})}}const P=new Map;function pe(t,s){if(!t.url||!s)return"";const n=s[t.url];return!n||!(n.count>0)?"":G(t.url,n)}const Y=["info","attention","danger","live"],he={"#/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 U(t,{fallbackLabel:s="",tone:n="info"}={}){const e=Number(typeof t=="object"&&t!==null?t.count:t);if(!Number.isFinite(e)||e<0)return null;const i=typeof t?.label=="string"&&t.label||(s?`${e} - ${s}`:String(e)),o={count:Math.round(e),label:i,tone:Y.includes(t?.tone)?t.tone:n};return t?.details&&typeof t.details=="object"&&(o.details=t.details),o}function ge(t){const s=(t.text||"").replace(/\s*\(\d+\)\s*$/,"").toLowerCase().replace(/\s+/g,"-"),n=(t.url||"").match(/^#\/projects\/([^/]+)/);return`sidebar.expanded.${n?`project-${decodeURIComponent(n[1])}.`:""}${s}`}function Q(t,s){if(t&&t.type==="separator")return'<hr class="sidebar-divider">';if(t&&t.type==="spacer"){const y=["xs","sm","md","lg","xl","flex"].includes(t.size)?t.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${h(y)}" aria-hidden="true"></div>`}const n=t.icon?`<span data-icon="${h(t.icon)}"></span>`:"",e=h(t.text||""),i=t.url?h(t.url):"",o=Array.isArray(t.items)&&t.items.length>0,l=t.colour?X(t.colour):"",a=ie(t),c=[a.hover&&` data-menu-item-hover="${h(a.hover)}"`,a.active&&` data-menu-item-active="${h(a.active)}"`].filter(Boolean).join(""),u=[l&&`color:${h(l)}`,a.accent&&`--dm-menu-highlight:${h(a.accent)}`].filter(Boolean).join(";"),m=u?` style="${u}"`:"";let v="";if(t.badge&&t.badge.text!=null&&t.badge.text!==""){const y=t.badge.variant?X(t.badge.variant):"";v=`<span class="dm-menu-badge"${y?` style="background:${h(y)};color:var(--dm-text-inverse,#fff)"`:""}>${h(String(t.badge.text))}</span>`}if(o){const y=ge(t),k=S.get(y)!==!1,E=t.items.map(w=>Q(w,s)).join("");return`<details data-state-key="${h(y)}"${k?" open":""}>
|
|
2
2
|
<summary${m}>${n} <span class="sidebar-text">${e}</span>${v}</summary>
|
|
3
3
|
<div class="sidebar-children">${E}</div>
|
|
4
|
-
</details>`}return`<a href="${i}" class="sidebar-link" data-url="${i}"${m}${c}>${n} <span class="sidebar-text">${e}</span>${pe(t,s)}${v}</a>`}async function be(t=[],s=[]){const n=d=>d.then(g=>Array.isArray(g)?g.length:Array.isArray(g?.entries)?g.entries.length:0).catch(()=>0);C=xe(t);const e=new Set(C.map(d=>d.url)),i={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"},o=(d,g)=>z(s,d)&&!e.has(i[d])?g():Promise.resolve(0),[l,a,c,u,m,v,y,k,E,w,P,B,q,T]=await Promise.all([o("pages",()=>n(p.pages.list())),o("media",()=>n(p.media.list())),o("menus",()=>n(p.menus.list())),o("context-menus",()=>n(p.contextMenus.list())),o("theme",()=>p.theme.get().then(d=>(d?.overrides||[]).length).catch(()=>0)),o("collections",()=>n(p.collections.list())),o("forms",()=>n(p.forms.list())),o("views",()=>n(p.views.list())),o("actions",()=>n(p.actions.list())),o("blocks",()=>n(p.blocks.list())),o("components",()=>n(p.components.list())),o("users",()=>n(p.users.list())),o("plugins",()=>n(p.plugins.list())),o("notifications",()=>p.system?.notifications?.unreadCount?.().then(d=>d?.count??0).catch(()=>0)??Promise.resolve(0))]),j={};await Promise.all(C.map(async d=>{const g=await Q(d);g&&(j[d.url]=g)}));const I={"#/pages":l,"#/media":a,"#/menus":c,"#/context-menus":u,"#/theme":m,"#/collections":v,"#/forms":y,"#/views":k,"#/actions":E,"#/blocks":w,"#/components":P,"#/users":B,"#/plugins":q,"#/system/notifications":T},x={};for(const[d,g]of Object.entries(I)){if(e.has(d))continue;const[L,R]=he[d]??["item","items"],A=U(g);A&&(x[d]={...A,label:`${A.count} ${A.count===1?L:R}`})}return{...x,...j}}let C=[];const ye="dm:sidebar-count",ve=6e4;function xe(t=[]){const s=[],n=e=>{if(e){e.url&&typeof e.countUrl=="string"&&e.countUrl&&s.push({url:e.url,countUrl:e.countUrl,text:e.text||"",countLabel:e.countLabel||"",inventory:e.inventory===!0});for(const i of[...e.children||[],...e.items||[]])n(i)}};for(const e of t??[])n(e?.item??e);return s}async function Q(t){try{const s=new Date,n=`${s.getFullYear()}-${String(s.getMonth()+1).padStart(2,"0")}-${String(s.getDate()).padStart(2,"0")}`,e=t.countUrl+(t.countUrl.includes("?")?"&":"?")+`today=${n}`;return U(await H.get(e),{fallbackLabel:t.countLabel||t.text})}catch{return null}}function Z(t,s,n){const e=[...t.querySelectorAll("a.sidebar-link")].find(o=>o.dataset.url===s);if(!e)return;const i=e.querySelector(".sidebar-badge[data-count-for]");if(!n||!(n.count>0))i?.remove();else{const o=document.createRange();o.selectNodeContents(e);const l=o.createContextualFragment(V(s,n)).firstElementChild;i?i.replaceWith(l):e.querySelector(".sidebar-text")?.after(l)}O.set(s,n),G(t)}const O=new Map;async function W(t,s=null){const n=s?C.filter(e=>e.url===s):C;await Promise.all(n.map(async e=>{const i=await Q(e),o=O.get(e.url);JSON.stringify(o??null)!==JSON.stringify(i)&&Z(t,e.url,i)}))}function $e(t){t.dataset.liveCounts||(t.dataset.liveCounts="1",window.addEventListener(ye,s=>{const n=s.detail||{};if(n.url)if(n.count!==void 0){const e=C.find(i=>i.url===n.url);Z(t,n.url,U(n,{fallbackLabel:e?.countLabel||e?.text||""}))}else W(t,n.url)}),setInterval(()=>{!document.hidden&&document.body.contains(t)&&W(t)},ve),document.addEventListener("visibilitychange",()=>{document.hidden||W(t)}),Se(t))}function Ce(t,s,n){const e=(l,a,c)=>{const u=document.createElement(l);return a&&(u.className=a),c!=null&&(u.textContent=String(c)),u};if(!n){t.textContent=s;return}t.appendChild(e("strong","dm-badge-pop-title",n.title||s)),(n.rows?.length||!n.items?.length)&&t.appendChild(e("p","dm-badge-pop-sub",s));const i=Array.isArray(n.rows)?n.rows.slice(0,6):[];if(i.length){const l=e("dl","dm-badge-pop-rows");for(const a of i)l.appendChild(e("dt",null,a.label)),l.appendChild(e("dd",a.tone==="danger"?"is-danger":null,a.value));t.appendChild(l)}const o=Array.isArray(n.items)?n.items.slice(0,6):[];if(o.length){const l=e("ul","dm-badge-pop-items");for(const a of o){const c=e("li",a.tone==="danger"?"is-danger":null);c.appendChild(e("span","dm-badge-pop-text",a.text)),a.meta&&c.appendChild(e("span","dm-badge-pop-meta",a.meta)),l.appendChild(c)}t.appendChild(l)}else n.empty&&t.appendChild(e("p","dm-badge-pop-empty",n.empty))}function Se(t){let s=null,n=null;const e=()=>{clearTimeout(n),s?.remove(),s=null},i=o=>{e();const l=o?.dataset.badgeLabel;l&&(n=setTimeout(()=>{s=document.createElement("div"),s.className="dm-help-pop dm-badge-pop",s.setAttribute("role","tooltip"),Ce(s,l,N.get(o.dataset.countFor)),document.body.appendChild(s);const a=o.getBoundingClientRect(),c=s.getBoundingClientRect(),u=Math.min(a.right+10,innerWidth-c.width-8);s.style.left=`${Math.max(8,u)}px`,s.style.top=`${Math.max(8,Math.min(a.top+a.height/2-c.height/2,innerHeight-c.height-8))}px`},150))};t.addEventListener("mouseover",o=>{const l=o.target.closest?.(".sidebar-badge[data-badge-label]");l&&i(l)}),t.addEventListener("mouseout",o=>{o.target.closest?.(".sidebar-badge[data-badge-label]")&&e()}),t.addEventListener("focusin",o=>{const l=o.target.closest?.("a, summary")?.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");l?i(l):e()}),t.addEventListener("focusout",e),window.addEventListener("scroll",e,!0),window.addEventListener("hashchange",e)}async function we(){try{const t=await p.settings.get();return t?.adminBrand?.title||t?.title||"Admin"}catch{return"Admin"}}export async function renderAdminSidebar({mount:t,permissions:s,adminScope:n=null}){const e=$(t).get(0);if(!e)return;let i,o=null,l=null,a=null,c=null,u=null;try{const r=await p.get("/sidebar/menu");i=Array.isArray(r?.items)?r.items:null,o=r?.variant||null,l=r?.position||null,a=r?.style||null,c=r?.float||null,u=r?.highlight||null}catch{i=null}(!i||!i.some(r=>r&&(!r.hidden||r.restricted)))&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),i=ue.slice());let m=[];try{m=await H.get("/api/sidebar/registered-items")||[]}catch{}i=re(i,de),i=ie(i,m),i=oe(i),i=le(i,[ae()]);const v=await me(s);v.length&&fe(i,v),i=K(i,s,n),i=ce(i);const[y,k]=await Promise.all([be(m.filter(r=>z(s,r?.item?.permission)),s).catch(()=>({})),we()]),E=o?` dm-admin-sidebar--${h(o)}`:"";function w(r,b="px"){if(r==null)return r;const f=String(r).trim();return/^\d+(\.\d+)?$/.test(f)?`${f}${b}`:f}let P="";if(a){const r=[];a.fontFamily&&r.push(`font-family: ${a.fontFamily}, sans-serif`),a.fontSize&&r.push(`font-size: ${w(a.fontSize)}`),a.fontWeight&&r.push(`font-weight: ${a.fontWeight}`),a.letterSpacing&&r.push(`letter-spacing: ${w(a.letterSpacing,"em")}`);const b=[];if(r.length&&b.push(`#admin-sidebar .dm-admin-sidebar, #admin-sidebar .dm-admin-sidebar .sidebar-link, #admin-sidebar .dm-admin-sidebar summary, #admin-sidebar .dm-admin-sidebar .sidebar-text { ${r.join("; ")} }`),a.iconSize){const f=w(a.iconSize);b.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${f} !important; height: ${f} !important; }`)}b.length&&(P=`<style data-admin-sidebar-style>${b.join(" ")}</style>`)}const B=`<div class="dm-admin-sidebar-header"><span data-icon="layout"></span> ${h(k)}</div>`,q=i.some(r=>r&&r.type==="spacer"&&r.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",T=`${P}<nav class="dm-admin-sidebar${E}${q}">${B}${i.map(r=>Y(r,y)).join("")}</nav>`,j=document.createRange();j.selectNodeContents(e);const I=j.createContextualFragment(T);e.replaceChildren(I),Domma.icons.scan(e);for(const r of C)O.set(r.url,y[r.url]??null);G(e),$e(e);const x=Number.parseInt(S.get("sidebar.width"),10);Number.isFinite(x)&&x>=180&&x<=480&&(e.style.width=x+"px"),e.removeAttribute("data-menu-hover"),e.removeAttribute("data-menu-active"),e.style.removeProperty("--dm-menu-highlight"),ne(e,{highlight:u});const d=document.querySelector(".dashboard-wrapper");if(["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(r=>{e.style[r]=""}),e.classList.remove("dm-menu-floating"),d&&(d.style.paddingLeft="",d.style.paddingRight=""),l==="floating"){te(e,{position:"floating",float:c||{}});const b=c&&c.anchor||"TL",f=Number.isFinite(Number(c?.offsetX))?Number(c.offsetX):16,F=e.getBoundingClientRect().width||x||260;if(d){const _=Math.round(F+f*2)+"px";b==="R"||b==="TR"||b==="BR"?d.style.paddingRight=_:d.style.paddingLeft=_}}const g=document.createElement("div");g.className="dm-admin-sidebar-handle",g.setAttribute("aria-label","Resize sidebar"),e.appendChild(g),e.querySelectorAll("details").forEach(r=>{r.addEventListener("toggle",function(){const b=this.getAttribute("data-state-key");b&&S.set(b,this.open);const f=this.querySelector(":scope > .sidebar-children");if(f)if(this.open){const F=f.scrollHeight;f.style.maxHeight="0",requestAnimationFrame(()=>{f.style.maxHeight=F+"px",f.addEventListener("transitionend",function _(){f.style.maxHeight="none",f.removeEventListener("transitionend",_)})})}else{const F=f.scrollHeight;f.style.maxHeight=F+"px",requestAnimationFrame(()=>{f.style.maxHeight="0"})}})});let L=!1,R=0,A=0;g.addEventListener("mousedown",r=>{L=!0,R=r.clientX,A=e.getBoundingClientRect().width,document.body.style.cursor="col-resize",document.body.style.userSelect="none",r.preventDefault()}),e.dataset.resizeWired||(e.dataset.resizeWired="1",document.addEventListener("mousemove",r=>e.__dmResize?.move(r)),document.addEventListener("mouseup",()=>e.__dmResize?.up()),M.subscribe("router:afterChange",()=>e.__dmSetActive?.())),e.__dmResize={move(r){if(!L)return;const b=Math.max(180,Math.min(480,A+(r.clientX-R)));e.style.width=b+"px"},up(){L&&(L=!1,document.body.style.cursor="",document.body.style.userSelect="",S.set("sidebar.width",parseInt(e.style.width,10)))}};function D(){const r=location.hash||"#/";$(e).find(".sidebar-link").removeClass("active"),$(e).find(`.sidebar-link[data-url="${r}"]`).addClass("active")}D(),e.__dmSetActive=D}
|
|
4
|
+
</details>`}return`<a href="${i}" class="sidebar-link" data-url="${i}"${m}${c}>${n} <span class="sidebar-text">${e}</span>${pe(t,s)}${v}</a>`}async function be(t=[],s=[]){const n=d=>d.then(g=>Array.isArray(g)?g.length:Array.isArray(g?.entries)?g.entries.length:0).catch(()=>0);C=xe(t);const e=new Set(C.map(d=>d.url)),i={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"},o=(d,g)=>N(s,d)&&!e.has(i[d])?g():Promise.resolve(0),[l,a,c,u,m,v,y,k,E,w,R,B,q,T]=await Promise.all([o("pages",()=>n(p.pages.list())),o("media",()=>n(p.media.list())),o("menus",()=>n(p.menus.list())),o("context-menus",()=>n(p.contextMenus.list())),o("theme",()=>p.theme.get().then(d=>(d?.overrides||[]).length).catch(()=>0)),o("collections",()=>n(p.collections.list())),o("forms",()=>n(p.forms.list())),o("views",()=>n(p.views.list())),o("actions",()=>n(p.actions.list())),o("blocks",()=>n(p.blocks.list())),o("components",()=>n(p.components.list())),o("users",()=>n(p.users.list())),o("plugins",()=>n(p.plugins.list())),o("notifications",()=>p.system?.notifications?.unreadCount?.().then(d=>d?.count??0).catch(()=>0)??Promise.resolve(0))]),j={};await Promise.all(C.map(async d=>{const g=await Z(d);g&&(j[d.url]=g)}));const I={"#/pages":l,"#/media":a,"#/menus":c,"#/context-menus":u,"#/theme":m,"#/collections":v,"#/forms":y,"#/views":k,"#/actions":E,"#/blocks":w,"#/components":R,"#/users":B,"#/plugins":q,"#/system/notifications":T},x={};for(const[d,g]of Object.entries(I)){if(e.has(d))continue;const[L,_]=he[d]??["item","items"],A=U(g);A&&(x[d]={...A,label:`${A.count} ${A.count===1?L:_}`})}return{...x,...j}}let C=[];const ye="dm:sidebar-count",ve=6e4;function xe(t=[]){const s=[],n=e=>{if(e){e.url&&typeof e.countUrl=="string"&&e.countUrl&&s.push({url:e.url,countUrl:e.countUrl,text:e.text||"",countLabel:e.countLabel||"",inventory:e.inventory===!0});for(const i of[...e.children||[],...e.items||[]])n(i)}};for(const e of t??[])n(e?.item??e);return s}async function Z(t){try{const s=new Date,n=`${s.getFullYear()}-${String(s.getMonth()+1).padStart(2,"0")}-${String(s.getDate()).padStart(2,"0")}`,e=t.countUrl+(t.countUrl.includes("?")?"&":"?")+`today=${n}`;return U(await H.get(e),{fallbackLabel:t.countLabel||t.text})}catch{return null}}function ee(t,s,n){const e=[...t.querySelectorAll("a.sidebar-link")].find(o=>o.dataset.url===s);if(!e)return;const i=e.querySelector(".sidebar-badge[data-count-for]");if(!n||!(n.count>0))i?.remove();else{const o=document.createRange();o.selectNodeContents(e);const l=o.createContextualFragment(G(s,n)).firstElementChild;i?i.replaceWith(l):e.querySelector(".sidebar-text")?.after(l)}O.set(s,n),J(t)}const O=new Map;async function W(t,s=null){const n=s?C.filter(e=>e.url===s):C;await Promise.all(n.map(async e=>{const i=await Z(e),o=O.get(e.url);JSON.stringify(o??null)!==JSON.stringify(i)&&ee(t,e.url,i)}))}function $e(t){t.dataset.liveCounts||(t.dataset.liveCounts="1",window.addEventListener(ye,s=>{const n=s.detail||{};if(n.url)if(n.count!==void 0){const e=C.find(i=>i.url===n.url);ee(t,n.url,U(n,{fallbackLabel:e?.countLabel||e?.text||""}))}else W(t,n.url)}),setInterval(()=>{!document.hidden&&document.body.contains(t)&&W(t)},ve),document.addEventListener("visibilitychange",()=>{document.hidden||W(t)}),Se(t))}function Ce(t,s,n){const e=(l,a,c)=>{const u=document.createElement(l);return a&&(u.className=a),c!=null&&(u.textContent=String(c)),u};if(!n){t.textContent=s;return}t.appendChild(e("strong","dm-badge-pop-title",n.title||s)),(n.rows?.length||!n.items?.length)&&t.appendChild(e("p","dm-badge-pop-sub",s));const i=Array.isArray(n.rows)?n.rows.slice(0,6):[];if(i.length){const l=e("dl","dm-badge-pop-rows");for(const a of i)l.appendChild(e("dt",null,a.label)),l.appendChild(e("dd",a.tone==="danger"?"is-danger":null,a.value));t.appendChild(l)}const o=Array.isArray(n.items)?n.items.slice(0,6):[];if(o.length){const l=e("ul","dm-badge-pop-items");for(const a of o){const c=e("li",a.tone==="danger"?"is-danger":null);c.appendChild(e("span","dm-badge-pop-text",a.text)),a.meta&&c.appendChild(e("span","dm-badge-pop-meta",a.meta)),l.appendChild(c)}t.appendChild(l)}else n.empty&&t.appendChild(e("p","dm-badge-pop-empty",n.empty))}function Se(t){let s=null,n=null;const e=()=>{clearTimeout(n),s?.remove(),s=null},i=o=>{e();const l=o?.dataset.badgeLabel;l&&(n=setTimeout(()=>{s=document.createElement("div"),s.className="dm-help-pop dm-badge-pop",s.setAttribute("role","tooltip"),Ce(s,l,P.get(o.dataset.countFor)),document.body.appendChild(s);const a=o.getBoundingClientRect(),c=s.getBoundingClientRect(),u=Math.min(a.right+10,innerWidth-c.width-8);s.style.left=`${Math.max(8,u)}px`,s.style.top=`${Math.max(8,Math.min(a.top+a.height/2-c.height/2,innerHeight-c.height-8))}px`},150))};t.addEventListener("mouseover",o=>{const l=o.target.closest?.(".sidebar-badge[data-badge-label]");l&&i(l)}),t.addEventListener("mouseout",o=>{o.target.closest?.(".sidebar-badge[data-badge-label]")&&e()}),t.addEventListener("focusin",o=>{const l=o.target.closest?.("a, summary")?.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");l?i(l):e()}),t.addEventListener("focusout",e),window.addEventListener("scroll",e,!0),window.addEventListener("hashchange",e)}async function we(t=[]){if(!N(t,"settings"))return"Admin";try{const s=await p.settings.get();return s?.adminBrand?.title||s?.title||"Admin"}catch{return"Admin"}}export async function renderAdminSidebar({mount:t,permissions:s,adminScope:n=null}){const e=$(t).get(0);if(!e)return;let i,o=null,l=null,a=null,c=null,u=null;try{const r=await p.get("/sidebar/menu");i=Array.isArray(r?.items)?r.items:null,o=r?.variant||null,l=r?.position||null,a=r?.style||null,c=r?.float||null,u=r?.highlight||null}catch{i=null}(!i||!i.some(r=>r&&(!r.hidden||r.restricted)))&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),i=ue.slice());let m=[];try{m=await H.get("/api/sidebar/registered-items")||[]}catch{}i=le(i,K),i=oe(i,m.filter(r=>(r?.item??r)?.url!==K)),i=ae(i),i=ce(i,[re()]);const v=await me(s);v.length&&fe(i,v),i=V(i,s,n),i=de(i);const[y,k]=await Promise.all([be(m.filter(r=>N(s,r?.item?.permission)),s).catch(()=>({})),we(s)]),E=o?` dm-admin-sidebar--${h(o)}`:"";function w(r,b="px"){if(r==null)return r;const f=String(r).trim();return/^\d+(\.\d+)?$/.test(f)?`${f}${b}`:f}let R="";if(a){const r=[];a.fontFamily&&r.push(`font-family: ${a.fontFamily}, sans-serif`),a.fontSize&&r.push(`font-size: ${w(a.fontSize)}`),a.fontWeight&&r.push(`font-weight: ${a.fontWeight}`),a.letterSpacing&&r.push(`letter-spacing: ${w(a.letterSpacing,"em")}`);const b=[];if(r.length&&b.push(`#admin-sidebar .dm-admin-sidebar, #admin-sidebar .dm-admin-sidebar .sidebar-link, #admin-sidebar .dm-admin-sidebar summary, #admin-sidebar .dm-admin-sidebar .sidebar-text { ${r.join("; ")} }`),a.iconSize){const f=w(a.iconSize);b.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${f} !important; height: ${f} !important; }`)}b.length&&(R=`<style data-admin-sidebar-style>${b.join(" ")}</style>`)}const B=`<div class="dm-admin-sidebar-header"><span data-icon="layout"></span> ${h(k)}</div>`,q=i.some(r=>r&&r.type==="spacer"&&r.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",T=`${R}<nav class="dm-admin-sidebar${E}${q}">${B}${i.map(r=>Q(r,y)).join("")}</nav>`,j=document.createRange();j.selectNodeContents(e);const I=j.createContextualFragment(T);e.replaceChildren(I),Domma.icons.scan(e);for(const r of C)O.set(r.url,y[r.url]??null);J(e),$e(e);const x=Number.parseInt(S.get("sidebar.width"),10);Number.isFinite(x)&&x>=180&&x<=480&&(e.style.width=x+"px"),e.removeAttribute("data-menu-hover"),e.removeAttribute("data-menu-active"),e.style.removeProperty("--dm-menu-highlight"),se(e,{highlight:u});const d=document.querySelector(".dashboard-wrapper");if(["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(r=>{e.style[r]=""}),e.classList.remove("dm-menu-floating"),d&&(d.style.paddingLeft="",d.style.paddingRight=""),l==="floating"){ne(e,{position:"floating",float:c||{}});const b=c&&c.anchor||"TL",f=Number.isFinite(Number(c?.offsetX))?Number(c.offsetX):16,F=e.getBoundingClientRect().width||x||260;if(d){const z=Math.round(F+f*2)+"px";b==="R"||b==="TR"||b==="BR"?d.style.paddingRight=z:d.style.paddingLeft=z}}const g=document.createElement("div");g.className="dm-admin-sidebar-handle",g.setAttribute("aria-label","Resize sidebar"),e.appendChild(g),e.querySelectorAll("details").forEach(r=>{r.addEventListener("toggle",function(){const b=this.getAttribute("data-state-key");b&&S.set(b,this.open);const f=this.querySelector(":scope > .sidebar-children");if(f)if(this.open){const F=f.scrollHeight;f.style.maxHeight="0",requestAnimationFrame(()=>{f.style.maxHeight=F+"px",f.addEventListener("transitionend",function z(){f.style.maxHeight="none",f.removeEventListener("transitionend",z)})})}else{const F=f.scrollHeight;f.style.maxHeight=F+"px",requestAnimationFrame(()=>{f.style.maxHeight="0"})}})});let L=!1,_=0,A=0;g.addEventListener("mousedown",r=>{L=!0,_=r.clientX,A=e.getBoundingClientRect().width,document.body.style.cursor="col-resize",document.body.style.userSelect="none",r.preventDefault()}),e.dataset.resizeWired||(e.dataset.resizeWired="1",document.addEventListener("mousemove",r=>e.__dmResize?.move(r)),document.addEventListener("mouseup",()=>e.__dmResize?.up()),M.subscribe("router:afterChange",()=>e.__dmSetActive?.())),e.__dmResize={move(r){if(!L)return;const b=Math.max(180,Math.min(480,A+(r.clientX-_)));e.style.width=b+"px"},up(){L&&(L=!1,document.body.style.cursor="",document.body.style.userSelect="",S.set("sidebar.width",parseInt(e.style.width,10)))}};function D(){const r=location.hash||"#/";$(e).find(".sidebar-link").removeClass("active"),$(e).find(`.sidebar-link[data-url="${r}"]`).addClass("active")}D(),e.__dmSetActive=D}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const TAB_FIELDS={general:["title","tagline","adminHome","spacerSize"],seo:["seoTitle","seoSeparator","seoDescription"],footer:["footerCopyright","footerMotionToggle","socialTwitter","socialFacebook","socialInstagram","socialLinkedin","socialGithub","socialYoutube"],email:["smtpHost","smtpPort","smtpUser","smtpPass","smtpSecure","smtpFromAddress","smtpFromName"],backToTop:["bttEnabled","bttSmooth","bttLabel","bttThreshold","bttPosition","bttOffset","bttBottomOffset"],cookies:["ccEnabled","ccMessage","ccAcceptAll","ccRejectAll","ccCustomize","ccSavePrefs","ccPrivacyText","ccPrivacyUrl","ccCookieText","ccCookieUrl","ccPosition","ccLayout","ccTheme","ccShowFunctional","ccShowAnalytics","ccShowMarketing","ccVersion"],breadcrumbs:["bcEnabled","bcHomeLabel","bcPosition","bcOffsetX","bcOffsetY"],css:["css"]},SECRET_FIELDS=["smtpPass"],BREADCRUMB_POSITIONS=["TL","TR","BL","BR"];const t=(e,c="")=>e==null?c:String(e),r=(e,c)=>{const s=Number.parseInt(e,10);return Number.isFinite(s)?s:c};export function homeValue(e){return/^\/[A-Za-z0-9\-_/]*$/.test(e||"")&&String(e).replace(/\/+$/,"")||"/"}export function formFromSettings(e={},c=""){const s=e.backToTop||{},o=e.cookieConsent||{},i=e.breadcrumbs||{};return{title:t(e.title),tagline:t(e.tagline),adminHome:homeValue(e.adminHome),spacerSize:t(e.layoutOptions?.spacerSize??40),seoTitle:t(e.seo?.defaultTitle),seoSeparator:t(e.seo?.titleSeparator," | "),seoDescription:t(e.seo?.defaultDescription),footerCopyright:t(e.footer?.copyright),footerMotionToggle:e.footer?.showMotionToggle!==!1,socialTwitter:t(e.social?.twitter),socialFacebook:t(e.social?.facebook),socialInstagram:t(e.social?.instagram),socialLinkedin:t(e.social?.linkedin),socialGithub:t(e.social?.github),socialYoutube:t(e.social?.youtube),smtpHost:t(e.smtp?.host),smtpPort:t(e.smtp?.port||587),smtpUser:t(e.smtp?.user),smtpPass:"",smtpSecure:e.smtp?.secure===!0,smtpFromAddress:t(e.smtp?.fromAddress),smtpFromName:t(e.smtp?.fromName),bttEnabled:s.enabled!==!1,bttSmooth:s.smooth!==!1,bttLabel:t(s.label),bttThreshold:t(s.scrollThreshold??300),bttPosition:t(s.position,"bottom-right"),bttOffset:t(s.offset??32),bttBottomOffset:t(s.bottomOffset??s.offset??32),ccEnabled:o.enabled!==!1,ccMessage:t(o.message),ccAcceptAll:t(o.acceptAllText,"Accept All"),ccRejectAll:t(o.rejectAllText,"Reject All"),ccCustomize:t(o.customizeText,"Customize"),ccSavePrefs:t(o.savePreferencesText,"Save Preferences"),ccPrivacyText:t(o.privacyPolicyText,"Privacy Policy"),ccPrivacyUrl:t(o.privacyPolicyUrl),ccCookieText:t(o.cookiePolicyText,"Cookie Policy"),ccCookieUrl:t(o.cookiePolicyUrl),ccPosition:t(o.position,"bottom"),ccLayout:t(o.layout,"bar"),ccTheme:t(o.theme,"dark"),ccShowFunctional:o.showFunctional!==!1,ccShowAnalytics:o.showAnalytics!==!1,ccShowMarketing:o.showMarketing!==!1,ccVersion:t(o.consentVersion,"1.0"),bcEnabled:i.enabled===!0,bcHomeLabel:t(i.homeLabel,"Home"),bcPosition:BREADCRUMB_POSITIONS.includes(i.position)?i.position:"TL",bcOffsetX:t(i.offsetX??8),bcOffsetY:t(i.offsetY??8),css:t(c)}}export function payloadFromForm(e,c={},{adminHomeReady:s=!0}={}){const o=i=>t(i).trim();return{title:o(e.title),tagline:o(e.tagline),...s&&{adminHome:o(e.adminHome)||"/"},layoutOptions:{spacerSize:r(e.spacerSize,40)||40},seo:{defaultTitle:o(e.seoTitle),titleSeparator:t(e.seoSeparator)||" | ",defaultDescription:o(e.seoDescription)},footer:{copyright:o(e.footerCopyright),showMotionToggle:e.footerMotionToggle===!0,links:c.footer?.links||[]},social:{twitter:o(e.socialTwitter),facebook:o(e.socialFacebook),instagram:o(e.socialInstagram),linkedin:o(e.socialLinkedin),github:o(e.socialGithub),youtube:o(e.socialYoutube)},smtp:{host:o(e.smtpHost),port:r(e.smtpPort,587)||587,user:o(e.smtpUser),pass:t(e.smtpPass),secure:e.smtpSecure===!0,fromAddress:o(e.smtpFromAddress),fromName:o(e.smtpFromName)},backToTop:{enabled:e.bttEnabled===!0,scrollThreshold:r(e.bttThreshold,300),position:t(e.bttPosition)||"bottom-right",offset:r(e.bttOffset,32),bottomOffset:r(e.bttBottomOffset,32),label:o(e.bttLabel),smooth:e.bttSmooth===!0},cookieConsent:{enabled:e.ccEnabled===!0,message:o(e.ccMessage),acceptAllText:o(e.ccAcceptAll),rejectAllText:o(e.ccRejectAll),customizeText:o(e.ccCustomize),savePreferencesText:o(e.ccSavePrefs),privacyPolicyText:o(e.ccPrivacyText),privacyPolicyUrl:o(e.ccPrivacyUrl),cookiePolicyText:o(e.ccCookieText),cookiePolicyUrl:o(e.ccCookieUrl),position:t(e.ccPosition)||"bottom",layout:t(e.ccLayout)||"bar",theme:t(e.ccTheme)||"dark",showFunctional:e.ccShowFunctional===!0,showAnalytics:e.ccShowAnalytics===!0,showMarketing:e.ccShowMarketing===!0,consentVersion:o(e.ccVersion)||"1.0"},breadcrumbs:{enabled:e.bcEnabled===!0,homeLabel:o(e.bcHomeLabel)||"Home",position:BREADCRUMB_POSITIONS.includes(e.bcPosition)?e.bcPosition:"TL",offsetX:r(e.bcOffsetX,8),offsetY:r(e.bcOffsetY,8)}}}const a=e=>typeof e=="boolean"?e:t(e);export function snapshot(e){return JSON.stringify(Object.keys(e).sort().map(c=>[c,a(e[c])]))}export function changedTabs(e,c){const s=[];for(const[o,i]of Object.entries(TAB_FIELDS))i.some(n=>a(e[n])!==a(c[n]))&&s.push(o);return s}export function draftOf(e){return Object.fromEntries(Object.entries(e).filter(([c])=>!SECRET_FIELDS.includes(c)))}export function settingsProblems(e){const c=[];return t(e.smtpHost).trim()?t(e.smtpFromAddress).trim()||c.push({tone:"warn",tab:"email",text:"No From address - mail has no sender and many servers refuse it."}):c.push({tone:"danger",tab:"email",text:"No SMTP host - mail goes to a throwaway test inbox and reaches no one (password resets, form notifications)."}),e.ccEnabled===!0&&!t(e.ccPrivacyUrl).trim()&&c.push({tone:"warn",tab:"cookies",text:"The cookie banner is on but links to no privacy policy."}),c}export function describeCacheKey(e){const c=/^page:(.*?):m\d+:role([^:]*):/.exec(String(e||""));return c?{path:c[1]||"/",role:c[2]||"anon",page:!0}:{path:String(e||""),role:"",page:!1}}export function expiresIn(e,c=Date.now()){if(e==null)return"no expiry";const s=e-c;return s<=0?"expired":s<6e4?`${Math.round(s/1e3)}s`:s<36e5?`${Math.round(s/6e4)}m`:`${Math.round(s/36e5)}h`}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const FILTERS=["all","active","inactive"],SORTS=["name","recent"];const f={"badge-danger":"danger","badge-warning":"warn","badge-success":"ok","badge-primary":"primary","badge-info":"primary"};export function roleIndex(e=[]){const t={};for(const a of e||[]){const i=a?.data||a||{};i.name&&(t[i.name]={label:i.label||i.name,level:Number.isFinite(i.level)?i.level:99,tone:f[i.badgeClass]||""})}return t}const d=(e,t)=>e[t]?.level??99;export function canManage(e,t,a={}){const i=!!e?.id&&e.id===t?.id,o=d(a,e?.role)<d(a,t?.role);return{self:i,edit:i||o,other:!i&&o}}export function initials(e){const t=i=>(String(i).match(/[\p{L}\p{N}]/u)||[""])[0],a=String(e?.name||"").split(/\s+/).map(t).filter(Boolean);return a.length?(a[0]+(a.length>1?a.at(-1):"")).toUpperCase():(t(e?.email||"")||"?").toUpperCase()}export function arrangeUsers(e,{filter:t="all",role:a="",query:i="",sort:o="name"}={}){const l=String(i).trim().toLowerCase(),c=(e||[]).filter(n=>t==="active"&&n.isActive===!1||t==="inactive"&&n.isActive!==!1||a&&n.role!==a&&!(Array.isArray(n.additionalRoles)&&n.additionalRoles.includes(a))?!1:l?[n.name,n.email,n.role,...n.additionalRoles||[],...n.projects||[]].some(r=>String(r||"").toLowerCase().includes(l)):!0),s=(n,r)=>String(n.name||n.email||"").localeCompare(String(r.name||r.email||""));return o==="recent"?c.sort((n,r)=>(n.lastLogin?0:1)-(r.lastLogin?0:1)||String(r.lastLogin||"").localeCompare(String(n.lastLogin||""))||s(n,r)):c.sort(s)}export function userRow(e,{roles:t={},me:a=null,when:i=o=>o}={}){const o=canManage(a,e,t),l=t[e.role]||{label:e.role||"no role",tone:""},c=(Array.isArray(e.additionalRoles)?e.additionalRoles:[]).filter(n=>n&&n!==e.role).map(n=>t[n]?.label||n),s=Array.isArray(e.projects)&&e.projects.length?e.projects:[];return{id:e.id,name:e.name||e.email||"Unnamed",initials:initials(e),meta:[e.email,e.lastLogin?`signed in ${i(e.lastLogin)}`:"never signed in"].filter(Boolean).join(" \xB7 "),roleLabel:l.label,roleTone:l.tone?`is-${l.tone}`:"",extraRoles:c.join(", "),scope:s.length?`Access: ${s.join(", ")}`:"",inactive:e.isActive===!1,self:o.self,locked:!o.edit,rowCls:[e.isActive===!1&&"is-off",!o.edit&&"is-locked"].filter(Boolean).join(" ")}}
|
|
@@ -1,13 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
<
|
|
1
|
+
<!-- API Tokens. Bound with M.applyBindings - see admin/js/views/api-tokens.js, which also inserts the row
|
|
2
|
+
template (the router renders a templateUrl through _.render, eating any {{…}}). No page heading:
|
|
3
|
+
the Tool banner above is the heading, and New token is a banner action. -->
|
|
4
|
+
<div class="atx">
|
|
5
|
+
<p class="atx-note">Project-scoped keys for the external collections API (<code>/api/v1/<collection></code>) and API Builder
|
|
6
|
+
endpoints set to token access.
|
|
7
|
+
<span data-help-title="API tokens" data-help="A token works only on collections in its own project, and only where a collection's API access is set to Token. The token itself is shown once, when it is made - only a fingerprint is kept, so a lost token is revoked and a new one made. Scopes narrow a token to some collections and verbs; an expiry date retires it on its own."></span></p>
|
|
8
8
|
|
|
9
|
-
<div class="card">
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
<div class="atx-card">
|
|
10
|
+
<div class="atx-bar">
|
|
11
|
+
<div class="atx-seg" role="group" aria-label="Show">
|
|
12
|
+
<button type="button" data-on-click="showAll" data-bind-class="filter.value === 'all' && 'is-on'">All <b data-bind-text="counts.value.all"></b></button>
|
|
13
|
+
<button type="button" data-on-click="showActive" data-bind-class="filter.value === 'active' && 'is-on'">Active <b data-bind-text="counts.value.active"></b></button>
|
|
14
|
+
<button type="button" data-on-click="showExpiring" data-bind-class="filter.value === 'expiring' && 'is-on'" data-bind-hidden="!counts.value.expiring">Expiring soon <b data-bind-text="counts.value.expiring"></b></button>
|
|
15
|
+
<button type="button" data-on-click="showExpired" data-bind-class="filter.value === 'expired' && 'is-on'" data-bind-hidden="!counts.value.expired">Expired <b data-bind-text="counts.value.expired"></b></button>
|
|
16
|
+
<button type="button" data-on-click="showDisabled" data-bind-class="filter.value === 'disabled' && 'is-on'" data-bind-hidden="!counts.value.disabled">Off <b data-bind-text="counts.value.disabled"></b></button>
|
|
17
|
+
</div>
|
|
18
|
+
<select class="form-input atx-project" aria-label="Project" data-model="project.value" data-bind-hidden="projects.value.length < 2">
|
|
19
|
+
<option value="">All projects</option>
|
|
20
|
+
</select>
|
|
21
|
+
<input type="search" class="form-input atx-search" placeholder="Search name, project, scope…" aria-label="Search tokens" data-model="query.value">
|
|
22
|
+
<span data-help-title="The list" data-help="Tokens that need attention come first: expired ones (every call with them is refused) and ones expiring within two weeks. Click a token to edit its name, scopes or expiry, or switch it off; right-click it (or ⋮, or Shift+F10) to do that, copy its ending, show only its project, or revoke it. A token is never shown again after it is made - only its last four characters."></span>
|
|
23
|
+
</div>
|
|
24
|
+
<p class="atx-note" data-bind-hidden="!loading.value">Loading…</p>
|
|
25
|
+
<p class="atx-note is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
|
|
26
|
+
<p class="atx-note" data-bind-hidden="loading.value || loadError.value || rows.value.length" data-bind-text="emptyText.value"></p>
|
|
27
|
+
<div class="atx-list" data-each="rows.value key=id"></div>
|
|
12
28
|
</div>
|
|
13
29
|
</div>
|
|
30
|
+
|
|
31
|
+
<style>
|
|
32
|
+
.atx { display: flex; flex-direction: column; gap: .75rem; }
|
|
33
|
+
.atx [hidden], .atx-form [hidden], .atx-secret [hidden] { display: none !important; }
|
|
34
|
+
.atx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
|
|
35
|
+
.atx-note.is-bad { color: var(--dm-danger); }
|
|
36
|
+
.atx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .6rem; }
|
|
37
|
+
.atx-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
|
|
38
|
+
.atx-search { flex: 1; min-width: 10rem; max-width: 22rem; }
|
|
39
|
+
.atx-project { width: auto; max-width: 14rem; }
|
|
40
|
+
.atx-seg { display: inline-flex; flex-wrap: wrap; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
|
|
41
|
+
.atx-seg button { border: 0; background: none; color: var(--dm-text); padding: .3rem .7rem; cursor: pointer; font: inherit; font-size: var(--dm-text-sm); }
|
|
42
|
+
.atx-seg button + button { border-left: 1px solid var(--dm-border); }
|
|
43
|
+
.atx-seg button b { font-weight: 500; opacity: .65; margin-left: .2rem; }
|
|
44
|
+
.atx-seg button:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); }
|
|
45
|
+
.atx-seg button.is-on { background: var(--dm-primary); color: var(--dm-primary-text); }
|
|
46
|
+
.atx-list { display: flex; flex-direction: column; }
|
|
47
|
+
.atx-row { display: flex; align-items: center; gap: .75rem; padding: .55rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
|
|
48
|
+
.atx-row:last-child { border-bottom: 0; }
|
|
49
|
+
.atx-row:hover, .atx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
|
|
50
|
+
.atx-icon { display: inline-flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; flex-shrink: 0;
|
|
51
|
+
border-radius: var(--dm-radius-md); color: var(--dm-primary); background: color-mix(in srgb, var(--dm-primary) 12%, transparent); }
|
|
52
|
+
.atx-row.is-off .atx-icon { color: var(--dm-text-muted); background: color-mix(in srgb, var(--dm-text-muted) 12%, transparent); }
|
|
53
|
+
.atx-row.is-off .atx-main strong { color: var(--dm-text-muted); }
|
|
54
|
+
.atx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
55
|
+
.atx-main strong, .atx-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
56
|
+
.atx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
|
|
57
|
+
.atx-main code { font-size: inherit; background: none; padding: 0; color: inherit; }
|
|
58
|
+
.atx-chips { display: inline-flex; gap: .3rem; flex-shrink: 0; }
|
|
59
|
+
.atx-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .5rem; border-radius: 999px; white-space: nowrap;
|
|
60
|
+
background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
|
|
61
|
+
.atx-chip:empty { display: none; }
|
|
62
|
+
.atx-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
|
|
63
|
+
.atx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
|
|
64
|
+
.atx-chip.is-danger { background: color-mix(in srgb, var(--dm-danger) 16%, transparent); color: var(--dm-danger); }
|
|
65
|
+
.atx-used { min-width: 6.5rem; text-align: right; color: var(--dm-text-muted); font-size: var(--dm-text-xs); white-space: nowrap; }
|
|
66
|
+
.atx-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); color: var(--dm-text-muted); border-radius: var(--dm-radius-sm); padding: 0 .3rem; }
|
|
67
|
+
.atx-kebab:hover { color: var(--dm-primary); }
|
|
68
|
+
/* The New / Edit slideover */
|
|
69
|
+
.atx-form { display: flex; flex-direction: column; gap: .85rem; }
|
|
70
|
+
.atx-form .form-label { display: flex; align-items: center; gap: .3rem; }
|
|
71
|
+
.atx-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
72
|
+
.atx-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
73
|
+
.atx-setting { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
|
|
74
|
+
.atx-setting .form-label { margin: 0; }
|
|
75
|
+
.atx-actions { display: flex; align-items: center; gap: .4rem; padding-top: .6rem; border-top: 1px solid var(--dm-border); }
|
|
76
|
+
.atx-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
|
|
77
|
+
.atx-spacer { flex: 1; }
|
|
78
|
+
.atx-fixed { padding: .45rem .6rem; border: 1px dashed var(--dm-border); border-radius: var(--dm-radius-sm); color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
|
|
79
|
+
/* The created token, shown once */
|
|
80
|
+
.atx-secret { display: flex; flex-direction: column; gap: .8rem; }
|
|
81
|
+
.atx-warn { margin: 0; padding: .6rem .75rem; border-radius: var(--dm-radius-md); color: var(--dm-text);
|
|
82
|
+
background: color-mix(in srgb, var(--dm-warning) 16%, transparent); border: 1px solid color-mix(in srgb, var(--dm-warning) 35%, transparent); }
|
|
83
|
+
.atx-token { display: block; padding: .65rem; word-break: break-all; user-select: all; border-radius: var(--dm-radius-sm);
|
|
84
|
+
background: var(--dm-surface-raised, color-mix(in srgb, var(--dm-text) 6%, transparent)); color: var(--dm-text); font-family: var(--dm-font-mono, monospace); }
|
|
85
|
+
@media (max-width: 640px) { .atx-used { display: none; } }
|
|
86
|
+
</style>
|