domma-cms 0.55.1 → 0.66.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 (211) hide show
  1. package/CLAUDE.md +130 -2
  2. package/README.md +4 -4
  3. package/admin/css/admin.css +1 -1
  4. package/admin/dist/domma/domma-tools.css +3 -3
  5. package/admin/dist/domma/domma-tools.min.js +3 -3
  6. package/admin/js/api.js +1 -1
  7. package/admin/js/app.js +3 -3
  8. package/admin/js/lib/page-picker.js +1 -0
  9. package/admin/js/lib/plugin-accent.js +1 -0
  10. package/admin/js/lib/plugin-chrome.js +1 -0
  11. package/admin/js/lib/shortcode-context-menu.js +2 -2
  12. package/admin/js/lib/sidebar-grouping.js +1 -1
  13. package/admin/js/lib/sidebar-grouping.test.js +1 -1
  14. package/admin/js/lib/sidebar-renderer.js +4 -4
  15. package/admin/js/lib/slideover-resizable.js +1 -0
  16. package/admin/js/templates/context-menu-editor.html +212 -0
  17. package/admin/js/templates/context-menus.html +16 -0
  18. package/admin/js/templates/plugin-code.html +1 -1
  19. package/{plugins/site-search/admin/templates/site-search.html → admin/js/templates/search.html} +50 -4
  20. package/admin/js/templates/settings.html +16 -97
  21. package/admin/js/templates/theme.html +173 -0
  22. package/admin/js/views/context-menu-editor.js +55 -0
  23. package/admin/js/views/context-menus.js +5 -0
  24. package/admin/js/views/form-editor.js +7 -7
  25. package/admin/js/views/index.js +1 -1
  26. package/admin/js/views/plugin-marketplace.js +1 -1
  27. package/admin/js/views/plugins.js +25 -23
  28. package/admin/js/views/search.js +1 -0
  29. package/admin/js/views/settings.js +3 -3
  30. package/admin/js/views/theme.js +1 -0
  31. package/bin/cli.js +2 -0
  32. package/bin/update.js +13 -2
  33. package/config/menus/admin-sidebar.json +129 -23
  34. package/config/plugins.json +5 -5
  35. package/config/search.json +13 -0
  36. package/config/theme.json +18 -0
  37. package/package.json +12 -4
  38. package/plugins/_lib/admin/mail/compose-window.js +914 -0
  39. package/plugins/_lib/admin/mail/contacts.js +301 -0
  40. package/plugins/_lib/admin/mail/diagnostics-section.js +133 -0
  41. package/plugins/_lib/admin/mail/folder-tree.js +254 -0
  42. package/plugins/_lib/admin/mail/identity.js +480 -0
  43. package/plugins/_lib/admin/mail/image-senders-section.js +131 -0
  44. package/plugins/_lib/admin/mail/keyboard.js +136 -0
  45. package/plugins/_lib/admin/mail/mail.css +1 -0
  46. package/plugins/_lib/admin/mail/mail.html +71 -0
  47. package/plugins/_lib/admin/mail/panes.js +253 -0
  48. package/plugins/_lib/admin/mail/reader-view.js +4453 -0
  49. package/plugins/_lib/admin/mail/resizable.js +26 -0
  50. package/plugins/_lib/admin/mail/rules.js +343 -0
  51. package/plugins/_lib/admin/mail/scheduling.js +203 -0
  52. package/plugins/_lib/admin/mail/section-kit.js +277 -0
  53. package/plugins/_lib/admin/mail/templates.js +238 -0
  54. package/plugins/_lib/admin/mail/threads.js +200 -0
  55. package/plugins/_lib/admin/mail/vacation.js +269 -0
  56. package/plugins/_lib/admin/ui/help.css +1 -0
  57. package/plugins/_lib/admin/ui/help.js +174 -0
  58. package/plugins/_lib/admin/ui/resizable.js +151 -0
  59. package/plugins/_lib/dataStore.js +117 -0
  60. package/plugins/_lib/mail/accounts.js +919 -0
  61. package/plugins/_lib/mail/bodyTokens.js +101 -0
  62. package/plugins/_lib/mail/compose.js +256 -0
  63. package/plugins/_lib/mail/defaults.js +57 -0
  64. package/plugins/_lib/mail/diagnostics.js +154 -0
  65. package/plugins/_lib/mail/envelope.js +161 -0
  66. package/plugins/_lib/mail/folders.js +192 -0
  67. package/plugins/_lib/mail/handoff.js +274 -0
  68. package/plugins/_lib/mail/imapPool.js +293 -0
  69. package/plugins/_lib/mail/mbox.js +74 -0
  70. package/plugins/_lib/mail/pollSchedule.js +79 -0
  71. package/plugins/_lib/mail/poller.js +135 -0
  72. package/plugins/_lib/mail/priority.js +138 -0
  73. package/plugins/_lib/mail/readRoutes.js +680 -0
  74. package/plugins/_lib/mail/render.js +291 -0
  75. package/plugins/_lib/mail/ruleRunner.js +152 -0
  76. package/plugins/_lib/mail/scheduler.js +254 -0
  77. package/plugins/_lib/mail/secretbox.js +229 -0
  78. package/plugins/_lib/mail/send.js +396 -0
  79. package/plugins/_lib/mail/store.js +1002 -0
  80. package/plugins/_lib/mail/sync.js +292 -0
  81. package/plugins/_lib/mail/syncPlan.js +126 -0
  82. package/plugins/_lib/mail/syncSelection.js +82 -0
  83. package/plugins/_lib/mail/unsubscribe.js +183 -0
  84. package/plugins/_lib/mail/vacationRunner.js +114 -0
  85. package/plugins/_lib/mail/write.js +473 -0
  86. package/plugins/_lib/schemaSync.js +83 -0
  87. package/plugins/_template/admin/css/index.css +0 -0
  88. package/plugins/_template/admin/templates/index.html +4 -4
  89. package/plugins/_template/admin/views/index.js +7 -0
  90. package/plugins/analytics/admin/css/index.css +1 -0
  91. package/plugins/analytics/admin/templates/analytics.html +22 -13
  92. package/plugins/analytics/plugin.json +3 -0
  93. package/plugins/blog/admin/css/index.css +1 -0
  94. package/plugins/blog/admin/templates/blog.html +30 -18
  95. package/plugins/blog/admin/templates/categories.html +2 -2
  96. package/plugins/blog/admin/templates/comments.html +2 -2
  97. package/plugins/blog/admin/templates/post-editor.html +34 -34
  98. package/plugins/blog/admin/templates/settings.html +6 -3
  99. package/plugins/blog/admin/views/blog.js +8 -5
  100. package/plugins/blog/admin/views/categories.js +5 -10
  101. package/plugins/blog/admin/views/comments.js +5 -5
  102. package/plugins/blog/admin/views/post-editor.js +39 -20
  103. package/plugins/blog/admin/views/settings.js +52 -50
  104. package/plugins/blog/collections/categories/schema.json +7 -6
  105. package/plugins/blog/collections/comments/schema.json +11 -10
  106. package/plugins/blog/collections/posts/schema.json +14 -13
  107. package/plugins/blog/plugin.js +36 -13
  108. package/plugins/blog/plugin.json +13 -5
  109. package/plugins/blog/plugin.public.js +312 -0
  110. package/plugins/contacts/admin/css/index.css +1 -0
  111. package/plugins/contacts/admin/templates/contacts.html +128 -0
  112. package/plugins/contacts/admin/views/contacts.js +237 -4
  113. package/plugins/contacts/collections/user-contacts/schema.json +108 -0
  114. package/plugins/contacts/plugin.js +214 -27
  115. package/plugins/contacts/plugin.json +4 -1
  116. package/plugins/invoice/admin/css/index.css +1 -0
  117. package/plugins/invoice/admin/templates/editor.html +140 -49
  118. package/plugins/invoice/admin/templates/index.html +153 -23
  119. package/plugins/invoice/admin/templates/issuers.html +2 -5
  120. package/plugins/invoice/admin/templates/receivers.html +2 -5
  121. package/plugins/invoice/admin/views/contacts-source.js +266 -0
  122. package/plugins/invoice/admin/views/editor.js +366 -199
  123. package/plugins/invoice/admin/views/export.js +199 -0
  124. package/plugins/invoice/admin/views/help-content.js +61 -0
  125. package/plugins/invoice/admin/views/index.js +582 -94
  126. package/plugins/invoice/admin/views/issuers.js +24 -17
  127. package/plugins/invoice/admin/views/media.js +172 -0
  128. package/plugins/invoice/admin/views/party-view.js +305 -67
  129. package/plugins/invoice/admin/views/payments.js +127 -0
  130. package/plugins/invoice/admin/views/print.js +130 -0
  131. package/plugins/invoice/admin/views/receivers.js +49 -16
  132. package/plugins/invoice/admin/views/send.js +212 -0
  133. package/plugins/invoice/admin/views/settings.js +594 -0
  134. package/plugins/invoice/admin/views/view-lifecycle.js +33 -0
  135. package/plugins/invoice/collections/invoice-issuers/schema.json +77 -11
  136. package/plugins/invoice/collections/invoice-receivers/schema.json +10 -9
  137. package/plugins/invoice/collections/invoices/schema.json +19 -13
  138. package/plugins/invoice/config.js +27 -6
  139. package/plugins/invoice/pdf.js +164 -0
  140. package/plugins/invoice/plugin.js +1217 -44
  141. package/plugins/invoice/plugin.json +10 -9
  142. package/plugins/invoice/templates/_base.css +1 -0
  143. package/plugins/invoice/templates/classic-nologo.html +100 -0
  144. package/plugins/invoice/templates/classic.html +91 -0
  145. package/plugins/invoice/templates/invoice-print.html +24 -0
  146. package/plugins/invoice/templates/minimal.html +99 -0
  147. package/plugins/invoice/templates/modern-nologo.html +114 -0
  148. package/plugins/invoice/templates/modern.html +113 -0
  149. package/plugins/invoice/templates/templates.json +11 -0
  150. package/plugins/mail-reader/admin/views/mail.js +19 -0
  151. package/plugins/mail-reader/config.js +7 -0
  152. package/plugins/mail-reader/plugin.js +48 -0
  153. package/plugins/mail-reader/plugin.json +33 -0
  154. package/plugins/notes/admin/views/notes.js +1 -1
  155. package/plugins/notes/plugin.json +2 -2
  156. package/plugins/surveys/lib/audience.js +37 -0
  157. package/plugins/surveys/lib/campaigns.js +43 -0
  158. package/plugins/surveys/lib/ledger.js +110 -0
  159. package/plugins/surveys/lib/sending.js +106 -0
  160. package/plugins/surveys/lib/stats.js +62 -0
  161. package/plugins/surveys/lib/submit.js +95 -0
  162. package/plugins/surveys/lib/tokens.js +28 -0
  163. package/plugins/surveys/plugin.public.js +149 -0
  164. package/plugins/theme-switcher/admin/templates/theme-switcher.html +1 -1
  165. package/public/css/forms.css +1 -1
  166. package/public/css/search.css +1 -0
  167. package/public/css/site.css +1 -1
  168. package/public/js/collection-context.js +2 -2
  169. package/public/js/context-menus.js +1 -0
  170. package/public/js/form-logic-engine.js +1 -1
  171. package/public/js/forms.js +2 -2
  172. package/public/js/search.js +1 -0
  173. package/public/js/site.js +1 -1
  174. package/scripts/build.js +37 -3
  175. package/scripts/copy-domma.js +48 -0
  176. package/scripts/seed.js +1996 -0
  177. package/server/routes/api/collections.js +34 -0
  178. package/server/routes/api/context-menus.js +104 -0
  179. package/server/routes/api/forms.js +42 -3
  180. package/server/routes/api/notifications.js +69 -19
  181. package/server/routes/api/plugins.js +50 -6
  182. package/server/routes/api/search.js +43 -0
  183. package/server/routes/api/theme.js +69 -0
  184. package/server/routes/public.js +42 -7
  185. package/server/server.js +74 -0
  186. package/server/services/adapters/FileAdapter.js +6 -1
  187. package/server/services/content.js +26 -0
  188. package/server/services/contextMenus.js +477 -0
  189. package/server/services/markdown.js +70 -9
  190. package/server/services/permissionRegistry.js +24 -0
  191. package/server/services/pluginFiles.js +52 -11
  192. package/server/services/plugins.js +229 -6
  193. package/server/services/renderer.js +144 -22
  194. package/server/services/roles.js +1 -1
  195. package/server/services/search-migration.js +82 -0
  196. package/server/services/search.js +413 -0
  197. package/server/services/sidebar-migration.js +1 -0
  198. package/server/services/themeSettings.js +541 -0
  199. package/server/services/users.js +8 -0
  200. package/server/templates/page.html +4 -2
  201. package/plugins/contacts/data/contacts.json +0 -20
  202. package/plugins/notes/data/notes.json +0 -1
  203. package/plugins/site-search/admin/views/site-search.js +0 -116
  204. package/plugins/site-search/config.js +0 -15
  205. package/plugins/site-search/plugin.js +0 -188
  206. package/plugins/site-search/plugin.json +0 -40
  207. package/plugins/site-search/public/inject-body.html +0 -17
  208. package/plugins/site-search/public/inject-head.html +0 -1
  209. package/plugins/site-search/public/search.css +0 -1
  210. package/plugins/site-search/public/search.js +0 -1
  211. package/plugins/todo/data/todos.json +0 -1
