domma-cms 0.75.0 → 0.77.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.
Files changed (150) hide show
  1. package/CLAUDE.md +1 -1
  2. package/admin/js/api.js +1 -1
  3. package/admin/js/app.js +4 -4
  4. package/admin/js/lib/action-editor-arrange.js +1 -0
  5. package/admin/js/lib/api-endpoint-editor-arrange.js +2 -0
  6. package/admin/js/lib/api-tokens-arrange.js +3 -0
  7. package/admin/js/lib/block-editor-arrange.js +2 -0
  8. package/admin/js/lib/collection-editor-arrange.js +3 -0
  9. package/admin/js/lib/collection-entries-arrange.js +1 -0
  10. package/admin/js/lib/component-editor-arrange.js +27 -0
  11. package/admin/js/lib/context-menu-editor-arrange.js +1 -0
  12. package/admin/js/lib/dashboard-arrange.js +1 -0
  13. package/admin/js/lib/effects-arrange.js +3 -0
  14. package/admin/js/lib/form-editor-arrange.js +1 -0
  15. package/admin/js/lib/form-submissions-arrange.js +2 -0
  16. package/admin/js/lib/layouts-arrange.js +1 -0
  17. package/admin/js/lib/menu-editor-arrange.js +2 -0
  18. package/admin/js/lib/my-profile-arrange.js +1 -0
  19. package/admin/js/lib/notifications-arrange.js +1 -0
  20. package/admin/js/lib/page-editor-arrange.js +1 -0
  21. package/admin/js/lib/plugin-code-arrange.js +1 -0
  22. package/admin/js/lib/plugins-arrange.js +2 -0
  23. package/admin/js/lib/project-page-arrange.js +1 -0
  24. package/admin/js/lib/role-editor-arrange.js +3 -0
  25. package/admin/js/lib/roles-arrange.js +1 -0
  26. package/admin/js/lib/search-settings-arrange.js +1 -0
  27. package/admin/js/lib/sidebar-renderer.js +2 -2
  28. package/admin/js/lib/site-settings-arrange.js +1 -0
  29. package/admin/js/lib/user-editor-arrange.js +1 -0
  30. package/admin/js/lib/users-arrange.js +1 -0
  31. package/admin/js/lib/view-editor-arrange.js +14 -0
  32. package/admin/js/templates/action-editor.html +223 -147
  33. package/admin/js/templates/api-endpoint-editor.html +172 -117
  34. package/admin/js/templates/api-tokens.html +83 -10
  35. package/admin/js/templates/block-editor.html +194 -203
  36. package/admin/js/templates/collection-editor.html +191 -113
  37. package/admin/js/templates/collection-entries.html +99 -36
  38. package/admin/js/templates/component-editor.html +128 -126
  39. package/admin/js/templates/context-menu-editor.html +208 -148
  40. package/admin/js/templates/dashboard.html +184 -22
  41. package/admin/js/templates/effects.html +155 -72
  42. package/admin/js/templates/form-editor.html +41 -26
  43. package/admin/js/templates/form-submissions.html +78 -26
  44. package/admin/js/templates/layouts.html +126 -43
  45. package/admin/js/templates/menu-editor.html +57 -17
  46. package/admin/js/templates/menu-locations.html +54 -11
  47. package/admin/js/templates/my-profile.html +100 -12
  48. package/admin/js/templates/notifications.html +70 -10
  49. package/admin/js/templates/page-editor.html +47 -26
  50. package/admin/js/templates/plugin-code.html +91 -73
  51. package/admin/js/templates/plugins.html +100 -28
  52. package/admin/js/templates/project-detail.html +83 -145
  53. package/admin/js/templates/project-settings.html +88 -53
  54. package/admin/js/templates/role-editor.html +158 -83
  55. package/admin/js/templates/roles.html +78 -7
  56. package/admin/js/templates/search.html +193 -216
  57. package/admin/js/templates/settings.html +404 -472
  58. package/admin/js/templates/user-editor.html +150 -14
  59. package/admin/js/templates/users.html +69 -9
  60. package/admin/js/templates/view-editor.html +203 -180
  61. package/admin/js/templates/view-preview.html +45 -45
  62. package/admin/js/views/action-editor.js +7 -1
  63. package/admin/js/views/api-endpoint-editor.js +2 -3
  64. package/admin/js/views/api-tokens.js +62 -8
  65. package/admin/js/views/block-editor.js +14 -8
  66. package/admin/js/views/collection-editor.js +64 -5
  67. package/admin/js/views/collection-entries.js +50 -1
  68. package/admin/js/views/component-editor.js +2 -28
  69. package/admin/js/views/context-menu-editor.js +54 -55
  70. package/admin/js/views/dashboard.js +7 -1
  71. package/admin/js/views/effects.js +31 -1
  72. package/admin/js/views/form-editor.js +7 -7
  73. package/admin/js/views/form-submissions.js +11 -1
  74. package/admin/js/views/index.js +1 -1
  75. package/admin/js/views/layouts.js +81 -1
  76. package/admin/js/views/login.js +2 -2
  77. package/admin/js/views/menu-editor.js +21 -43
  78. package/admin/js/views/menu-locations.js +16 -1
  79. package/admin/js/views/my-profile.js +1 -1
  80. package/admin/js/views/notifications.js +18 -1
  81. package/admin/js/views/page-editor.js +26 -26
  82. package/admin/js/views/plugin-code.js +23 -4
  83. package/admin/js/views/plugins.js +97 -39
  84. package/admin/js/views/project-detail.js +9 -1
  85. package/admin/js/views/project-settings.js +1 -1
  86. package/admin/js/views/role-editor.js +11 -3
  87. package/admin/js/views/roles.js +49 -4
  88. package/admin/js/views/search.js +7 -1
  89. package/admin/js/views/settings.js +13 -3
  90. package/admin/js/views/user-editor.js +1 -1
  91. package/admin/js/views/users.js +13 -4
  92. package/admin/js/views/view-editor.js +1 -1
  93. package/admin/js/views/view-preview.js +1 -1
  94. package/package.json +1 -1
  95. package/plugins/_lib/admin/mail/compose-window.js +97 -40
  96. package/plugins/_lib/admin/mail/contacts.js +4 -1
  97. package/plugins/_lib/admin/mail/diagnostics-section.js +4 -1
  98. package/plugins/_lib/admin/mail/folder-tree.js +1 -1
  99. package/plugins/_lib/admin/mail/image-senders-section.js +69 -90
  100. package/plugins/_lib/admin/mail/mail.css +1 -1
  101. package/plugins/_lib/admin/mail/mail.html +1 -1
  102. package/plugins/_lib/admin/mail/reader-view.js +303 -44
  103. package/plugins/_lib/admin/mail/when-closed.js +26 -0
  104. package/plugins/_lib/mail/readRoutes.js +43 -1
  105. package/plugins/_lib/mail/unreadBadge.js +42 -0
  106. package/plugins/free-tier.lock.json +5 -5
  107. package/plugins/mail-reader/CLAUDE.md +1 -0
  108. package/plugins/mail-reader/plugin.js +3 -1
  109. package/plugins/mail-reader/plugin.json +5 -4
  110. package/server/routes/api/api-tokens.js +56 -0
  111. package/server/routes/api/auth.js +7 -1
  112. package/server/routes/api/collections.js +13 -1
  113. package/server/routes/api/dashboard.js +32 -4
  114. package/server/routes/api/effects.js +68 -13
  115. package/server/routes/api/forms.js +104 -13
  116. package/server/routes/api/layouts.js +93 -2
  117. package/server/routes/api/notifications.js +140 -14
  118. package/server/routes/api/plugins.js +135 -4
  119. package/server/routes/api/roles.js +111 -0
  120. package/server/routes/api/search.js +38 -4
  121. package/server/routes/api/settings.js +52 -0
  122. package/server/routes/api/users.js +65 -3
  123. package/server/routes/api/views.js +55 -1
  124. package/server/server.js +12 -0
  125. package/server/services/components.js +9 -0
  126. package/server/services/plugins.js +7 -0
  127. package/server/services/roles.js +3 -0
  128. package/server/services/search.js +58 -0
  129. package/server/services/users.js +16 -2
  130. package/server/services/views.js +14 -0
  131. package/admin/js/templates/dashboard/activity-feed.html +0 -3
  132. package/admin/js/templates/dashboard/cache.html +0 -32
  133. package/admin/js/templates/dashboard/health-detail.html +0 -2
  134. package/admin/js/templates/dashboard/journeys.html +0 -17
  135. package/admin/js/templates/dashboard/kpi-strip.html +0 -34
  136. package/admin/js/templates/dashboard/spike-feed.html +0 -3
  137. package/admin/js/templates/dashboard/top-pages.html +0 -3
  138. package/admin/js/templates/dashboard/traffic-chart.html +0 -3
  139. package/admin/js/templates/plugin-marketplace.html +0 -29
  140. package/admin/js/views/block-editor-enhance.js +0 -1
  141. package/admin/js/views/dashboard/lib/escape.js +0 -1
  142. package/admin/js/views/dashboard/widgets/activity-feed.js +0 -1
  143. package/admin/js/views/dashboard/widgets/cache.js +0 -1
  144. package/admin/js/views/dashboard/widgets/health-detail.js +0 -1
  145. package/admin/js/views/dashboard/widgets/journeys.js +0 -1
  146. package/admin/js/views/dashboard/widgets/kpi-strip.js +0 -1
  147. package/admin/js/views/dashboard/widgets/spike-feed.js +0 -6
  148. package/admin/js/views/dashboard/widgets/top-pages.js +0 -1
  149. package/admin/js/views/dashboard/widgets/traffic-chart.js +0 -1
  150. package/admin/js/views/plugin-marketplace.js +0 -1