package/admin/js/app.js CHANGED
@@ -1,6 +1,6 @@
1
- import{views as W}from"./views/index.js";import{api as l,getUser as c,isAuthenticated as m,logout as _}from"./api.js";import{installHttpInterceptor as B}from"./http-interceptor.js";$(()=>{B(),(async()=>{try{const t=m()?await l.settings.get():null;Domma.theme.init({theme:t?.adminTheme||"charcoal-dark",persist:!0})}catch{Domma.theme.init({theme:"charcoal-dark",persist:!0})}})();const A=["jb-company","jb-agent","jb-candidate"],k=["/job-board","/my-profile"];function h(t){return t&&A.includes(t.role)}function w(t){return k.some(e=>t===e||t.startsWith(e+"/"))}R.use(async(t,e,o)=>{if(t.path==="/login"||t.path==="/reset-password")return o();if(!m()){R.navigate("/login");return}if(h(c())&&!w(t.path)){R.navigate("/job-board");return}o()});async function f(){try{return(await l.get("/auth/permissions")).permissions||[]}catch{return[]}}async function C(t){const{renderAdminSidebar:e}=await import("./lib/sidebar-renderer.js");await e({mount:"#admin-sidebar",permissions:t})}M.subscribe("router:afterChange",({to:t})=>{(t.path==="/login"||t.path==="/reset-password")&&n&&(clearInterval(n),n=null)});const I=[{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:"/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:"/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:"/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:"projectEditor",title:"New Project - Domma CMS"},{path:"/projects/edit/:slug",view:"projectEditor",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(h(c())&&!w(t.path)){R.navigate("/job-board");return}if($("#admin-sidebar").show(),$("#admin-topbar").show(),e?.path==="/login"||e?.path==="/reset-password"){$("#topbar-user-name").remove(),await P();const o=await f();C(o);try{const i=await l.system.notifications.list().catch(()=>[]),s=(Array.isArray(i)?i:[]).filter(a=>a.unread&&["warning","critical"].includes(a.data?.severity)).slice(0,3);for(const a of s){const r=p(a.data?.title||""),d=p((a.data?.body||"").slice(0,120));E.toast(`${r} - ${d}`,{type:a.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}j()}}),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 U=["#field-project","#menu-project","#collection-project","#endpoint-project","#action-project","#view-project","#block-project","#component-project","#role-project",'[name="project"]','[name="ownedByProject"]'];function N(t){return/^\/(pages|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||!N(e))return;const i=new URLSearchParams(location.hash.split("?")[1]||"").get("project");if(!i||!/\/new$/.test(e))return;let s=0;const a=60,r=1500,d=()=>{s+=a;for(const L of U){const u=document.querySelector(L);if(!u)continue;if(Array.from(u.options||[]).find(T=>T.value===i)){u.value=i,u.dispatchEvent(new Event("change",{bubbles:!0}));return}}s<r&&setTimeout(d,a)};setTimeout(d,a)});const D="cms_card_states",g=S.get(D)||{};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&&g[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&&(g[o]=e.hasClass("card-collapsed")?"collapsed":"open",S.set(D,g))}),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 b={...W},v=[...I];async function P(){if(m())try{const t=await l.plugins.adminConfig();t.routes?.length&&v.push(...t.routes);for(const[e,o]of Object.entries(t.views||{}))try{const i=await import(`/plugins/${o.entry}`);b[e]=i[o.exportName]}catch{}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 j(){const t=c();if(!t||$("#topbar-user-name").length)return;const o={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role;$("#topbar-user").html(`
1
+ import{views as T}from"./views/index.js";import{api as l,getUser as c,isAuthenticated as m,logout as W}from"./api.js";import{installHttpInterceptor as _}from"./http-interceptor.js";import{installResizableSlideovers as B}from"./lib/slideover-resizable.js";$(()=>{_(),B(),(async()=>{try{const t=m()?await l.theme.get():null;Domma.theme.init({theme:t?.adminTheme||"charcoal-dark",persist:!0})}catch{Domma.theme.init({theme:"charcoal-dark",persist:!0})}})();const A=["jb-company","jb-agent","jb-candidate"],k=["/job-board","/my-profile"];function h(t){return t&&A.includes(t.role)}function w(t){return k.some(e=>t===e||t.startsWith(e+"/"))}R.use(async(t,e,a)=>{if(t.path==="/login"||t.path==="/reset-password")return a();if(!m()){R.navigate("/login");return}if(h(c())&&!w(t.path)){R.navigate("/job-board");return}a()});async function f(){try{return(await l.get("/auth/permissions")).permissions||[]}catch{return[]}}async function C(t){const{renderAdminSidebar:e}=await import("./lib/sidebar-renderer.js");await e({mount:"#admin-sidebar",permissions:t})}M.subscribe("router:afterChange",({to:t})=>{(t.path==="/login"||t.path==="/reset-password")&&n&&(clearInterval(n),n=null)});const I=[{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:"/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:"projectEditor",title:"New Project - Domma CMS"},{path:"/projects/edit/:slug",view:"projectEditor",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(h(c())&&!w(t.path)){R.navigate("/job-board");return}if($("#admin-sidebar").show(),$("#admin-topbar").show(),e?.path==="/login"||e?.path==="/reset-password"){$("#topbar-user-name").remove(),await P();const a=await f();C(a);try{const i=await l.system.notifications.list().catch(()=>[]),o=(Array.isArray(i)?i:[]).filter(s=>s.unread&&["warning","critical"].includes(s.data?.severity)).slice(0,3);for(const s of o){const r=p(s.data?.title||""),d=p((s.data?.body||"").slice(0,120));E.toast(`${r} - ${d}`,{type:s.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}j()}}),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 U=["#field-project","#menu-project","#collection-project","#endpoint-project","#action-project","#view-project","#block-project","#component-project","#role-project",'[name="project"]','[name="ownedByProject"]'];function N(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||!N(e))return;const i=new URLSearchParams(location.hash.split("?")[1]||"").get("project");if(!i||!/\/new$/.test(e))return;let o=0;const s=60,r=1500,d=()=>{o+=s;for(const x of U){const u=document.querySelector(x);if(!u)continue;if(Array.from(u.options||[]).find(L=>L.value===i)){u.value=i,u.dispatchEvent(new Event("change",{bubbles:!0}));return}}o<r&&setTimeout(d,s)};setTimeout(d,s)});const D="cms_card_states",g=S.get(D)||{};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&&g[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 a=$(this).find("h2, h3").first().text().trim();a&&(g[a]=e.hasClass("card-collapsed")?"collapsed":"open",S.set(D,g))}),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 b={...T},v=[...I];async function P(){if(m())try{const t=await l.plugins.adminConfig();t.routes?.length&&v.push(...t.routes);const{withPluginChrome:e}=await import("./lib/plugin-chrome.js");for(const[a,i]of Object.entries(t.views||{}))try{const o=await import(`/plugins/${i.entry}`);b[a]=e(o[i.exportName],(t.meta||{})[a])}catch{}for(const{id:a,href:i}of t.css||[])if(!document.getElementById(a)){const o=document.createElement("link");o.id=a,o.rel="stylesheet",o.href=i,document.head.appendChild(o)}}catch{}}function j(){const t=c();if(!t||$("#topbar-user-name").length)return;const a={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role;$("#topbar-user").html(`
2
2
  <span id="topbar-user-name" class="topbar-user-name">${p(t.name)}</span>
3
- <span class="topbar-role-badge topbar-role-badge--${p(t.role)}">${o}</span>
3
+ <span class="topbar-role-badge topbar-role-badge--${p(t.role)}">${a}</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 W}from"./views/index.js";import{api as l,getUser as c,isAuthenti
18
18
  <span data-icon="log-out"></span>
19
19
  <span>Sign out</span>
20
20
  </a>
21
- `),$("#topbar-logout-btn").on("click",i=>{i.preventDefault(),_()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"}),y(),n||(n=setInterval(y,6e4))}function p(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}let n=null;async function y(){if(m())try{const{count:t}=await l.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(!m()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash="#/login";else{const i=(window.location.hash||"#/").slice(1)||"/";if(h(c())&&!w(i)&&(window.location.hash="#/job-board"),await P(),c()){const a=await f();await C(a),j()}}R.init({container:"#view-container",routes:v,views:b,default:"/",transitions:{enter:"fadeIn",leave:"fadeOut",duration:150}});const t=R._extractParams.bind(R);R._extractParams=function(i,s){const a=s.indexOf("?");if(a!==-1&&(s=s.slice(0,a)),i.endsWith("/*")){const r=i.slice(0,-2);return s.startsWith(r+"/")?{}:null}return t(i,s)};const e=(window.location.hash||"#/").slice(1)||"/";v.filter(i=>i.path.endsWith("/*")).some(i=>e.startsWith(i.path.slice(0,-2)+"/"))&&R._handleRouteChange()})()});
21
+ `),$("#topbar-logout-btn").on("click",i=>{i.preventDefault(),W()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"}),y(),n||(n=setInterval(y,6e4))}function p(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}let n=null;async function y(){if(m())try{const{count:t}=await l.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(!m()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash="#/login";else{const i=(window.location.hash||"#/").slice(1)||"/";if(h(c())&&!w(i)&&(window.location.hash="#/job-board"),await P(),c()){const s=await f();await C(s),j()}}R.init({container:"#view-container",routes:v,views:b,default:"/",transitions:{enter:"fadeIn",leave:"fadeOut",duration:150}});const t=R._extractParams.bind(R);R._extractParams=function(i,o){const s=o.indexOf("?");if(s!==-1&&(o=o.slice(0,s)),i.endsWith("/*")){const r=i.slice(0,-2);return o.startsWith(r+"/")?{}:null}return t(i,o)};const e=(window.location.hash||"#/").slice(1)||"/";v.filter(i=>i.path.endsWith("/*")).some(i=>e.startsWith(i.path.slice(0,-2)+"/"))&&R._handleRouteChange()})()});
@@ -0,0 +1 @@
1
+ import{api as b}from"../api.js";function o(s,e){const r=document.createElement(s);return e&&(r.style.cssText=e),r}let m=null;export function invalidatePageCache(){m=null}async function y(){if(m)return m;const s=await b.pages.list().catch(()=>[]);return m=(Array.isArray(s)?s:[]).filter(e=>e&&e.urlPath).sort((e,r)=>String(e.urlPath).localeCompare(String(r.urlPath))),m}export function openPagePicker(s){const e=o("div","position:fixed;inset:0;background:rgba(0,0,0,.7);display:flex;align-items:center;justify-content:center;z-index:10000;padding:24px"),r=o("div","background:var(--dm-surface,#1e293b);border:1px solid var(--dm-border,#475569);border-radius:12px;padding:16px;width:100%;max-width:560px;max-height:70vh;display:flex;flex-direction:column;gap:12px"),a=o("div","display:flex;gap:8px;align-items:center"),t=o("input","flex:1;min-width:160px;padding:8px 12px;border-radius:8px;border:1px solid var(--dm-border,#334155);background:var(--dm-background,#0f172a);color:var(--dm-text,#e2e8f0);font-size:13px;font-family:inherit");t.type="text",t.placeholder="Search pages\u2026";const d=o("button","padding:8px 14px;background:var(--dm-border,#334155);border:none;border-radius:8px;color:var(--dm-text-muted,#94a3b8);font-size:12px;cursor:pointer");d.type="button",d.textContent="Close",d.addEventListener("click",()=>e.remove()),a.appendChild(t),a.appendChild(d);const u=o("div","font-size:11px;color:var(--dm-text-muted,#94a3b8)"),p=o("div","display:flex;flex-direction:column;gap:4px;overflow-y:auto;max-height:420px");let x=[];function f(){for(;p.firstChild;)p.removeChild(p.firstChild);const l=t.value.trim().toLowerCase(),c=x.filter(n=>!l||String(n.urlPath).toLowerCase().includes(l)||String(n.title||"").toLowerCase().includes(l));if(u.textContent=`${c.length} page${c.length===1?"":"s"}`,!c.length){const n=o("div","font-size:12px;color:var(--dm-text-muted,#94a3b8);padding:12px");n.textContent=x.length?"No page matches that.":"No pages yet.",p.appendChild(n);return}for(const n of c){const i=o("button","display:flex;align-items:baseline;gap:10px;width:100%;text-align:left;padding:8px 10px;border-radius:8px;border:1px solid transparent;cursor:pointer;background:var(--dm-background,#0f172a);color:var(--dm-text,#e2e8f0);font-family:inherit");i.type="button";const g=o("span","font-size:13px;font-weight:600");g.textContent=n.title||n.slug||n.urlPath;const h=o("span","font-size:11px;color:var(--dm-text-muted,#94a3b8);font-family:var(--dm-font-mono,monospace)");if(h.textContent=n.urlPath,i.appendChild(g),i.appendChild(h),n.status&&n.status!=="published"){const v=o("span","font-size:10px;text-transform:uppercase;letter-spacing:.04em;color:var(--dm-warning,#f59e0b);margin-left:auto");v.textContent=n.status,i.appendChild(v)}i.addEventListener("mouseenter",()=>{i.style.borderColor="var(--dm-primary)"}),i.addEventListener("mouseleave",()=>{i.style.borderColor="transparent"}),i.addEventListener("click",()=>{s(n.urlPath),e.remove()}),p.appendChild(i)}}t.addEventListener("input",f),r.appendChild(a),r.appendChild(u),r.appendChild(p),e.appendChild(r),e.addEventListener("click",l=>{l.target===e&&e.remove()}),document.addEventListener("keydown",function l(c){c.key==="Escape"&&(e.remove(),document.removeEventListener("keydown",l))}),document.body.appendChild(e),u.textContent="Loading\u2026",y().then(l=>{x=l,f(),t.focus()})}export function makePageInput(s="/about",e=""){const r=o("div","display:flex;gap:.35rem;"),a=document.createElement("input");a.type="text",a.className="form-input",a.placeholder=s,a.value=e||"",a.style.flex="1";const t=document.createElement("button");t.type="button",t.title="Browse pages",t.style.cssText="padding:.35rem .55rem;background:transparent;border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text-muted,#aaa);cursor:pointer;flex-shrink:0;display:inline-flex;align-items:center;";const d=document.createElement("span");return d.setAttribute("data-icon","file-text"),d.setAttribute("data-icon-size","14"),d.style.cssText="display:inline-flex;align-items:center;vertical-align:middle;",t.appendChild(d),window.I?.scan?.(t),t.addEventListener("click",()=>openPagePicker(u=>{a.value=u,a.dispatchEvent(new Event("input",{bubbles:!0}))})),r.appendChild(a),r.appendChild(t),{el:r,input:a}}
@@ -0,0 +1 @@
1
+ const a={invoice:150,contacts:210,blog:265,analytics:285,"mail-reader":190,"email-pro":190,messages:220,notes:45,todo:25,surveys:330,calendar:10,"site-search":200,"theme-switcher":310,"demo-viewer":175,docs:240,garage:20,"form-builder":255,"site-manager":205,"site-tweaks":95,"shopping-cart":140,"job-board":35,"customer-registration":170,_template:0};function n(t){let e=0;for(let r=0;r<t.length;r++)e=(e*31+t.charCodeAt(r))%360;return e}function i(t){return t==="_template"?0:62}export function pluginAccent(t){const e=String(t||"");return`hsl(${a[e]??n(e)} ${i(e)}% 52%)`}export function pluginWatermark(t){const e=document.createElement("span");return e.className="plugin-watermark",e.setAttribute("data-icon",t||"package"),e.setAttribute("data-icon-size","148"),e.setAttribute("aria-hidden","true"),e}
@@ -0,0 +1 @@
1
+ const p=/^(built in|included\b)/i;export function formatReleased(n){if(!n||typeof n!="string"||!/^\d{4}-\d{2}-\d{2}/.test(n))return"";const e=new Date(n);if(Number.isNaN(e.getTime()))return"";const t=c=>String(c).padStart(2,"0");return`${t(e.getDate())}.${t(e.getMonth()+1)}.${e.getFullYear()}`}export function buildBanner(n){const e=n||{},t=document.createElement("header");t.className="dm-plugin-banner";const c=document.createElement("div");c.className="dm-plugin-banner-identity";const a=document.createElement("span");a.className="dm-plugin-banner-icon",a.setAttribute("data-icon",e.icon||"package"),a.setAttribute("data-icon-size","18"),c.appendChild(a);const r=document.createElement("h1");r.className="dm-plugin-banner-name",r.textContent=e.displayName||e.plugin||"Plugin",c.appendChild(r),t.appendChild(c);const o=document.createElement("div");o.className="dm-plugin-banner-facts",((i,l)=>{if(!i)return;const d=document.createElement("span");d.className="dm-plugin-fact"+(l?" "+l:""),d.textContent=i,o.appendChild(d)})(e.version?`v${e.version}`:"","dm-plugin-fact--version");const s=formatReleased(e.date);if(s){const i=document.createElement("span");i.className="dm-plugin-fact dm-plugin-fact--date",i.title="Release date",i.textContent=s,o.appendChild(i)}if(e.licence){const i=document.createElement("span");i.className="dm-plugin-fact dm-plugin-fact--licence"+(p.test(e.licence)?" is-included":" is-licensed"),i.title="Licence",i.textContent=e.licence,o.appendChild(i)}return t.appendChild(o),t}export function withPluginChrome(n,e){if(!n||typeof n!="object")return n;const t=Object.create(Object.getPrototypeOf(n));Object.assign(t,n);const c=typeof n.onMount=="function"?n.onMount:null;return t.onMount=async function(...a){const r=a[0],o=r&&typeof r.get=="function"?r.get(0):r;return o&&o.nodeType===1&&!o.querySelector(":scope > .dm-plugin-shell")&&applyChrome(o,e),c?c.apply(n,a):void 0},t}export function applyChrome(n,e){const t=document.createElement("section");t.className="dm-plugin-shell",e&&e.plugin&&t.setAttribute("data-plugin",e.plugin),t.appendChild(buildBanner(e));const c=document.createElement("div");for(c.className="dm-plugin-body";n.firstChild;)c.appendChild(n.firstChild);t.appendChild(c),n.appendChild(t),g(c,e),f(c),typeof I<"u"&&I.scan?I.scan(t):typeof Domma<"u"&&Domma.icons&&Domma.icons.scan()}function m(n){return[...n.children].filter(e=>!/^(STYLE|SCRIPT|TEMPLATE|LINK)$/.test(e.tagName))}function f(n){let e=n;for(let t=0;t<2;t++){const c=m(e);if(c.length!==1)return;if(e=c[0],e.classList.contains("card")){e.classList.add("dm-plugin-unwrap");return}}}function g(n,e){const t=String(e&&e.displayName||"").trim().toLowerCase(),c=n.querySelector(":scope > .view-header, :scope > * > .view-header");if(c){const a=c.querySelector("h1, h2"),r=c.querySelector("button, a, input, select");a&&r?a.remove():a&&c.remove();return}if(t){for(const a of n.querySelectorAll("h1, h2"))if(a.textContent.trim().toLowerCase()===t){a.remove();return}}}export function addBannerAction(n,{icon:e,title:t,onClick:c,id:a="",label:r=""}){const o=n&&typeof n.get=="function"?n.get(0):n;if(!o||o.nodeType!==1)return null;const u=o.querySelector(":scope > .dm-plugin-shell > .dm-plugin-banner")||o.closest(".dm-plugin-shell")?.querySelector(":scope > .dm-plugin-banner");if(!u)return null;let s=u.querySelector(":scope > .dm-plugin-banner-facts");s||(s=document.createElement("div"),s.className="dm-plugin-banner-facts",u.appendChild(s));let i=s.querySelector(":scope > .dm-plugin-banner-actions");i||(i=document.createElement("div"),i.className="dm-plugin-banner-actions",s.insertBefore(i,s.firstChild)),a&&i.querySelector(`[data-banner-action="${CSS.escape(a)}"]`)?.remove();const l=document.createElement("button");l.type="button",l.className="btn btn-sm btn-ghost dm-plugin-banner-action",l.title=t||"",l.setAttribute("aria-label",t||r||"Action"),a&&(l.dataset.bannerAction=a);const d=document.createElement("span");return d.setAttribute("data-icon",e||"settings"),d.setAttribute("data-icon-size","16"),l.appendChild(d),r&&l.appendChild(document.createTextNode(` ${r}`)),l.addEventListener("click",c),i.appendChild(l),typeof I<"u"&&I.scan?I.scan(l):typeof Domma<"u"&&Domma.icons&&Domma.icons.scan(l),l}
@@ -1,3 +1,3 @@
1
- import{SHORTCODES as $}from"./shortcode-registry.js";import{EFFECTS as C}from"./effect-defs.js";import{boolState as B,boolValue as K,findEnclosingShortcode as U,setAttr as M,setAttrs as V,effectAttrKeys as G}from"./shortcode-tags.js";import{insertAtCursor as X}from"./markdown-toolbar.js";import{clearLookupCache as q,lookupOptions as J}from"./shortcode-lookups.js";import{insertMenuGroups as Q,insertShortcodeByName as Z}from"./shortcode-insert.js";import{openShortcodeForm as N}from"./shortcode-form.js";function ee(e){return J(e,c?.span?.attrs||{})}const te=$.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),ne=new Map($.map(e=>[e.name,e])),oe=new Map(C.map(e=>[e.name,e])),se=C.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),ie=[...te,...se],R=new Set(["card","hero","banner","col","button"]);let c=null;const z="position:fixed;z-index:10000;min-width:210px;max-height:70vh;overflow-y:auto;background:var(--dm-surface,#1c1c30);border:1px solid var(--dm-border,#2a2a40);border-radius:6px;box-shadow:var(--dm-shadow-lg);padding:.3rem;font-size:.83rem;color:var(--dm-text,#e5e5e5);",p="display:flex;align-items:center;gap:.5rem;width:100%;text-align:left;padding:.35rem .55rem;border:none;background:transparent;color:inherit;border-radius:4px;cursor:pointer;font-size:.83rem;",S="padding:.3rem .55rem;color:var(--dm-text-muted,#8a8a9a);font-size:.68rem;text-transform:uppercase;letter-spacing:.06em;",P="flex:1;min-width:0;padding:.2rem .4rem;background:var(--dm-input-bg,#12121e);border:1px solid var(--dm-border,#333);border-radius:3px;color:var(--dm-text,#eee);font-size:.8rem;",ce="height:1px;background:var(--dm-border,#2a2a40);margin:.3rem .2rem;";function m(e){return e.addEventListener("mouseenter",()=>{e.style.background="var(--dm-primary,#4338ca)",e.style.color="var(--dm-primary-text,#fff)"}),e.addEventListener("mouseleave",()=>{e.style.background="transparent",e.style.color="inherit"}),e}export function attachContextMenu(e){e.addEventListener("contextmenu",n=>{n.preventDefault(),h();const t=U(e,ie);ae(e,t,n.clientX,n.clientY)})}function h(){c&&(v(),c.root.remove(),document.removeEventListener("mousedown",H,!0),document.removeEventListener("keydown",Y,!0),window.removeEventListener("scroll",W,!0),c=null)}const H=e=>{c&&!c.root.contains(e.target)&&!(c.submenu&&c.submenu.contains(e.target))&&h()},W=e=>{c&&!c.root.contains(e.target)&&!(c.submenu&&c.submenu.contains(e.target))&&h()},Y=e=>{e.key==="Escape"&&h()};function ae(e,n,t,o){q();const s=document.createElement("div");s.className="dm-sc-menu",s.style.cssText=z,c={root:s,textarea:e,span:n,submenu:null};const i=n&&(ne.get(n.name)||oe.get(n.name)),l=n&&R.has(n.name)?C.filter(r=>r.name in n.attrs):[];i&&re(s,i);for(const r of l)xe(s,r);(i||l.length)&&s.appendChild(le()),s.appendChild(y("Add effect\u2026",()=>be())),s.appendChild(y("Insert\u2026",()=>ye()));const a=e.selectionStart!==e.selectionEnd;(n||a)&&s.appendChild(y(a?"Wrap selection in\u2026":"Wrap in\u2026",()=>Ee())),document.body.appendChild(s),O(s,t,o),document.addEventListener("mousedown",H,!0),document.addEventListener("keydown",Y,!0),window.addEventListener("scroll",W,!0),window.I&&I.scan(s)}function le(){const e=document.createElement("div");return e.style.cssText=ce,e}function k(e){const n=document.createElement("div");return n.style.cssText=S,n.textContent=e,n}function re(e,n){e.appendChild(k(`${n.label} options`));const t=m(document.createElement("button"));t.type="button",t.style.cssText=p,t.innerHTML='<span data-icon="edit" data-icon-size="13" style="display:inline-flex;align-items:center;"></span>',t.appendChild(document.createTextNode(`Edit ${n.label}\u2026`)),t.addEventListener("click",()=>{const{textarea:o,span:s}=c;h(),N(o,n,{span:s})}),e.appendChild(t);for(const o of n.attrs||[])e.appendChild(j(o))}function j(e,n=e.name){const t=c.span.attrs[n];switch(e.type){case"checkbox":return de(e,n,B(t,e.default));case"select":return me(e,n,t);case"lookup":return he(e,n,t);case"colour":return pe(e,n,t);case"icon":return fe(e,n,t);default:return ue(e,n,t)}}function b(e,n){c.span=M(c.textarea,c.span,e,n)}function de(e,n,t){const o=m(document.createElement("label"));o.style.cssText=p;const s=document.createElement("input");return s.type="checkbox",s.checked=t,s.addEventListener("change",()=>b(n,K(e,s.checked))),o.appendChild(s),o.appendChild(document.createTextNode(e.label)),o}function ue(e,n,t){const o=document.createElement("div");o.style.cssText=p+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.cssText="white-space:nowrap;color:var(--dm-text-muted,#9a9aac);";const i=document.createElement("input");i.type=e.type==="number"?"number":"text",i.value=t===!0||t==null?"":t,e.default!=null&&e.default!==""&&(i.placeholder=String(e.default)),i.style.cssText=P;let l=null;return i.addEventListener("input",()=>{clearTimeout(l),l=setTimeout(()=>b(n,i.value.trim()),180)}),i.addEventListener("mousedown",a=>a.stopPropagation()),o.appendChild(s),o.appendChild(i),o}function pe(e,n,t){const o=document.createElement("div");o.style.cssText=p+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.flex="1";const i=document.createElement("input");return i.type="color",typeof t=="string"&&/^#/.test(t)?i.value=t:typeof e.default=="string"&&/^#/.test(e.default)&&(i.value=e.default),i.addEventListener("input",()=>b(n,i.value)),o.appendChild(s),o.appendChild(i),o}function me(e,n,t){const o=t==null||t===!0?e.default??"":t;return y(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=g();for(const[i,l]of e.options||[]){const a=m(document.createElement("button"));a.type="button",a.style.cssText=p,a.textContent=l,i===o&&(a.style.fontWeight="700"),a.addEventListener("click",()=>{b(n,i),v()}),s.appendChild(a)}return s})}function fe(e,n,t){const o=t==null||t===!0?"":String(t),s=o?`${e.label}: ${o}`:e.label;return y(s,()=>{const i=g();i.style.width="232px";const l=document.createElement("input");l.type="text",l.placeholder="Search icons\u2026",l.style.cssText=P+"margin:6px;width:calc(100% - 12px);",l.addEventListener("mousedown",T=>T.stopPropagation()),i.appendChild(l);const a=m(document.createElement("button"));a.type="button",a.style.cssText=p,a.textContent="- none -",o||(a.style.fontWeight="700"),a.addEventListener("click",()=>{b(n,null),v()}),i.appendChild(a);const r=document.createElement("div");r.style.cssText="display:flex;flex-wrap:wrap;gap:2px;padding:4px 6px;max-height:220px;overflow-y:auto;",i.appendChild(r);const u=96,w=()=>{const T=typeof I<"u"&&typeof I.list=="function"?I.list():[],A=l.value.trim().toLowerCase(),_=A?T.filter(d=>d.toLowerCase().includes(A)):T;r.replaceChildren();for(const d of _.slice(0,u)){const x=m(document.createElement("button"));x.type="button",x.title=d,x.style.cssText="width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;background:none;border:1px solid transparent;border-radius:4px;cursor:pointer;color:inherit;",d===o&&(x.style.borderColor="var(--dm-primary,#5b8cff)");const L=document.createElement("span");L.setAttribute("data-icon",d),L.setAttribute("data-icon-size","15"),L.style.cssText="display:inline-flex;align-items:center;",x.appendChild(L),x.addEventListener("click",()=>{b(n,d),v()}),r.appendChild(x)}if(_.length>u){const d=document.createElement("div");d.style.cssText=S,d.textContent=`${_.length-u} more - refine search`,r.appendChild(d)}if(!_.length){const d=document.createElement("div");d.style.cssText=S,d.textContent="No icons match",r.appendChild(d)}window.I&&I.scan(r)};let f=null;return l.addEventListener("input",()=>{clearTimeout(f),f=setTimeout(w,120)}),w(),setTimeout(()=>l.focus(),0),i})}function he(e,n,t){const o=t==null||t===!0?e.default??"":t;return y(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=g();s.style.minHeight="48px";const i=typeof E<"u"&&E.loader?E.loader(s,{type:"dots"}):null;return ee(e.source).then(l=>{if(i&&i.destroy(),!c||c.submenu!==s)return;if(s.style.minHeight="",!l.length){const u=document.createElement("div");u.style.cssText=S,u.textContent="Nothing found",s.appendChild(u)}const a=l.some(([u])=>u===o),r=[["","- none -"],...l];o!==""&&!a&&r.push([o,`${o} (missing)`]);for(const[u,w]of r){const f=m(document.createElement("button"));f.type="button",f.style.cssText=p,f.textContent=w,u===o&&(f.style.fontWeight="700"),f.addEventListener("click",()=>{b(n,u===""?null:u),v()}),s.appendChild(f)}}),s})}function xe(e,n){e.appendChild(k(`Effect: ${n.label}`));for(const o of n.attrs||[])e.appendChild(j(o,`${n.name}-${o.name}`));const t=m(document.createElement("button"));t.type="button",t.style.cssText=p,t.innerHTML='<span data-icon="trash" data-icon-size="13" style="display:inline-flex;align-items:center;"></span> Remove effect',t.addEventListener("click",()=>{const o={};for(const s of G(c.span.attrs,n.name))o[s]=null;V(c.textarea,c.span,o),h()}),e.appendChild(t)}function be(){const e=g(),n=c.span,t=n&&R.has(n.name)?n.name:null;for(const o of[...new Set(C.map(s=>s.category))]){const s=C.filter(i=>i.category===o);if(s.length){e.appendChild(k(o));for(const i of s){const l=t&&(i.hosts||[]).includes(t)&&!(i.name in n.attrs),a=m(document.createElement("button"));if(a.type="button",a.style.cssText=p+"justify-content:space-between;",a.appendChild(document.createTextNode(i.label)),l){const r=document.createElement("span");r.textContent=`inject \u2192 [${t}]`,r.style.cssText="opacity:.6;font-size:.7rem;",a.appendChild(r)}a.addEventListener("click",()=>{if(l)M(c.textarea,c.span,i.name,!0);else{const r=i.selfClosing?`[${i.name} /]`:`[${i.name}]
1
+ import{SHORTCODES as $}from"./shortcode-registry.js";import{EFFECTS as C}from"./effect-defs.js";import{boolState as V,boolValue as G,findEnclosingShortcode as M,setAttr as N,setAttrs as X,effectAttrKeys as q}from"./shortcode-tags.js";import{insertAtCursor as J}from"./markdown-toolbar.js";import{clearLookupCache as Q,lookupOptions as Z}from"./shortcode-lookups.js";import{insertMenuGroups as ee,insertShortcodeByName as te}from"./shortcode-insert.js";import{openShortcodeForm as R}from"./shortcode-form.js";function ne(e){return Z(e,c?.span?.attrs||{})}const oe=$.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),se=new Map($.map(e=>[e.name,e])),ie=new Map(C.map(e=>[e.name,e])),ce=C.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),z=[...oe,...ce],D=new Set(["card","hero","banner","col","button"]);let c=null;const P="position:fixed;z-index:10000;min-width:210px;max-height:70vh;overflow-y:auto;background:var(--dm-surface,#1c1c30);border:1px solid var(--dm-border,#2a2a40);border-radius:6px;box-shadow:var(--dm-shadow-lg);padding:.3rem;font-size:.83rem;color:var(--dm-text,#e5e5e5);",p="display:flex;align-items:center;gap:.5rem;width:100%;text-align:left;padding:.35rem .55rem;border:none;background:transparent;color:inherit;border-radius:4px;cursor:pointer;font-size:.83rem;",S="padding:.3rem .55rem;color:var(--dm-text-muted,#8a8a9a);font-size:.68rem;text-transform:uppercase;letter-spacing:.06em;",H="flex:1;min-width:0;padding:.2rem .4rem;background:var(--dm-input-bg,#12121e);border:1px solid var(--dm-border,#333);border-radius:3px;color:var(--dm-text,#eee);font-size:.8rem;",ae="height:1px;background:var(--dm-border,#2a2a40);margin:.3rem .2rem;";function m(e){return e.addEventListener("mouseenter",()=>{e.style.background="var(--dm-primary,#4338ca)",e.style.color="var(--dm-primary-text,#fff)"}),e.addEventListener("mouseleave",()=>{e.style.background="transparent",e.style.color="inherit"}),e}export function attachContextMenu(e){return typeof Domma>"u"||!Domma.elements?.contextMenu?(e.addEventListener("contextmenu",n=>{n.preventDefault(),f();const t=M(e,z);F(e,t,n.clientX,n.clientY)}),null):Domma.elements.contextMenu(e,{inherit:!1,render:n=>{f();const t=M(e,z);F(e,t,n.x,n.y)}})}function f(){c&&(v(),c.root.remove(),document.removeEventListener("mousedown",W,!0),document.removeEventListener("keydown",j,!0),window.removeEventListener("scroll",Y,!0),c=null)}const W=e=>{c&&!c.root.contains(e.target)&&!(c.submenu&&c.submenu.contains(e.target))&&f()},Y=e=>{c&&!c.root.contains(e.target)&&!(c.submenu&&c.submenu.contains(e.target))&&f()},j=e=>{e.key==="Escape"&&f()};function F(e,n,t,o){Q();const s=document.createElement("div");s.className="dm-sc-menu",s.style.cssText=P,c={root:s,textarea:e,span:n,submenu:null};const i=n&&(se.get(n.name)||ie.get(n.name)),l=n&&D.has(n.name)?C.filter(r=>r.name in n.attrs):[];i&&re(s,i);for(const r of l)xe(s,r);(i||l.length)&&s.appendChild(le()),s.appendChild(y("Add effect\u2026",()=>be())),s.appendChild(y("Insert\u2026",()=>ye()));const a=e.selectionStart!==e.selectionEnd;(n||a)&&s.appendChild(y(a?"Wrap selection in\u2026":"Wrap in\u2026",()=>Ee())),document.body.appendChild(s),U(s,t,o),document.addEventListener("mousedown",W,!0),document.addEventListener("keydown",j,!0),window.addEventListener("scroll",Y,!0),window.I&&I.scan(s)}function le(){const e=document.createElement("div");return e.style.cssText=ae,e}function k(e){const n=document.createElement("div");return n.style.cssText=S,n.textContent=e,n}function re(e,n){e.appendChild(k(`${n.label} options`));const t=m(document.createElement("button"));t.type="button",t.style.cssText=p,t.innerHTML='<span data-icon="edit" data-icon-size="13" style="display:inline-flex;align-items:center;"></span>',t.appendChild(document.createTextNode(`Edit ${n.label}\u2026`)),t.addEventListener("click",()=>{const{textarea:o,span:s}=c;f(),R(o,n,{span:s})}),e.appendChild(t);for(const o of n.attrs||[])e.appendChild(O(o))}function O(e,n=e.name){const t=c.span.attrs[n];switch(e.type){case"checkbox":return de(e,n,V(t,e.default));case"select":return me(e,n,t);case"lookup":return he(e,n,t);case"colour":return pe(e,n,t);case"icon":return fe(e,n,t);default:return ue(e,n,t)}}function b(e,n){c.span=N(c.textarea,c.span,e,n)}function de(e,n,t){const o=m(document.createElement("label"));o.style.cssText=p;const s=document.createElement("input");return s.type="checkbox",s.checked=t,s.addEventListener("change",()=>b(n,G(e,s.checked))),o.appendChild(s),o.appendChild(document.createTextNode(e.label)),o}function ue(e,n,t){const o=document.createElement("div");o.style.cssText=p+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.cssText="white-space:nowrap;color:var(--dm-text-muted,#9a9aac);";const i=document.createElement("input");i.type=e.type==="number"?"number":"text",i.value=t===!0||t==null?"":t,e.default!=null&&e.default!==""&&(i.placeholder=String(e.default)),i.style.cssText=H;let l=null;return i.addEventListener("input",()=>{clearTimeout(l),l=setTimeout(()=>b(n,i.value.trim()),180)}),i.addEventListener("mousedown",a=>a.stopPropagation()),o.appendChild(s),o.appendChild(i),o}function pe(e,n,t){const o=document.createElement("div");o.style.cssText=p+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.flex="1";const i=document.createElement("input");return i.type="color",typeof t=="string"&&/^#/.test(t)?i.value=t:typeof e.default=="string"&&/^#/.test(e.default)&&(i.value=e.default),i.addEventListener("input",()=>b(n,i.value)),o.appendChild(s),o.appendChild(i),o}function me(e,n,t){const o=t==null||t===!0?e.default??"":t;return y(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=g();for(const[i,l]of e.options||[]){const a=m(document.createElement("button"));a.type="button",a.style.cssText=p,a.textContent=l,i===o&&(a.style.fontWeight="700"),a.addEventListener("click",()=>{b(n,i),v()}),s.appendChild(a)}return s})}function fe(e,n,t){const o=t==null||t===!0?"":String(t),s=o?`${e.label}: ${o}`:e.label;return y(s,()=>{const i=g();i.style.width="232px";const l=document.createElement("input");l.type="text",l.placeholder="Search icons\u2026",l.style.cssText=H+"margin:6px;width:calc(100% - 12px);",l.addEventListener("mousedown",T=>T.stopPropagation()),i.appendChild(l);const a=m(document.createElement("button"));a.type="button",a.style.cssText=p,a.textContent="- none -",o||(a.style.fontWeight="700"),a.addEventListener("click",()=>{b(n,null),v()}),i.appendChild(a);const r=document.createElement("div");r.style.cssText="display:flex;flex-wrap:wrap;gap:2px;padding:4px 6px;max-height:220px;overflow-y:auto;",i.appendChild(r);const u=96,w=()=>{const T=typeof I<"u"&&typeof I.list=="function"?I.list():[],A=l.value.trim().toLowerCase(),_=A?T.filter(d=>d.toLowerCase().includes(A)):T;r.replaceChildren();for(const d of _.slice(0,u)){const x=m(document.createElement("button"));x.type="button",x.title=d,x.style.cssText="width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;background:none;border:1px solid transparent;border-radius:4px;cursor:pointer;color:inherit;",d===o&&(x.style.borderColor="var(--dm-primary,#5b8cff)");const L=document.createElement("span");L.setAttribute("data-icon",d),L.setAttribute("data-icon-size","15"),L.style.cssText="display:inline-flex;align-items:center;",x.appendChild(L),x.addEventListener("click",()=>{b(n,d),v()}),r.appendChild(x)}if(_.length>u){const d=document.createElement("div");d.style.cssText=S,d.textContent=`${_.length-u} more - refine search`,r.appendChild(d)}if(!_.length){const d=document.createElement("div");d.style.cssText=S,d.textContent="No icons match",r.appendChild(d)}window.I&&I.scan(r)};let h=null;return l.addEventListener("input",()=>{clearTimeout(h),h=setTimeout(w,120)}),w(),setTimeout(()=>l.focus(),0),i})}function he(e,n,t){const o=t==null||t===!0?e.default??"":t;return y(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=g();s.style.minHeight="48px";const i=typeof E<"u"&&E.loader?E.loader(s,{type:"dots"}):null;return ne(e.source).then(l=>{if(i&&i.destroy(),!c||c.submenu!==s)return;if(s.style.minHeight="",!l.length){const u=document.createElement("div");u.style.cssText=S,u.textContent="Nothing found",s.appendChild(u)}const a=l.some(([u])=>u===o),r=[["","- none -"],...l];o!==""&&!a&&r.push([o,`${o} (missing)`]);for(const[u,w]of r){const h=m(document.createElement("button"));h.type="button",h.style.cssText=p,h.textContent=w,u===o&&(h.style.fontWeight="700"),h.addEventListener("click",()=>{b(n,u===""?null:u),v()}),s.appendChild(h)}}),s})}function xe(e,n){e.appendChild(k(`Effect: ${n.label}`));for(const o of n.attrs||[])e.appendChild(O(o,`${n.name}-${o.name}`));const t=m(document.createElement("button"));t.type="button",t.style.cssText=p,t.innerHTML='<span data-icon="trash" data-icon-size="13" style="display:inline-flex;align-items:center;"></span> Remove effect',t.addEventListener("click",()=>{const o={};for(const s of q(c.span.attrs,n.name))o[s]=null;X(c.textarea,c.span,o),f()}),e.appendChild(t)}function be(){const e=g(),n=c.span,t=n&&D.has(n.name)?n.name:null;for(const o of[...new Set(C.map(s=>s.category))]){const s=C.filter(i=>i.category===o);if(s.length){e.appendChild(k(o));for(const i of s){const l=t&&(i.hosts||[]).includes(t)&&!(i.name in n.attrs),a=m(document.createElement("button"));if(a.type="button",a.style.cssText=p+"justify-content:space-between;",a.appendChild(document.createTextNode(i.label)),l){const r=document.createElement("span");r.textContent=`inject \u2192 [${t}]`,r.style.cssText="opacity:.6;font-size:.7rem;",a.appendChild(r)}a.addEventListener("click",()=>{if(l)N(c.textarea,c.span,i.name,!0);else{const r=i.selfClosing?`[${i.name} /]`:`[${i.name}]
2
2
  Content
3
- [/${i.name}]`;X(c.textarea,r)}h()}),e.appendChild(a)}}}return e}function y(e,n){const t=m(document.createElement("button"));t.type="button",t.style.cssText=p+"justify-content:space-between;",t.appendChild(document.createTextNode(e));const o=document.createElement("span");return o.setAttribute("data-icon","chevron-right"),o.setAttribute("data-icon-size","12"),o.style.cssText="opacity:.6;display:inline-flex;align-items:center;flex-shrink:0;",t.appendChild(o),t.addEventListener("mouseenter",()=>D(t,n)),t.addEventListener("click",s=>{s.stopPropagation(),D(t,n)}),t}function g(){const e=document.createElement("div");return e.className="dm-sc-submenu",e.style.cssText=z,e}function D(e,n){v();const t=n();c.submenu=t,document.body.appendChild(t);const o=e.getBoundingClientRect();O(t,o.right+2,o.top,o.left-2),window.I&&I.scan(t)}function v(){c&&c.submenu&&(c.submenu.remove(),c.submenu=null)}function F(e){const n=m(document.createElement("button"));if(n.type="button",n.style.cssText=p,e.icon){const t=document.createElement("span");t.setAttribute("data-icon",e.icon),t.setAttribute("data-icon-size","14"),t.style.cssText="opacity:.75;display:inline-flex;align-items:center;flex-shrink:0;",n.appendChild(t)}return n.appendChild(document.createTextNode(e.label)),n}function ye(){const e=g();for(const{category:n,items:t}of Q()){e.appendChild(k(n));for(const o of t){const s=F(o);s.addEventListener("click",()=>{const i=c.textarea;h(),Z(o.name,i)}),e.appendChild(s)}}return e}function Ee(){const e=g();for(const n of $.filter(t=>t.wrappable||t.container)){const t=F(n);t.addEventListener("click",()=>{const o=c.textarea;h(),N(o,n)}),e.appendChild(t)}return e}function O(e,n,t,o){e.style.left="0px",e.style.top="0px";const s=e.getBoundingClientRect();let i=n;n+s.width>window.innerWidth-8&&(i=o!=null?o-s.width:n-s.width);let l=t;t+s.height>window.innerHeight-8&&(l=Math.max(8,window.innerHeight-s.height-8)),e.style.left=`${Math.max(8,i)}px`,e.style.top=`${Math.max(8,l)}px`}
3
+ [/${i.name}]`;J(c.textarea,r)}f()}),e.appendChild(a)}}}return e}function y(e,n){const t=m(document.createElement("button"));t.type="button",t.style.cssText=p+"justify-content:space-between;",t.appendChild(document.createTextNode(e));const o=document.createElement("span");return o.setAttribute("data-icon","chevron-right"),o.setAttribute("data-icon-size","12"),o.style.cssText="opacity:.6;display:inline-flex;align-items:center;flex-shrink:0;",t.appendChild(o),t.addEventListener("mouseenter",()=>B(t,n)),t.addEventListener("click",s=>{s.stopPropagation(),B(t,n)}),t}function g(){const e=document.createElement("div");return e.className="dm-sc-submenu",e.style.cssText=P,e}function B(e,n){v();const t=n();c.submenu=t,document.body.appendChild(t);const o=e.getBoundingClientRect();U(t,o.right+2,o.top,o.left-2),window.I&&I.scan(t)}function v(){c&&c.submenu&&(c.submenu.remove(),c.submenu=null)}function K(e){const n=m(document.createElement("button"));if(n.type="button",n.style.cssText=p,e.icon){const t=document.createElement("span");t.setAttribute("data-icon",e.icon),t.setAttribute("data-icon-size","14"),t.style.cssText="opacity:.75;display:inline-flex;align-items:center;flex-shrink:0;",n.appendChild(t)}return n.appendChild(document.createTextNode(e.label)),n}function ye(){const e=g();for(const{category:n,items:t}of ee()){e.appendChild(k(n));for(const o of t){const s=K(o);s.addEventListener("click",()=>{const i=c.textarea;f(),te(o.name,i)}),e.appendChild(s)}}return e}function Ee(){const e=g();for(const n of $.filter(t=>t.wrappable||t.container)){const t=K(n);t.addEventListener("click",()=>{const o=c.textarea;f(),R(o,n)}),e.appendChild(t)}return e}function U(e,n,t,o){e.style.left="0px",e.style.top="0px";const s=e.getBoundingClientRect();let i=n;n+s.width>window.innerWidth-8&&(i=o!=null?o-s.width:n-s.width);let l=t;t+s.height>window.innerHeight-8&&(l=Math.max(8,window.innerHeight-s.height-8)),e.style.left=`${Math.max(8,i)}px`,e.style.top=`${Math.max(8,l)}px`}
@@ -1 +1 @@
1
- export const TOOLS_FOLDER_TEXT="Tools",PLUGINS_FOLDER_TEXT="Plugins",MANAGE_PLUGINS_URL="#/plugins",MANAGE_PLUGINS_ITEM={text:"Manage Plugins",url:MANAGE_PLUGINS_URL,icon:"sliders",permission:"plugins"};export function stripItemByUrl(e,s){const t=[];for(const n of e||[]){if(!n||n.url===s)continue;const o={...n};Array.isArray(n.items)&&n.items.length&&(o.items=stripItemByUrl(n.items,s)),t.push(o)}return t}export function groupPluginItems(e){const s=[],t=[],n=[];for(const i of e||[])if(!(!i||!i.item)){if(i.parent){s.push(i);continue}i.item.core===!0?t.push(i.item):n.push(i.item)}const o=t.length?{text:TOOLS_FOLDER_TEXT,icon:"tool",items:t}:null,r={text:PLUGINS_FOLDER_TEXT,icon:"package",items:[{...MANAGE_PLUGINS_ITEM},...n]};return{toolsFolder:o,pluginsFolder:r,parented:s}}export function insertFoldersBeforeSystem(e,s){const t=(s||[]).filter(Boolean);if(!t.length)return e;const n=["system","documentation"],o=e.findIndex(r=>n.includes(String(r&&r.text||"").toLowerCase()));return o===-1?[...e,...t]:[...e.slice(0,o),...t,...e.slice(o)]}export function pruneEmptySynthesisedFolders(e){const s=new Set([TOOLS_FOLDER_TEXT,PLUGINS_FOLDER_TEXT]);return(e||[]).filter(t=>!t||!s.has(t.text)?!0:Array.isArray(t.items)&&t.items.length>0)}
1
+ export const TOOLS_FOLDER_TEXT="Tools",MANAGE_PLUGINS_URL="#/plugins",MANAGE_PLUGINS_ITEM={text:"Manage Plugins",url:MANAGE_PLUGINS_URL,icon:"sliders",permission:"plugins"};export function stripItemByUrl(e,o){const t=[];for(const s of e||[]){if(!s||s.url===o)continue;const n={...s};Array.isArray(s.items)&&s.items.length&&(n.items=stripItemByUrl(s.items,o)),t.push(n)}return t}export function groupPluginItems(e){const o=[],t=[];for(const n of e||[])if(!(!n||!n.item)){if(n.parent){o.push(n);continue}t.push(n.item)}return{toolsFolder:t.length?{text:TOOLS_FOLDER_TEXT,icon:"tool",items:t}:null,managePluginsItem:{...MANAGE_PLUGINS_ITEM},parented:o}}export function insertFoldersBeforeSystem(e,o){const t=(o||[]).filter(Boolean);if(!t.length)return e;const s=["system","documentation"],n=e.findIndex(r=>s.includes(String(r&&r.text||"").toLowerCase()));return n===-1?[...e,...t]:[...e.slice(0,n),...t,...e.slice(n)]}export function pruneEmptySynthesisedFolders(e){const o=new Set([TOOLS_FOLDER_TEXT]);return(e||[]).filter(t=>!t||!o.has(t.text)?!0:Array.isArray(t.items)&&t.items.length>0)}
@@ -1 +1 @@
1
- import{test as l}from"node:test";import t from"node:assert/strict";import{groupPluginItems as u,stripItemByUrl as a,insertFoldersBeforeSystem as i,pruneEmptySynthesisedFolders as p,TOOLS_FOLDER_TEXT as m,PLUGINS_FOLDER_TEXT as g}from"./sidebar-grouping.js";const n=(s,e=null)=>({parent:e,item:s});l("groupPluginItems routes core items to Tools, optional to Plugins",()=>{const{toolsFolder:s,pluginsFolder:e}=u([n({text:"Analytics",url:"#/plugins/analytics",core:!0}),n({text:"Todo",url:"#/plugins/todo",core:!1}),n({text:"Notes",url:"#/plugins/notes"})]);t.equal(s.text,m),t.deepEqual(s.items.map(o=>o.text),["Analytics"]),t.equal(e.text,g),t.deepEqual(e.items.map(o=>o.text),["Manage Plugins","Todo","Notes"]),t.equal(e.items[0].url,"#/plugins"),t.equal(e.items[0].permission,"plugins")}),l("groupPluginItems omits Tools folder when no core plugins",()=>{const{toolsFolder:s,pluginsFolder:e}=u([n({text:"Todo",url:"#/plugins/todo",core:!1})]);t.equal(s,null),t.deepEqual(e.items.map(o=>o.text),["Manage Plugins","Todo"])}),l("groupPluginItems still returns Plugins folder (with Manage Plugins) when no plugins at all",()=>{const{toolsFolder:s,pluginsFolder:e}=u([]);t.equal(s,null),t.deepEqual(e.items.map(o=>o.text),["Manage Plugins"])}),l("groupPluginItems keeps explicitly-parented items separate",()=>{const{parented:s,pluginsFolder:e}=u([n({text:"Nested",url:"#/x"},"Content"),n({text:"Todo",url:"#/plugins/todo",core:!1})]);t.equal(s.length,1),t.equal(s[0].parent,"Content"),t.deepEqual(e.items.map(o=>o.text),["Manage Plugins","Todo"])}),l("stripItemByUrl removes the management link at any depth",()=>{const s=[{text:"Overview",items:[{text:"Dashboard",url:"#/"}]},{text:"System",items:[{text:"Users",url:"#/users"},{text:"Plugins",url:"#/plugins"}]}],o=a(s,"#/plugins").find(r=>r.text==="System");t.deepEqual(o.items.map(r=>r.text),["Users"]),t.equal(s.find(r=>r.text==="System").items.length,2)}),l("insertFoldersBeforeSystem places folders just before System",()=>{const e=i([{text:"Overview"},{text:"Data"},{text:"System"},{text:"Documentation"}],[{text:"Tools"},{text:"Plugins"},null]);t.deepEqual(e.map(o=>o.text),["Overview","Data","Tools","Plugins","System","Documentation"])}),l("insertFoldersBeforeSystem appends when no System/Documentation anchor",()=>{const e=i([{text:"Overview"},{text:"Data"}],[{text:"Plugins"}]);t.deepEqual(e.map(o=>o.text),["Overview","Data","Plugins"])}),l("pruneEmptySynthesisedFolders drops an empty Plugins folder but keeps built-ins",()=>{const e=p([{text:"Overview",items:[]},{text:"Tools",items:[{text:"Analytics"}]},{text:"Plugins",items:[]}]);t.deepEqual(e.map(o=>o.text),["Overview","Tools"])});
1
+ import{test as n}from"node:test";import s from"node:assert/strict";import{groupPluginItems as l,stripItemByUrl as m,insertFoldersBeforeSystem as u,pruneEmptySynthesisedFolders as i,TOOLS_FOLDER_TEXT as g,MANAGE_PLUGINS_URL as p}from"./sidebar-grouping.js";const a=(e,t=null)=>({parent:t,item:e});n("groupPluginItems routes every enabled item to Tools, whatever its core flag",()=>{const{toolsFolder:e}=l([a({text:"Analytics",url:"#/plugins/analytics",core:!0}),a({text:"Todo",url:"#/plugins/todo",core:!1}),a({text:"Notes",url:"#/plugins/notes"})]);s.equal(e.text,g),s.deepEqual(e.items.map(t=>t.text),["Analytics","Todo","Notes"])}),n("Manage Plugins is a standalone entry, not a folder",()=>{const{managePluginsItem:e}=l([a({text:"Todo",url:"#/plugins/todo"})]);s.equal(e.text,"Manage Plugins"),s.equal(e.url,p),s.equal(e.permission,"plugins"),s.ok(!("items"in e),"it must not carry a sub-tree")}),n("groupPluginItems omits the Tools folder only when nothing is enabled",()=>{const e=l([a({text:"Todo",url:"#/plugins/todo"})]);s.deepEqual(e.toolsFolder.items.map(o=>o.text),["Todo"]);const t=l([]);s.equal(t.toolsFolder,null),s.equal(t.managePluginsItem.text,"Manage Plugins")}),n("groupPluginItems keeps explicitly-parented items separate",()=>{const{parented:e,toolsFolder:t}=l([a({text:"Nested",url:"#/x"},"Content"),a({text:"Todo",url:"#/plugins/todo",core:!1})]);s.equal(e.length,1),s.equal(e[0].parent,"Content"),s.deepEqual(t.items.map(o=>o.text),["Todo"])}),n("each call returns its own Manage Plugins object",()=>{const e=l([]).managePluginsItem,t=l([]).managePluginsItem;s.notEqual(e,t),s.deepEqual(e,t)}),n("stripItemByUrl removes the management link at any depth",()=>{const e=[{text:"Overview",items:[{text:"Dashboard",url:"#/"}]},{text:"System",items:[{text:"Users",url:"#/users"},{text:"Plugins",url:"#/plugins"}]}],o=m(e,"#/plugins").find(r=>r.text==="System");s.deepEqual(o.items.map(r=>r.text),["Users"]),s.equal(e.find(r=>r.text==="System").items.length,2)}),n("insertFoldersBeforeSystem places Tools then Manage Plugins before System",()=>{const t=u([{text:"Overview"},{text:"Data"},{text:"System"},{text:"Documentation"}],[{text:"Tools",items:[]},{text:"Manage Plugins",url:"#/plugins"},null]);s.deepEqual(t.map(o=>o.text),["Overview","Data","Tools","Manage Plugins","System","Documentation"])}),n("insertFoldersBeforeSystem appends when no System/Documentation anchor",()=>{const t=u([{text:"Overview"},{text:"Data"}],[{text:"Manage Plugins",url:"#/plugins"}]);s.deepEqual(t.map(o=>o.text),["Overview","Data","Manage Plugins"])}),n("pruneEmptySynthesisedFolders drops an empty Tools folder but keeps built-ins",()=>{const e=[{text:"Overview",items:[]},{text:"Tools",items:[]}];s.deepEqual(i(e).map(t=>t.text),["Overview"])}),n("pruneEmptySynthesisedFolders leaves the standalone Manage Plugins alone",()=>{const e=[{text:"Tools",items:[{text:"Analytics"}]},{text:"Manage Plugins",url:"#/plugins"}];s.deepEqual(i(e).map(t=>t.text),["Tools","Manage Plugins"])});
@@ -1,4 +1,4 @@
1
- import{api as u}from"../api.js";import{colourToCss as I,applyFloat as K,applyHighlight as G,itemHighlightAttrs as O}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{groupPluginItems as V,stripItemByUrl as Y,insertFoldersBeforeSystem as J,pruneEmptySynthesisedFolders as Q,MANAGE_PLUGINS_URL as Z}from"./sidebar-grouping.js";function f(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const ee=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Menus",url:"#/menus",icon:"menu"}];function N(e,a){return a?!e||!e.length?!1:e.includes(a)?!0:["read","create","update","delete"].some(t=>e.includes(`${a}.${t}`)):!0}function T(e,a){const t=[];for(const n of e){if(n.hidden||n.permission&&!N(a,n.permission))continue;const i=Array.isArray(n.items)&&n.items.length?T(n.items,a):[];t.push({...n,items:i})}return t}function te(e,a){if(!a||!a.length)return e;const t=(n,i)=>{for(const m of n){if((m.text||"").toLowerCase()===i.toLowerCase())return m;if(Array.isArray(m.items)){const r=t(m.items,i);if(r)return r}}return null};for(const n of a)if(n.parent){const i=t(e,n.parent);i?(i.items=Array.isArray(i.items)?i.items:[],i.items.push(n.item)):e.push(n.item)}else e.push(n.item);return e}async function ne(e){if(!N(e,"projects"))return[];let a=[];try{a=await u.projects.list()}catch{return[]}const t={};await Promise.all(a.map(async i=>{try{const m=await u.projects.artefacts(i.slug);t[i.slug]=m}catch{t[i.slug]={}}}));const n=[{key:"pages",text:"Pages",icon:"file-text",path:"/pages"},{key:"collections",text:"Collections",icon:"database",path:"/collections"},{key:"forms",text:"Forms",icon:"layout",path:"/forms"},{key:"actions",text:"Actions",icon:"zap",path:"/actions"},{key:"menus",text:"Menus",icon:"menu",path:"/menus"},{key:"blocks",text:"Blocks",icon:"box",path:"/blocks"},{key:"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 a.map(i=>{const r="#/projects/"+encodeURIComponent(i.slug),l=t[i.slug]||{},p=[{text:"Overview",url:r,icon:i.icon||"folder"}];for(const h of n){const g=l[h.key],c=Array.isArray(g)?g.length:0;c<=0||p.push({text:`${h.text} (${c})`,url:r+h.path,icon:h.icon})}p.push({text:"Settings",url:r+"/settings",icon:"settings"});const b=i.enabled===!1;return{text:i.name||i.slug,icon:i.icon||"folder",items:p,...b&&{colour:"neutral",badge:{text:"off"}}}})}function se(e,a){for(const t of e)if((t.text||"").toLowerCase()==="projects"){t.items=Array.isArray(t.items)?t.items.slice():[],t.items.push(...a);return}}function ie(e,a){if(!e.url||!a)return"";const t=a[e.url];return t==null||t<=0?"":`<span class="sidebar-badge">${f(String(t))}</span>`}function ae(e){const a=(e.text||"").replace(/\s*\(\d+\)\s*$/,"").toLowerCase().replace(/\s+/g,"-"),t=(e.url||"").match(/^#\/projects\/([^/]+)/);return`sidebar.expanded.${t?`project-${decodeURIComponent(t[1])}.`:""}${a}`}function U(e,a){if(e&&e.type==="separator")return'<hr class="sidebar-divider">';if(e&&e.type==="spacer"){const c=["xs","sm","md","lg","xl","flex"].includes(e.size)?e.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${f(c)}" aria-hidden="true"></div>`}const t=e.icon?`<span data-icon="${f(e.icon)}"></span>`:"",n=f(e.text||""),i=e.url?f(e.url):"",m=Array.isArray(e.items)&&e.items.length>0,r=e.colour?I(e.colour):"",l=O(e),p=[l.hover&&` data-menu-item-hover="${f(l.hover)}"`,l.active&&` data-menu-item-active="${f(l.active)}"`].filter(Boolean).join(""),b=[r&&`color:${f(r)}`,l.accent&&`--dm-menu-highlight:${f(l.accent)}`].filter(Boolean).join(";"),h=b?` style="${b}"`:"";let g="";if(e.badge&&e.badge.text!=null&&e.badge.text!==""){const c=e.badge.variant?I(e.badge.variant):"";g=`<span class="dm-menu-badge"${c?` style="background:${f(c)};color:var(--dm-text-inverse,#fff)"`:""}>${f(String(e.badge.text))}</span>`}if(m){const c=ae(e),y=S.get(c)!==!1,j=e.items.map(E=>U(E,a)).join("");return`<details data-state-key="${f(c)}"${y?" open":""}>
2
- <summary${h}>${t} <span class="sidebar-text">${n}</span>${g}</summary>
3
- <div class="sidebar-children">${j}</div>
4
- </details>`}return`<a href="${i}" class="sidebar-link" data-url="${i}"${h}${p}>${t} <span class="sidebar-text">${n}</span>${ie(e,a)}${g}</a>`}async function re(){const e=c=>c.then(y=>Array.isArray(y)?y.length:Array.isArray(y?.entries)?y.entries.length:0).catch(()=>0),[a,t,n,i,m,r,l,p,b,h,g]=await Promise.all([e(u.pages.list()),e(u.media.list()),e(u.collections.list()),e(u.forms.list()),e(u.views.list()),e(u.actions.list()),e(u.blocks.list()),e(u.components.list()),e(u.users.list()),e(u.plugins.list()),u.system?.notifications?.unreadCount?.().then(c=>c?.count??0).catch(()=>0)??Promise.resolve(0)]);return{"#/pages":a,"#/media":t,"#/collections":n,"#/forms":i,"#/views":m,"#/actions":r,"#/blocks":l,"#/components":p,"#/users":b,"#/plugins":h,"#/system/notifications":g}}async function oe(){try{const e=await u.settings.get();return e?.adminBrand?.title||e?.title||"Admin"}catch{return"Admin"}}export async function renderAdminSidebar({mount:e,permissions:a}){const t=$(e).get(0);if(!t)return;let n,i=null,m=null,r=null,l=null,p=null;try{const s=await u.menus.get("admin-sidebar");n=Array.isArray(s?.items)?s.items:null,i=s?.variant||null,m=s?.position||null,r=s?.style||null,l=s?.float||null,p=s?.highlight||null}catch{n=null}(!n||!n.length)&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),n=ee.slice());let b=[];try{b=await H.get("/api/sidebar/registered-items")||[]}catch{}n=Y(n,Z);const{toolsFolder:h,pluginsFolder:g,parented:c}=V(b);n=te(n,c),n=J(n,[h,g]);const y=await ne(a);y.length&&se(n,y),n=T(n,a),n=Q(n);const[j,E]=await Promise.all([re().catch(()=>({})),oe()]),q=i?` dm-admin-sidebar--${f(i)}`:"";function L(s,d="px"){if(s==null)return s;const o=String(s).trim();return/^\d+(\.\d+)?$/.test(o)?`${o}${d}`:o}let P="";if(r){const s=[];r.fontFamily&&s.push(`font-family: ${r.fontFamily}, sans-serif`),r.fontSize&&s.push(`font-size: ${L(r.fontSize)}`),r.fontWeight&&s.push(`font-weight: ${r.fontWeight}`),r.letterSpacing&&s.push(`letter-spacing: ${L(r.letterSpacing,"em")}`);const d=[];if(s.length&&d.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 { ${s.join("; ")} }`),r.iconSize){const o=L(r.iconSize);d.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${o} !important; height: ${o} !important; }`)}d.length&&(P=`<style data-admin-sidebar-style>${d.join(" ")}</style>`)}const X=`<div class="dm-admin-sidebar-header"><span data-icon="layout"></span> ${f(E)}</div>`,W=n.some(s=>s&&s.type==="spacer"&&s.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",D=`${P}<nav class="dm-admin-sidebar${q}${W}">${X}${n.map(s=>U(s,j)).join("")}</nav>`,F=document.createRange();F.selectNodeContents(t);const _=F.createContextualFragment(D);t.replaceChildren(_),Domma.icons.scan(t);const v=Number.parseInt(S.get("sidebar.width"),10);Number.isFinite(v)&&v>=180&&v<=480&&(t.style.width=v+"px"),t.removeAttribute("data-menu-hover"),t.removeAttribute("data-menu-active"),t.style.removeProperty("--dm-menu-highlight"),G(t,{highlight:p});const x=document.querySelector(".dashboard-wrapper");if(["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(s=>{t.style[s]=""}),t.classList.remove("dm-menu-floating"),x&&(x.style.paddingLeft="",x.style.paddingRight=""),m==="floating"){K(t,{position:"floating",float:l||{}});const d=l&&l.anchor||"TL",o=Number.isFinite(Number(l?.offsetX))?Number(l.offsetX):16,A=t.getBoundingClientRect().width||v||260;if(x){const C=Math.round(A+o*2)+"px";d==="R"||d==="TR"||d==="BR"?x.style.paddingRight=C:x.style.paddingLeft=C}}const w=document.createElement("div");w.className="dm-admin-sidebar-handle",w.setAttribute("aria-label","Resize sidebar"),t.appendChild(w),t.querySelectorAll("details").forEach(s=>{s.addEventListener("toggle",function(){const d=this.getAttribute("data-state-key");d&&S.set(d,this.open);const o=this.querySelector(":scope > .sidebar-children");if(o)if(this.open){const A=o.scrollHeight;o.style.maxHeight="0",requestAnimationFrame(()=>{o.style.maxHeight=A+"px",o.addEventListener("transitionend",function C(){o.style.maxHeight="none",o.removeEventListener("transitionend",C)})})}else{const A=o.scrollHeight;o.style.maxHeight=A+"px",requestAnimationFrame(()=>{o.style.maxHeight="0"})}})});let k=!1,z=0,B=0;w.addEventListener("mousedown",s=>{k=!0,z=s.clientX,B=t.getBoundingClientRect().width,document.body.style.cursor="col-resize",document.body.style.userSelect="none",s.preventDefault()}),document.addEventListener("mousemove",s=>{if(!k)return;const d=Math.max(180,Math.min(480,B+(s.clientX-z)));t.style.width=d+"px"}),document.addEventListener("mouseup",()=>{k&&(k=!1,document.body.style.cursor="",document.body.style.userSelect="",S.set("sidebar.width",parseInt(t.style.width,10)))});function R(){const s=location.hash||"#/";$(t).find(".sidebar-link").removeClass("active"),$(t).find(`.sidebar-link[data-url="${s}"]`).addClass("active")}R(),M.subscribe("router:afterChange",R)}
1
+ import{api as c}from"../api.js";import{colourToCss as U,applyFloat as K,applyHighlight as O,itemHighlightAttrs as G}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{groupPluginItems as V,stripItemByUrl as Y,insertFoldersBeforeSystem as J,pruneEmptySynthesisedFolders as Q,MANAGE_PLUGINS_URL as Z}from"./sidebar-grouping.js";function h(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const ee=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Menus",url:"#/menus",icon:"menu"}];function T(e,s){return s?!e||!e.length?!1:e.includes(s)?!0:["read","create","update","delete"].some(t=>e.includes(`${s}.${t}`)):!0}function q(e,s){const t=[];for(const n of e){if(n.hidden||n.permission&&!T(s,n.permission))continue;const a=Array.isArray(n.items)&&n.items.length?q(n.items,s):[];t.push({...n,items:a})}return t}function te(e,s){if(!s||!s.length)return e;const t=(n,a)=>{for(const m of n){if((m.text||"").toLowerCase()===a.toLowerCase())return m;if(Array.isArray(m.items)){const r=t(m.items,a);if(r)return r}}return null};for(const n of s)if(n.parent){const a=t(e,n.parent);a?(a.items=Array.isArray(a.items)?a.items:[],a.items.push(n.item)):e.push(n.item)}else e.push(n.item);return e}async function ne(e){if(!T(e,"projects"))return[];let s=[];try{s=await c.projects.list()}catch{return[]}const t={};await Promise.all(s.map(async a=>{try{const m=await c.projects.artefacts(a.slug);t[a.slug]=m}catch{t[a.slug]={}}}));const n=[{key:"pages",text:"Pages",icon:"file-text",path:"/pages"},{key:"collections",text:"Collections",icon:"database",path:"/collections"},{key:"forms",text:"Forms",icon:"layout",path:"/forms"},{key:"actions",text:"Actions",icon:"zap",path:"/actions"},{key:"menus",text:"Menus",icon:"menu",path:"/menus"},{key:"blocks",text:"Blocks",icon:"box",path:"/blocks"},{key:"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(a=>{const r="#/projects/"+encodeURIComponent(a.slug),d=t[a.slug]||{},g=[{text:"Overview",url:r,icon:a.icon||"folder"}];for(const p of n){const b=d[p.key],f=Array.isArray(b)?b.length:0;f<=0||g.push({text:`${p.text} (${f})`,url:r+p.path,icon:p.icon})}g.push({text:"Settings",url:r+"/settings",icon:"settings"});const y=a.enabled===!1;return{text:a.name||a.slug,icon:a.icon||"folder",items:g,...y&&{colour:"neutral",badge:{text:"off"}}}})}function se(e,s){for(const t of e)if((t.text||"").toLowerCase()==="projects"){t.items=Array.isArray(t.items)?t.items.slice():[],t.items.push(...s);return}}function ie(e,s){if(!e.url||!s)return"";const t=s[e.url];return t==null||t<=0?"":`<span class="sidebar-badge">${h(String(t))}</span>`}function ae(e){const s=(e.text||"").replace(/\s*\(\d+\)\s*$/,"").toLowerCase().replace(/\s+/g,"-"),t=(e.url||"").match(/^#\/projects\/([^/]+)/);return`sidebar.expanded.${t?`project-${decodeURIComponent(t[1])}.`:""}${s}`}function X(e,s){if(e&&e.type==="separator")return'<hr class="sidebar-divider">';if(e&&e.type==="spacer"){const f=["xs","sm","md","lg","xl","flex"].includes(e.size)?e.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${h(f)}" aria-hidden="true"></div>`}const t=e.icon?`<span data-icon="${h(e.icon)}"></span>`:"",n=h(e.text||""),a=e.url?h(e.url):"",m=Array.isArray(e.items)&&e.items.length>0,r=e.colour?U(e.colour):"",d=G(e),g=[d.hover&&` data-menu-item-hover="${h(d.hover)}"`,d.active&&` data-menu-item-active="${h(d.active)}"`].filter(Boolean).join(""),y=[r&&`color:${h(r)}`,d.accent&&`--dm-menu-highlight:${h(d.accent)}`].filter(Boolean).join(";"),p=y?` style="${y}"`:"";let b="";if(e.badge&&e.badge.text!=null&&e.badge.text!==""){const f=e.badge.variant?U(e.badge.variant):"";b=`<span class="dm-menu-badge"${f?` style="background:${h(f)};color:var(--dm-text-inverse,#fff)"`:""}>${h(String(e.badge.text))}</span>`}if(m){const f=ae(e),v=S.get(f)!==!1,w=e.items.map(k=>X(k,s)).join("");return`<details data-state-key="${h(f)}"${v?" open":""}>
2
+ <summary${p}>${t} <span class="sidebar-text">${n}</span>${b}</summary>
3
+ <div class="sidebar-children">${w}</div>
4
+ </details>`}return`<a href="${a}" class="sidebar-link" data-url="${a}"${p}${g}>${t} <span class="sidebar-text">${n}</span>${ie(e,s)}${b}</a>`}async function re(e=[]){const s=o=>o.then(x=>Array.isArray(x)?x.length:Array.isArray(x?.entries)?x.entries.length:0).catch(()=>0),[t,n,a,m,r,d,g,y,p,b,f,v,w,k]=await Promise.all([s(c.pages.list()),s(c.media.list()),s(c.menus.list()),s(c.contextMenus.list()),c.theme.get().then(o=>(o?.overrides||[]).length).catch(()=>0),s(c.collections.list()),s(c.forms.list()),s(c.views.list()),s(c.actions.list()),s(c.blocks.list()),s(c.components.list()),s(c.users.list()),s(c.plugins.list()),c.system?.notifications?.unreadCount?.().then(o=>o?.count??0).catch(()=>0)??Promise.resolve(0)]),E={};return await Promise.all((e??[]).map(o=>o?.item??o).filter(o=>o?.url&&typeof o.countUrl=="string"&&o.countUrl).map(async o=>{try{const x=await H.get(o.countUrl),C=Number(x?.count);Number.isFinite(C)&&C>0&&(E[o.url]=C)}catch{}})),{...E,"#/pages":t,"#/media":n,"#/menus":a,"#/context-menus":m,"#/theme":r,"#/collections":d,"#/forms":g,"#/views":y,"#/actions":p,"#/blocks":b,"#/components":f,"#/users":v,"#/plugins":w,"#/system/notifications":k}}async function oe(){try{const e=await c.settings.get();return e?.adminBrand?.title||e?.title||"Admin"}catch{return"Admin"}}export async function renderAdminSidebar({mount:e,permissions:s}){const t=$(e).get(0);if(!t)return;let n,a=null,m=null,r=null,d=null,g=null;try{const i=await c.menus.get("admin-sidebar");n=Array.isArray(i?.items)?i.items:null,a=i?.variant||null,m=i?.position||null,r=i?.style||null,d=i?.float||null,g=i?.highlight||null}catch{n=null}(!n||!n.length)&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),n=ee.slice());let y=[];try{y=await H.get("/api/sidebar/registered-items")||[]}catch{}n=Y(n,Z);const{toolsFolder:p,managePluginsItem:b,parented:f}=V(y);n=te(n,f),n=J(n,[p,b]);const v=await ne(s);v.length&&se(n,v),n=q(n,s),n=Q(n);const[w,k]=await Promise.all([re(y).catch(()=>({})),oe()]),E=a?` dm-admin-sidebar--${h(a)}`:"";function o(i,u="px"){if(i==null)return i;const l=String(i).trim();return/^\d+(\.\d+)?$/.test(l)?`${l}${u}`:l}let x="";if(r){const i=[];r.fontFamily&&i.push(`font-family: ${r.fontFamily}, sans-serif`),r.fontSize&&i.push(`font-size: ${o(r.fontSize)}`),r.fontWeight&&i.push(`font-weight: ${r.fontWeight}`),r.letterSpacing&&i.push(`letter-spacing: ${o(r.letterSpacing,"em")}`);const u=[];if(i.length&&u.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 { ${i.join("; ")} }`),r.iconSize){const l=o(r.iconSize);u.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${l} !important; height: ${l} !important; }`)}u.length&&(x=`<style data-admin-sidebar-style>${u.join(" ")}</style>`)}const C=`<div class="dm-admin-sidebar-header"><span data-icon="layout"></span> ${h(k)}</div>`,W=n.some(i=>i&&i.type==="spacer"&&i.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",D=`${x}<nav class="dm-admin-sidebar${E}${W}">${C}${n.map(i=>X(i,w)).join("")}</nav>`,B=document.createRange();B.selectNodeContents(t);const _=B.createContextualFragment(D);t.replaceChildren(_),Domma.icons.scan(t);const j=Number.parseInt(S.get("sidebar.width"),10);Number.isFinite(j)&&j>=180&&j<=480&&(t.style.width=j+"px"),t.removeAttribute("data-menu-hover"),t.removeAttribute("data-menu-active"),t.style.removeProperty("--dm-menu-highlight"),O(t,{highlight:g});const A=document.querySelector(".dashboard-wrapper");if(["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(i=>{t.style[i]=""}),t.classList.remove("dm-menu-floating"),A&&(A.style.paddingLeft="",A.style.paddingRight=""),m==="floating"){K(t,{position:"floating",float:d||{}});const u=d&&d.anchor||"TL",l=Number.isFinite(Number(d?.offsetX))?Number(d.offsetX):16,P=t.getBoundingClientRect().width||j||260;if(A){const z=Math.round(P+l*2)+"px";u==="R"||u==="TR"||u==="BR"?A.style.paddingRight=z:A.style.paddingLeft=z}}const L=document.createElement("div");L.className="dm-admin-sidebar-handle",L.setAttribute("aria-label","Resize sidebar"),t.appendChild(L),t.querySelectorAll("details").forEach(i=>{i.addEventListener("toggle",function(){const u=this.getAttribute("data-state-key");u&&S.set(u,this.open);const l=this.querySelector(":scope > .sidebar-children");if(l)if(this.open){const P=l.scrollHeight;l.style.maxHeight="0",requestAnimationFrame(()=>{l.style.maxHeight=P+"px",l.addEventListener("transitionend",function z(){l.style.maxHeight="none",l.removeEventListener("transitionend",z)})})}else{const P=l.scrollHeight;l.style.maxHeight=P+"px",requestAnimationFrame(()=>{l.style.maxHeight="0"})}})});let F=!1,R=0,I=0;L.addEventListener("mousedown",i=>{F=!0,R=i.clientX,I=t.getBoundingClientRect().width,document.body.style.cursor="col-resize",document.body.style.userSelect="none",i.preventDefault()}),document.addEventListener("mousemove",i=>{if(!F)return;const u=Math.max(180,Math.min(480,I+(i.clientX-R)));t.style.width=u+"px"}),document.addEventListener("mouseup",()=>{F&&(F=!1,document.body.style.cursor="",document.body.style.userSelect="",S.set("sidebar.width",parseInt(t.style.width,10)))});function N(){const i=location.hash||"#/";$(t).find(".sidebar-link").removeClass("active"),$(t).find(`.sidebar-link[data-url="${i}"]`).addClass("active")}N(),M.subscribe("router:afterChange",N)}
@@ -0,0 +1 @@
1
+ const I=360,g="dm-slideover-resizer",L="dm.slideover.width.",f=new Set;function _(e){return String(e||"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"default"}function a(e){return Math.min(Math.max(360,Math.round(e)),Math.max(360,window.innerWidth-80))}function z(e){try{const n=Number(S.get(e));return Number.isFinite(n)&&n>=360?n:null}catch{return null}}function W(){for(const e of f){if(!e.isConnected){f.delete(e);continue}e.style.setProperty("width",`${a(e.__dmResize.width)}px`,"important")}}window.addEventListener("resize",W);export function applyResizable(e,{storageKey:n,defaultWidth:c}={}){if(!e||e.nodeType!==1)return;const r=e.classList.contains("dm-slideover-right"),l=e.classList.contains("dm-slideover-left");if(!r&&!l)return;const s=e.__dmResize,t=s??{width:640,storageKey:"",defaultWidth:640};n&&(t.storageKey=n),c&&(t.defaultWidth=c);const v=t.storageKey?z(t.storageKey):null;t.width=a(v??t.defaultWidth);const o=()=>{e.style.setProperty("width",`${t.width}px`,"important"),e.style.setProperty("min-width","360px","important")};if(t.apply=o,e.__dmResize=t,o(),s)return;const i=document.createElement("span");i.className=g,i.setAttribute("role","separator"),i.setAttribute("aria-orientation","vertical"),i.title="Drag to resize \xB7 double-click to reset",e.appendChild(i),f.add(e),i.addEventListener("pointerdown",u=>{u.preventDefault();const y=u.clientX,h=t.width;i.setPointerCapture?.(u.pointerId),e.classList.add("is-resizing");const m=w=>{const p=w.clientX-y;t.width=a(r?h-p:h+p),o()},d=()=>{i.removeEventListener("pointermove",m),i.removeEventListener("pointerup",d),i.removeEventListener("pointercancel",d),e.classList.remove("is-resizing");try{t.storageKey&&S.set(t.storageKey,t.width)}catch{}};i.addEventListener("pointermove",m),i.addEventListener("pointerup",d),i.addEventListener("pointercancel",d)}),i.addEventListener("dblclick",()=>{t.width=a(t.defaultWidth),o();try{t.storageKey&&S.set(t.storageKey,t.width)}catch{}})}export function installResizableSlideovers(){if(typeof E>"u"||typeof E.slideover!="function"||E.slideover.__dmResizable)return;const e=E.slideover,n=function(r={},...l){const s=e.call(this,r,...l);try{applyResizable(s?.element,{storageKey:`${L}${_(r.title)}`,defaultWidth:r.defaultWidth})}catch{}return s};n.__dmResizable=!0,E.slideover=n}
@@ -0,0 +1,212 @@
1
+ <div class="page-header">
2
+ <h2><span data-icon="menu"></span> <span id="ctx-title">New context menu</span></h2>
3
+ <div class="header-actions">
4
+ <a class="btn btn-secondary" href="#/context-menus"><span data-icon="arrow-left"></span> Back</a>
5
+ <button class="btn btn-primary" id="ctx-save"><span data-icon="check"></span> Save</button>
6
+ </div>
7
+ </div>
8
+
9
+ <div id="ctx-errors" class="alert alert-danger mb-4" style="display:none;"></div>
10
+
11
+ <p class="text-muted ctx-intro">
12
+ A context menu is not placed on a page the way a nav menu is. It attaches to a <strong>region</strong>
13
+ and opens when someone right-clicks inside it. So there are two questions to answer: which region, and
14
+ what the menu offers. Everything else has a sensible default.
15
+ </p>
16
+
17
+ <div class="row ctx-workspace">
18
+ <div class="col-8">
19
+ <div class="tabs" id="ctx-tabs">
20
+ <div class="tab-list">
21
+ <button class="tab-item active">Binding</button>
22
+ <button class="tab-item">Items</button>
23
+ <button class="tab-item">Appearance</button>
24
+ <button class="tab-item">Scope</button>
25
+ </div>
26
+
27
+ <!-- Binding ------------------------------------------------------- -->
28
+ <div class="tab-panel active">
29
+ <div class="row">
30
+ <div class="col-12">
31
+ <div class="card mb-4">
32
+ <div class="card-header"><h3 class="m-0">Where it binds</h3></div>
33
+ <div class="card-body">
34
+ <div class="form-group">
35
+ <label class="form-label" for="ctx-name">Name<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Your label for this menu in the admin list. Visitors never see it.">?</button></label>
36
+ <input class="form-input" id="ctx-name" type="text" placeholder="Entry actions">
37
+ </div>
38
+ <div class="form-group">
39
+ <label class="form-label" for="ctx-slug">Slug<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="The filename this menu is saved as. Fixed once created, because the file is the record.">?</button></label>
40
+ <input class="form-input" id="ctx-slug" type="text" placeholder="entry-actions">
41
+ <small class="text-muted">Lowercase letters, numbers and hyphens. Cannot be changed later.</small>
42
+ </div>
43
+ <div class="form-group">
44
+ <label class="form-label" for="ctx-selector">Container selector<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="A CSS selector for the region, e.g. #product-grid, .entry-card or main. Right-clicks anywhere inside it are offered to this menu. body, html, * and :root are refused, because they would swallow every other menu on the page.">?</button></label>
45
+ <input class="form-input" id="ctx-selector" type="text" placeholder="#product-grid">
46
+ <small class="text-muted">The region this menu covers. Right-clicks inside it are offered to this menu.</small>
47
+ </div>
48
+ <div class="form-group">
49
+ <label class="form-label" for="ctx-match">Match (optional)<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Narrows it: only claim the click when it landed on something matching this, e.g. [data-entry-id] to answer on rows but not the space between them. Blank means the whole container.">?</button></label>
50
+ <input class="form-input" id="ctx-match" type="text" placeholder="[data-entry-id]">
51
+ <small class="text-muted">Only claim right-clicks on descendants matching this. Leave empty to cover the whole container.</small>
52
+ </div>
53
+ <div class="form-group">
54
+ <label class="form-label" for="ctx-exclude">Exclude (optional)<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Holes in the region. A right-click on one of these falls through to whatever encloses it, which is useful for links and inputs where the browser's own menu is more use.">?</button></label>
55
+ <input class="form-input" id="ctx-exclude" type="text" placeholder="a, button, input">
56
+ <small class="text-muted">Regions that decline, so the click falls through to whatever encloses them.</small>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="col-12">
63
+ <div class="card mb-4">
64
+ <div class="card-header"><h3 class="m-0">Behaviour<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Menus can sit inside one another. A right-click picks the INNERMOST menu covering the spot clicked, never the one that happened to load first.">?</button></h3></div>
65
+ <div class="card-body">
66
+ <div class="form-group">
67
+ <label class="form-label" for="ctx-inherit">When nested inside another menu<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Append puts the outer menu's items after yours, Prepend before, and Stand alone shows only yours.">?</button></label>
68
+ <select class="form-select" id="ctx-inherit">
69
+ <option value="append">Show my items, then theirs (default)</option>
70
+ <option value="prepend">Show their items, then mine</option>
71
+ <option value="false">Show only my items</option>
72
+ </select>
73
+ </div>
74
+ <div class="form-group">
75
+ <label class="form-check">
76
+ <input type="checkbox" id="ctx-enabled" checked> Enabled
77
+ </label>
78
+ <small class="text-muted">Turn this off to park a menu without deleting it. A disabled menu is never sent to the browser.</small>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </div>
85
+
86
+ <!-- Items --------------------------------------------------------- -->
87
+ <div class="tab-panel">
88
+ <div class="card">
89
+ <div class="card-header">
90
+ <h3 class="m-0">Items<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="What the menu offers when it opens, top to bottom. An Item does something when clicked, a Divider draws a line, a Heading labels a group. A menu whose items are all dividers and headings is refused on save, because there would be nothing to click.">?</button></h3>
91
+ <button class="btn btn-sm btn-secondary" id="ctx-add-item"><span data-icon="plus"></span> Add item</button>
92
+ </div>
93
+ <div class="card-body">
94
+ <div id="ctx-items"></div>
95
+ <p class="text-muted mb-0" id="ctx-items-empty">No items yet. Every menu needs at least one.</p>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- Appearance ---------------------------------------------------- -->
101
+ <div class="tab-panel">
102
+ <div class="card">
103
+ <div class="card-header"><h3 class="m-0">Appearance<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="How the panel looks when it opens. Leave anything blank to inherit the site theme, which is usually what you want so the menu matches everything around it.">?</button></h3></div>
104
+ <div class="card-body">
105
+ <div class="row">
106
+ <div class="col-6">
107
+ <div class="form-group">
108
+ <label class="form-label" for="ctx-accent">Accent<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="The colour used for hover and the active item.">?</button></label>
109
+ <select class="form-select" id="ctx-accent">
110
+ <option value="">Theme default</option>
111
+ <option value="primary">Primary</option>
112
+ <option value="success">Success</option>
113
+ <option value="danger">Danger</option>
114
+ <option value="warning">Warning</option>
115
+ <option value="info">Info</option>
116
+ </select>
117
+ <small class="text-muted">Tints the border and hover wash. Labels keep the theme's text colour so they stay readable.</small>
118
+ </div>
119
+ <div class="form-group">
120
+ <label class="form-label" for="ctx-transition">Transition</label>
121
+ <select class="form-select" id="ctx-transition">
122
+ <option value="">Default (scale)</option>
123
+ <option value="fade">Fade</option>
124
+ <option value="slide">Slide</option>
125
+ <option value="none">None</option>
126
+ </select>
127
+ </div>
128
+ <div class="form-group">
129
+ <label class="form-label" for="ctx-density">Density</label>
130
+ <select class="form-select" id="ctx-density">
131
+ <option value="">Comfortable</option>
132
+ <option value="compact">Compact</option>
133
+ </select>
134
+ </div>
135
+ </div>
136
+ <div class="col-6">
137
+ <div class="form-group">
138
+ <label class="form-label" for="ctx-radius">Corner radius</label>
139
+ <select class="form-select" id="ctx-radius">
140
+ <option value="">Default</option>
141
+ <option value="none">Square</option>
142
+ <option value="sm">Small</option>
143
+ <option value="md">Medium</option>
144
+ <option value="lg">Large</option>
145
+ <option value="xl">Extra large</option>
146
+ </select>
147
+ </div>
148
+ <div class="form-group">
149
+ <label class="form-label" for="ctx-shadow">Shadow</label>
150
+ <select class="form-select" id="ctx-shadow">
151
+ <option value="">Default</option>
152
+ <option value="none">None</option>
153
+ <option value="sm">Small</option>
154
+ <option value="md">Medium</option>
155
+ <option value="lg">Large</option>
156
+ <option value="xl">Extra large</option>
157
+ </select>
158
+ </div>
159
+ <div class="form-group">
160
+ <label class="form-label" for="ctx-opacity">Opacity<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Below 100 the panel lets the page show through. Anything under about 80 gets hard to read over busy content.">?</button></label>
161
+ <input class="form-input" id="ctx-opacity" type="number" min="20" max="100" placeholder="100">
162
+ <small class="text-muted">Below 100 gives a translucent panel with a blurred backdrop. Text stays fully opaque.</small>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+
170
+ <!-- Scope --------------------------------------------------------- -->
171
+ <div class="tab-panel">
172
+ <div class="card">
173
+ <div class="card-header"><h3 class="m-0">Scope<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Which pages carry this menu. Leave BOTH blank and it applies site-wide. A menu that does not apply to a page is not sent to that page at all.">?</button></h3></div>
174
+ <div class="card-body">
175
+ <div class="row">
176
+ <div class="col-6">
177
+ <div class="form-group">
178
+ <label class="form-label" for="ctx-pages">Pages<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Comma separated URL paths. Supports * as a wildcard, so /shop/* covers everything under /shop. Blank means every page.">?</button></label>
179
+ <input class="form-input" id="ctx-pages" type="text" placeholder="/shop/*, /about">
180
+ <small class="text-muted">Comma separated. Supports a trailing <code>/*</code>. Leave empty for site-wide.</small>
181
+ </div>
182
+ </div>
183
+ <div class="col-6">
184
+ <div class="form-group">
185
+ <label class="form-label" for="ctx-projects">Projects<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Comma separated project slugs. The menu then applies only to pages belonging to those projects.">?</button></label>
186
+ <input class="form-input" id="ctx-projects" type="text" placeholder="core, shop">
187
+ <small class="text-muted">Comma separated project slugs.</small>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ </div>
193
+ </div>
194
+ </div>
195
+
196
+ </div><!-- /col-8 -->
197
+
198
+ <!-- One preview, beside the tabs rather than inside them: four copies would
199
+ be four elements sharing #ctx-preview, and the menu binds by selector.
200
+ Sticky, so it stays put while a long Items tab scrolls past it. -->
201
+ <div class="col-4">
202
+ <div class="card ctx-preview-card">
203
+ <div class="card-header"><h3 class="m-0">Try it<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="The real menu, built from what you have entered, updating as you type. This is the component itself rather than a drawing of it.">?</button></h3></div>
204
+ <div class="card-body">
205
+ <p class="text-muted">Right-click the panel below to see this menu as configured.</p>
206
+ <div id="ctx-preview" class="p-4 text-center" style="border:1px dashed var(--dm-border); border-radius:var(--dm-radius-md);">
207
+ Right-click to preview
208
+ </div>
209
+ </div>
210
+ </div>
211
+ </div>
212
+ </div><!-- /ctx-workspace -->
@@ -0,0 +1,16 @@
1
+ <div class="page-header">
2
+ <h2><span data-icon="menu"></span> Context Menus</h2>
3
+ <div class="header-actions">
4
+ <a class="btn btn-primary" href="#/context-menus/new"><span data-icon="plus"></span> New context menu</a>
5
+ </div>
6
+ </div>
7
+ <p class="text-muted mb-4">
8
+ Right-click menus bound to a region of a page. Unlike navigation menus these have no slot -
9
+ they bind to a CSS selector and are resolved in the browser by whichever menu is nearest the click.
10
+ </p>
11
+
12
+ <div class="card">
13
+ <div class="card-body">
14
+ <div id="context-menus-table"></div>
15
+ </div>
16
+ </div>
@@ -68,7 +68,7 @@
68
68
  <span class="pcode-current" id="pcode-current">Select a file</span>
69
69
  <span style="flex:1"></span>
70
70
  <button id="pcode-rename" class="btn btn-sm btn-ghost" title="Rename file" disabled><span data-icon="edit-3"></span></button>
71
- <button id="pcode-delete" class="btn btn-sm btn-ghost" title="Delete file" disabled><span data-icon="trash-2"></span></button>
71
+ <button id="pcode-delete" class="btn btn-sm btn-ghost" title="Delete file" disabled><span data-icon="trash"></span></button>
72
72
  <button id="pcode-saveall" class="btn btn-sm btn-ghost" disabled>Save All</button>
73
73
  <button id="pcode-save" class="btn btn-sm btn-primary" disabled><span data-icon="save"></span> Save</button>
74
74
  </div>