@@ -0,0 +1,3 @@
1
+ export const BASE_ROLES=["super-admin","admin","user"],NAME_RE=/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/,ROUTE_RE=/^\/[A-Za-z0-9\-_/]*$/,NEEDED_TO_RETURN=[{perm:"plugins.read",why:"open Roles"},{perm:"collections.read",why:"load a role"},{perm:"collections.update",why:"save a role"}];const $=e=>(e?.actions||[]).map(n=>n.key);export function expandPerms(e,n=[]){const i=Array.isArray(e)?e:[],r=new Set;for(const o of n){const l=$(o);i.includes(o.key)?l.forEach(t=>r.add(`${o.key}.${t}`)):l.forEach(t=>{i.includes(`${o.key}.${t}`)&&r.add(`${o.key}.${t}`)})}return[...r].sort()}export function compactPerms(e,n=[],i=[]){const r=new Set(e),o=new Set(n.map(t=>t.key)),l=[];for(const t of n){const a=$(t),u=a.filter(c=>r.has(`${t.key}.${c}`));a.length&&u.length===a.length?l.push(t.key):u.forEach(c=>l.push(`${t.key}.${c}`))}for(const t of Array.isArray(i)?i:[])o.has(String(t).split(".")[0])||l.push(t);return l}export function formFromRole(e={},n=[]){return{name:String(e.name??""),label:String(e.label??""),level:e.level===void 0||e.level===null?"":String(e.level),badgeClass:String(e.badgeClass||"badge-secondary"),project:String(e.meta?.project||""),adminAllow:(Array.isArray(e.admin?.allow)?e.admin.allow:[]).join(`
2
+ `),adminHome:String(e.admin?.home||""),granted:expandPerms(e.permissions,n)}}export function snapshot(e){return JSON.stringify({name:String(e.name??"").trim(),label:String(e.label??"").trim(),level:String(e.level??"").trim(),badgeClass:e.badgeClass||"",project:e.project||"",adminAllow:w(e.adminAllow),adminHome:String(e.adminHome??"").trim(),granted:[...e.granted||[]].sort()})}const w=e=>String(e??"").split(`
3
+ `).map(n=>n.trim()).filter(Boolean);export function roleFacts(e={}){const n=e.level===0,i=BASE_ROLES.includes(e.name),r=typeof e.plugin=="string"?e.plugin:"",o=n||i?"A built-in role keeps its name.":r?`Made by the ${r} plugin, which finds it by this name.`:"";return{isRoot:n,isBase:i,plugin:r,nameLocked:o}}export function checkRole(e,{facts:n={},taken:i=[],originalName:r=""}={}){const o={},l=String(e.name??"").trim();if(n.nameLocked||(NAME_RE.test(l)?l!==r&&i.includes(l)&&(o.name=`"${l}" is already a role.`):o.name="Lowercase letters, numbers and hyphens; no hyphen at either end."),String(e.label??"").trim()||(o.label="A role needs a label."),!n.isRoot){const t=Number(String(e.level??"").trim());String(e.level??"").trim()===""||!Number.isInteger(t)?o.level="A whole number.":t<1&&(o.level="0 belongs to the root role - use 1 or more.");const a=[...w(e.adminAllow),String(e.adminHome??"").trim()].find(u=>u&&!ROUTE_RE.test(u));a&&(o.admin=`"${a}" is not an admin route - use a path like /portal.`)}return o}export function payloadFromForm(e,n={},{facts:i={},resources:r=[]}={}){const{admin:o,...l}=n,t=w(e.adminAllow),a=String(e.adminHome??"").trim(),u=!i.isRoot&&(t.length||a)?{allow:t,...a&&{home:a}}:null,c=Number.parseInt(e.level,10);return{...l,...u&&{admin:u},name:i.nameLocked?n.name:String(e.name??"").trim()||n.name,label:String(e.label??"").trim(),level:i.isRoot?n.level:Number.isFinite(c)?c:n.level,permissions:compactPerms(e.granted,r,n.permissions),badgeClass:e.badgeClass||n.badgeClass,meta:{...n.meta||{},project:e.project||null}}}export function matrixRows(e=[],n=[],i=[],{query:r="",show:o="all",locked:l=!1}={}){const t=new Set(i),a=String(r).trim().toLowerCase(),u=[],c=[...n,...new Set(e.map(m=>m.group).filter(m=>!n.includes(m)))];for(const m of c){const y=e.filter(s=>s.group===m),b=y.filter(s=>{const g=$(s).filter(h=>t.has(`${s.key}.${h}`)).length;return o==="on"&&!g||o==="off"&&g?!1:a?[m,s.key,s.label,s.description,...(s.actions||[]).map(h=>h.label)].some(h=>String(h||"").toLowerCase().includes(a)):!0});if(!b.length)continue;const f=y.reduce((s,p)=>s+$(p).length,0),d=y.reduce((s,p)=>s+$(p).filter(g=>t.has(`${p.key}.${g}`)).length,0);u.push({id:`g:${m}`,kind:"group",group:m,label:m,icon:"layers",description:"",meta:`${d} of ${f} granted`,state:d===0?"none":d===f?"all":"some",actions:[],locked:l});for(const s of b){const p=s.actions||[],g=p.filter(h=>t.has(`${s.key}.${h.key}`)).length;u.push({id:`r:${s.key}`,kind:"res",group:m,key:s.key,label:s.label||s.key,icon:s.icon||"box",description:s.description||"",meta:g===p.length&&g?"Full access":g?`${g} of ${p.length}`:"No access",state:g===0?"none":g===p.length?"all":"some",locked:l,actions:p.map(h=>({key:h.key,label:h.label||h.key,on:t.has(`${s.key}.${h.key}`)?"true":"false",title:h.description||h.label||h.key}))})}}return u}export function setActions(e,n,{group:i,resource:r,to:o,only:l}={}){const t=new Set(e);for(const a of n)if(!(r&&a.key!==r)&&!(i&&a.group!==i))for(const u of $(a)){const c=`${a.key}.${u}`;(l?u===l:o)?t.add(c):t.delete(c)}return[...t].sort()}export function toggleAction(e,n){const i=new Set(e);return i.has(n)?i.delete(n):i.add(n),[...i].sort()}const k=e=>e.length<=1?e.join(""):`${e.slice(0,-1).join(", ")} and ${e.at(-1)}`;export function summaryLines(e,n=[],i=[],{isRoot:r=!1,admin:o=null}={}){if(r)return[{id:"root",title:"Everything",text:"The root role holds every permission - core and plugins - whatever is ticked here."}];const l=new Set(e),t=[],a=[...i,...new Set(n.map(c=>c.group).filter(c=>!i.includes(c)))];for(const c of a){const m=[],y=[],b=[];for(const d of n.filter(s=>s.group===c)){const s=d.actions||[],p=s.filter(h=>l.has(`${d.key}.${h.key}`));if(!p.length)continue;const g=d.label||d.key;p.length===s.length?m.push(g):p.length===1&&p[0].key==="read"?y.push(g):b.push(`${g} - ${k(p.map(h=>String(h.label||h.key).toLowerCase()))}`)}const f=[];m.length&&f.push(`manage ${k(m)}`),y.length&&f.push(`view ${k(y)}`),f.push(...b),f.length&&t.push({id:c,title:c,text:f.join("; ")})}t.length||t.push({id:"none",title:"Nothing",text:"No permissions: holders can sign in and edit their own profile, nothing more."});const u=Array.isArray(o?.allow)?o.allow.filter(Boolean):[];return u.length&&t.push({id:"scope",title:"Admin area",text:`Confined to ${k(u)}${o.home?`, starting at ${o.home}`:""} (unless another role they hold is not confined).`}),t}export function selfRisks({holds:e=!1,otherRoot:n=!1,otherGranted:i=[],oldLevel:r,newLevel:o,granted:l=[],isRoot:t=!1}={}){if(!e||t||n)return[];const a=[];Number.isFinite(o)&&Number.isFinite(r)&&o>r&&a.push(`You hold this role: level ${r} \u2192 ${o} makes you less senior, and you may lose the right to manage users at levels ${r+1}-${o}.`);const u=new Set([...l,...i]),c=NEEDED_TO_RETURN.filter(m=>!u.has(m.perm));return c.length&&a.push(`You hold this role and no other role gives you ${k(c.map(m=>m.perm))} - without ${c.length===1?"it":"them"} you could not ${k(c.map(m=>m.why))} to put this back.`),a}
@@ -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 ee}from"./admin-scope.js";import{colourToCss as X,applyFloat as te,applyHighlight as ne,itemHighlightAttrs as se}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{placeTools as ie,ensureLockedTools as oe,managePluginsItem as ae,stripItemByUrl as re,insertFoldersBeforeSystem as le,pruneEmptyToolsFolder as ce,MANAGE_PLUGINS_URL as de}from"./sidebar-grouping.js";function h(t){return String(t??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const ue=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Menus",url:"#/menus",icon:"menu",permission:"menus"}];function z(t,s){return s?!t||!t.length?!1:t.includes(s)?!0:["read","create","update","delete"].some(n=>t.includes(`${s}.${n}`)):!0}function K(t,s,n=null){const e=[];for(const i of t){if(i.hidden||i.permission&&!z(s,i.permission))continue;const o=typeof i.url=="string"?i.url:"";if(n&&o.startsWith("#/")&&!ee(n,o.slice(1)))continue;const l=Array.isArray(i.items)&&i.items.length>0,a=l?K(i.items,s,n):[];l&&!a.length&&!o||e.push({...i,items:a})}return e}async function me(t){if(!z(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 V(t,s){const n=J.includes(s.tone)?s.tone:"info",e=s.label||String(s.count);s.details?N.set(t,s.details):N.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 G(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(),N.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),N.set(l,{title:c,items:o})}}const N=new Map;function pe(t,s){if(!t.url||!s)return"";const n=s[t.url];return!n||!(n.count>0)?"":V(t.url,n)}const J=["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:J.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 Y(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=se(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=>Y(w,s)).join("");return`<details data-state-key="${h(y)}"${k?" open":""}>
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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}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
+ import{fieldKind as g,profileValue as S}from"./my-profile-arrange.js";export const PASSWORD_MIN=8;const y=/^[^\s@]+@[^\s@]+\.[^\s@]+$/,u=(e,o)=>e?.[o]?.level??99;export const rolesHeld=e=>[...new Set([e?.role,...Array.isArray(e?.additionalRoles)?e.additionalRoles:[]].filter(Boolean))];export function effectiveLevel(e,o){const n=rolesHeld(e);return n.length?Math.min(...n.map(s=>u(o,s))):99}export function editRights(e,o,n){if(!o)return{self:!1,canEdit:!0,reason:""};const s=!!e?.id&&e.id===o.id,l=s||effectiveLevel(e,n)<effectiveLevel(o,n);return{self:s,canEdit:l,reason:l?"":"Your role is not senior to theirs, so you can look but not change anything."}}export function roleChoices(e,o,n=null){const s=effectiveLevel(o,e),l=new Set(rolesHeld(n));return Object.entries(e||{}).map(([t,r])=>{const c=(r.level??99)>s||l.has(t);return{name:t,label:r.label||t,level:r.level??99,assignable:c,reason:c?"":"Only a more senior role can give this one"}}).sort((t,r)=>t.level-r.level||t.label.localeCompare(r.label))}export function formFromUser(e,o=[],{project:n="",defaultRole:s="user"}={}){return{name:String(e?.name??""),email:String(e?.email??""),password:"",role:String(e?.role||s),additionalRoles:Array.isArray(e?.additionalRoles)?e.additionalRoles.filter(Boolean).map(String):[],ownedByProject:String(e?e.meta?.project??"":n),projects:Array.isArray(e?.projects)?e.projects.filter(Boolean).map(String):[],isActive:e?e.isActive!==!1:!0,profile:Object.fromEntries((o||[]).filter(l=>l?.name).map(l=>[l.name,S(l,e?.profile?.[l.name])]))}}export function draftOf(e){const{password:o,...n}=e||{};return n}const p=e=>[...new Set((e||[]).filter(Boolean).map(String))];export function cleanAdditional(e,o){return p(e).filter(n=>n!==o)}export function snapshot(e){const o=e||{};return JSON.stringify({name:String(o.name??"").trim(),email:String(o.email??"").trim().toLowerCase(),password:!!o.password,role:o.role||"",additionalRoles:cleanAdditional(o.additionalRoles,o.role).sort(),ownedByProject:o.ownedByProject||"",projects:p(o.projects).sort(),isActive:o.isActive!==!1,profile:o.profile||{}})}export function checkUserForm(e,{isNew:o,fields:n=[],saved:s,choices:l=[]}={}){const t={},r=e||{};String(r.name??"").trim().length<2&&(t.name="A name needs at least 2 characters."),y.test(String(r.email??"").trim())||(t.email="That does not look like an email address.");const c=String(r.password??"");o&&!c?t.password=`A new account needs a password (at least ${PASSWORD_MIN} characters).`:c&&c.length<PASSWORD_MIN&&(t.password=`At least ${PASSWORD_MIN} characters.`);const d=Object.fromEntries(l.map(i=>[i.name,i]));r.role?l.length&&!d[r.role]?.assignable&&(t.role="Only a more senior role can give this one."):t.role="Choose a role.";const f=cleanAdditional(r.additionalRoles,r.role).filter(i=>l.length&&!d[i]?.assignable);f.length&&(t.additionalRoles=`You cannot give ${f.join(", ")}.`);const a=!s||JSON.stringify(r.profile||{})!==JSON.stringify(s.profile||{});for(const i of a?n||[]:[]){if(!i?.name||!i.required||g(i.type)==="hidden")continue;const m=r.profile?.[i.name];(Array.isArray(m)?!m.length:g(i.type)==="checkbox"?m!==!0:String(m??"").trim()==="")&&(t[`profile.${i.name}`]=`${i.label||i.name} is required.`)}return t}export function buildUserPayload(e,o,{isNew:n,fields:s=[],meta:l={}}={}){const t=o||{},r=cleanAdditional(t.additionalRoles,t.role),c=p(t.projects),d=()=>Object.fromEntries((s||[]).filter(i=>i?.name).map(i=>[i.name,t.profile?.[i.name]??S(i,void 0)]));if(n){const i={name:String(t.name).trim(),email:String(t.email).trim(),password:String(t.password||""),role:t.role,additionalRoles:r};return c.length&&(i.projects=c),t.ownedByProject&&(i.meta={project:t.ownedByProject}),t.isActive===!1&&(i.isActive=!1),JSON.stringify(t.profile||{})!==JSON.stringify(e?.profile||{})&&(i.profile=d()),{create:i}}const f=e||{},a={};return String(t.name).trim()!==String(f.name??"").trim()&&(a.name=String(t.name).trim()),String(t.email).trim().toLowerCase()!==String(f.email??"").trim().toLowerCase()&&(a.email=String(t.email).trim()),t.password&&(a.password=String(t.password)),t.role!==f.role&&(a.role=t.role),(JSON.stringify([...r].sort())!==JSON.stringify(cleanAdditional(f.additionalRoles,f.role).sort())||a.role)&&(a.additionalRoles=r),JSON.stringify([...c].sort())!==JSON.stringify(p(f.projects).sort())&&(a.projects=c),(t.ownedByProject||"")!==(f.ownedByProject||"")&&(a.meta={...l||{},project:t.ownedByProject||null}),t.isActive!==!1!=(f.isActive!==!1)&&(a.isActive=t.isActive!==!1),JSON.stringify(t.profile||{})!==JSON.stringify(f.profile||{})&&(a.profile=d()),Object.keys(a).length?{update:a}:null}export function emailConflict(e){return e?.status===409&&/email/i.test(String(e?.message||""))?"Another account already uses this email.":""}
@@ -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(" ")}}
@@ -0,0 +1,14 @@
1
+ export const STAGE_TYPES=[{type:"$match",label:"Filter rows",guided:!0},{type:"$sort",label:"Order rows",guided:!0},{type:"$lookup",label:"Join a collection",example:`{
2
+ "from": "other-collection",
3
+ "localField": "data.ownerId",
4
+ "foreignField": "id",
5
+ "as": "owner"
6
+ }`},{type:"$project",label:"Reshape fields",example:`{
7
+ "data.title": 1,
8
+ "data.price": 1
9
+ }`},{type:"$unwind",label:"Flatten an array",example:'"$data.tags"'},{type:"$addFields",label:"Compute fields",example:`{
10
+ "label": "$data.title"
11
+ }`},{type:"$group",label:"Group and total",example:`{
12
+ "_id": "$data.category",
13
+ "count": {"$sum": 1}
14
+ }`},{type:"$limit",label:"Keep the first N",example:"10"},{type:"$skip",label:"Skip the first N",example:"10"},{type:"$count",label:"Count the rows",example:'"total"'}];const f=Object.fromEntries(STAGE_TYPES.map(r=>[r.type,r]));export const isGuided=r=>f[r]?.guided===!0,OPERATORS=[{value:"eq",label:"= equals"},{value:"ne",label:"\u2260 not equals"},{value:"gt",label:"> greater than"},{value:"lt",label:"< less than"},{value:"gte",label:"\u2265 greater or equal"},{value:"lte",label:"\u2264 less or equal"},{value:"contains",label:"~ contains"},{value:"in",label:"\u2208 one of (comma list)"}];const g={eq:"=",ne:"\u2260",gt:">",lt:"<",gte:"\u2265",lte:"\u2264",contains:"~",in:"\u2208"};export const slugify=r=>String(r??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");export function matchToRows(r){const n=r?.$match||r||{},e=Array.isArray(n.$or),t=[],o={$eq:"eq",$ne:"ne",$gt:"gt",$lt:"lt",$gte:"gte",$lte:"lte",$in:"in"},s=a=>{for(const[i,l]of Object.entries(a||{}))if(l&&typeof l=="object"&&!Array.isArray(l)){if("$regex"in l){t.push({field:i,op:"contains",val:String(l.$regex??"")});continue}for(const[p,c]of Object.entries(l))o[p]&&t.push({field:i,op:o[p],val:Array.isArray(c)?c.join(", "):String(c)})}else t.push({field:i,op:"eq",val:String(l??"")})};return e?n.$or.forEach(s):s(n),{logic:e?"or":"and",rows:t}}const u=r=>String(r).startsWith("__")?String(r).slice(2):String(r),d=r=>r!==""&&!Number.isNaN(Number(r))?Number(r):r;export function rowsToMatch(r,n){const e=[];for(const t of n||[]){const o=String(t?.field??"").trim();if(!o)continue;const s=u(o),a=String(t.val??"").trim(),i={};switch(t.op){case"ne":i[s]={$ne:a};break;case"gt":case"lt":case"gte":case"lte":i[s]={[`$${t.op}`]:d(a)};break;case"contains":i[s]={$regex:a,$options:"i"};break;case"in":i[s]={$in:a.split(",").map(l=>l.trim()).filter(Boolean)};break;default:i[s]=a}e.push(i)}return e.length?r==="or"?{$or:e}:Object.assign({},...e):{}}export const sortToRows=r=>Object.entries(r?.$sort||r||{}).map(([n,e])=>({field:n,dir:Number(e)===-1?-1:1}));export function rowsToSort(r){const n={};for(const e of r||[]){const t=String(e?.field??"").trim();t&&(n[u(t)]=Number(e.dir)===-1?-1:1)}return n}export function parseStageJson(r,n){const e=String(n??"").trim();if(r==="$limit"||r==="$skip"){const t=Number(e.replace(/^"|"$/g,""));return Number.isInteger(t)&&t>=0?{config:t,error:""}:{config:null,error:`${r} takes a whole number, e.g. 10`}}if(r==="$count"){const t=e.replace(/^"|"$/g,"");return/^[A-Za-z_][\w]*$/.test(t)?{config:t,error:""}:{config:null,error:'$count takes a field name, e.g. "total"'}}if(!e)return{config:{},error:r==="$unwind"?'$unwind needs the array to flatten, e.g. "$data.tags"':""};try{const t=JSON.parse(e);return r==="$unwind"&&(typeof t=="string"||t&&typeof t=="object")?{config:t,error:""}:!t||typeof t!="object"||Array.isArray(t)?{config:null,error:`${r} takes an object - {\u2026}`}:{config:t,error:""}}catch(t){return{config:null,error:`Not valid JSON: ${t.message}`}}}export function stageText(r,n){return n==null||typeof n=="object"&&!Array.isArray(n)&&!Object.keys(n).length?"":typeof n=="number"?String(n):JSON.stringify(n,null,2)}export function stageSummary(r){const{type:n,config:e}=r||{};if(n==="$match"){const{logic:t,rows:o}=matchToRows(e);return o.length?o.slice(0,2).map(a=>`${u(a.field).replace(/^data\./,"")} ${g[a.op]||"="} ${a.val}`).join(t==="or"?" or ":" and ")+(o.length>2?` \u2026 (${o.length} conditions)`:""):"every row"}if(n==="$sort"){const t=sortToRows(e);return t.length?t.map(o=>`${o.field.replace(/^data\./,"")} ${o.dir===-1?"\u2193":"\u2191"}`).join(", "):"no order set"}if(n==="$lookup")return e?.from?`join ${e.from}${e.as?` as ${e.as}`:""}`:"join\u2026";if(n==="$limit")return`first ${e}`;if(n==="$skip")return`skip ${e}`;if(n==="$count")return`count as ${e}`;if(n==="$unwind")return`flatten ${typeof e=="string"?e:e?.path||"\u2026"}`;if(e&&typeof e=="object"){const t=Object.keys(e);return t.length?`${t.slice(0,3).join(", ")}${t.length>3?", \u2026":""}`:"empty"}return f[n]?.label||""}export function move(r,n,e){const t=n+e;if(n<0||n>=r.length||t<0||t>=r.length)return r.slice();const o=r.slice(),[s]=o.splice(n,1);return o.splice(t,0,s),o}export function checkView(r,{stageErrors:n=[],isNew:e=!1,taken:t=[]}={}){const o=r||{},s={source:[],pipeline:[],display:[],access:[]};if(String(o.title??"").trim()||s.source.push("A view needs a title."),o.pipeline?.source||s.source.push("Choose the collection it reads from."),e){const i=o.slug?slugify(o.slug):slugify(o.title);i?t.includes(i)&&s.source.push(`"${i}" is already a view - choose another slug.`):s.source.push("The title (or slug) needs at least one letter or number.")}for(const i of n)s.pipeline.push(`Stage ${i.index+1} (${i.type}): ${i.error}`);const a=Number(o.display?.pageSize);return(!Number.isInteger(a)||a<1||a>200)&&s.display.push("Page size is 1 to 200."),o.display?.mode==="block"&&!o.display?.block&&s.display.push("Block mode needs a block template."),o.access?.rowLevel?.mode==="field"&&!String(o.access.rowLevel.field??"").trim()&&s.access.push("Field Match needs the field to compare."),!o.access?.public&&!(o.access?.roles||[]).length&&s.access.push("Pick at least one role, or make it public."),s}export const errorCount=r=>Object.values(r||{}).reduce((n,e)=>n+e.length,0);export function snapshot(r){const n=e=>Array.isArray(e)?e.map(n):e&&typeof e=="object"?Object.fromEntries(Object.keys(e).sort().map(t=>[t,n(e[t])])):e;return JSON.stringify(n(r??null))}export const draftKey=r=>`view-editor.draft.${r||"new"}`;export function flattenDoc(r,n="",e={}){for(const[t,o]of Object.entries(r||{})){const s=n?`${n}.${t}`:t;o&&typeof o=="object"&&!Array.isArray(o)&&Object.keys(o).length<8?flattenDoc(o,s,e):e[s]=o}return e}export function resultColumns(r,n=[]){return Array.isArray(n)&&n.length?n.map(e=>({key:e.key,label:e.label||e.key})):Object.keys(flattenDoc(r?.[0]||{})).slice(0,6).map(e=>({key:e,label:e}))}export function cellText(r,n,e=80){let t=r;for(const s of String(n).split("."))t=t?.[s];const o=t==null?"":Array.isArray(t)?t.map(s=>s&&typeof s=="object"?JSON.stringify(s):String(s)).join(", "):typeof t=="object"?JSON.stringify(t):String(t);return o.length>e?`${o.slice(0,e)}\u2026`:o}