domma-cms 0.44.0 → 0.45.1

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 (254) hide show
  1. package/CLAUDE.md +53 -53
  2. package/README.md +27 -27
  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/index.html +2 -2
  7. package/admin/js/app.js +1 -1
  8. package/admin/js/lib/card-builder.js +2 -2
  9. package/admin/js/lib/crud-tutorial.js +1 -1
  10. package/admin/js/lib/effect-defs.js +1 -1
  11. package/admin/js/lib/effects-builder.js +1 -1
  12. package/admin/js/lib/image-editor.js +1 -1
  13. package/admin/js/lib/markdown-toolbar.js +2 -2
  14. package/admin/js/lib/project-context.js +1 -1
  15. package/admin/js/lib/project-quick-create.js +1 -1
  16. package/admin/js/lib/scribe-composer.js +1 -1
  17. package/admin/js/lib/server-restart.js +1 -1
  18. package/admin/js/lib/shortcode-context-menu.js +2 -2
  19. package/admin/js/lib/simple-editor.js +1 -1
  20. package/admin/js/lib/themes.js +1 -1
  21. package/admin/js/lib/timeline-builder.js +1 -1
  22. package/admin/js/templates/action-editor.html +8 -8
  23. package/admin/js/templates/api-endpoint-editor.html +7 -7
  24. package/admin/js/templates/api-endpoints.html +1 -1
  25. package/admin/js/templates/api-reference.html +1 -1
  26. package/admin/js/templates/block-editor.html +6 -6
  27. package/admin/js/templates/collection-editor.html +1 -1
  28. package/admin/js/templates/component-editor.html +2 -2
  29. package/admin/js/templates/dashboard/cache.html +3 -3
  30. package/admin/js/templates/dashboard/journeys.html +3 -3
  31. package/admin/js/templates/dashboard/kpi-strip.html +3 -3
  32. package/admin/js/templates/dashboard/top-pages.html +1 -1
  33. package/admin/js/templates/dashboard/traffic-chart.html +1 -1
  34. package/admin/js/templates/docs/api-actions.html +1 -1
  35. package/admin/js/templates/docs/api-collections.html +4 -4
  36. package/admin/js/templates/docs/api-pages.html +2 -2
  37. package/admin/js/templates/docs/api-plugins.html +1 -1
  38. package/admin/js/templates/docs/api-settings.html +2 -2
  39. package/admin/js/templates/docs/api-users.html +1 -1
  40. package/admin/js/templates/docs/components-howto.html +4 -4
  41. package/admin/js/templates/docs/components-reference.html +21 -21
  42. package/admin/js/templates/docs/components-rules.html +14 -14
  43. package/admin/js/templates/docs/components-walkthrough.html +13 -13
  44. package/admin/js/templates/docs/tutorial-crud.html +49 -49
  45. package/admin/js/templates/docs/tutorial-forms.html +4 -4
  46. package/admin/js/templates/docs/tutorial-plugin.html +10 -10
  47. package/admin/js/templates/docs/usage-actions.html +6 -6
  48. package/admin/js/templates/docs/usage-cta-shortcode.html +9 -9
  49. package/admin/js/templates/docs/usage-dconfig.html +4 -4
  50. package/admin/js/templates/docs/usage-navigation.html +1 -1
  51. package/admin/js/templates/docs/usage-pages.html +1 -1
  52. package/admin/js/templates/docs/usage-shortcodes.html +24 -24
  53. package/admin/js/templates/docs/usage-users-roles.html +3 -3
  54. package/admin/js/templates/docs/usage-views.html +4 -4
  55. package/admin/js/templates/documentation.html +1 -1
  56. package/admin/js/templates/effects.html +752 -752
  57. package/admin/js/templates/form-editor.html +5 -5
  58. package/admin/js/templates/forms.html +17 -17
  59. package/admin/js/templates/login.html +5 -5
  60. package/admin/js/templates/menu-editor.html +10 -10
  61. package/admin/js/templates/my-profile.html +17 -17
  62. package/admin/js/templates/navigation.html +11 -11
  63. package/admin/js/templates/notifications.html +1 -1
  64. package/admin/js/templates/page-editor.html +4 -4
  65. package/admin/js/templates/pro-docs.html +14 -14
  66. package/admin/js/templates/role-editor.html +70 -70
  67. package/admin/js/templates/roles.html +10 -10
  68. package/admin/js/templates/settings.html +10 -10
  69. package/admin/js/templates/tutorials.html +3 -3
  70. package/admin/js/templates/view-editor.html +15 -15
  71. package/admin/js/views/action-editor.js +1 -1
  72. package/admin/js/views/actions-list.js +1 -1
  73. package/admin/js/views/api-endpoint-editor.js +2 -2
  74. package/admin/js/views/api-endpoints.js +2 -2
  75. package/admin/js/views/api-tokens.js +3 -3
  76. package/admin/js/views/blocks.js +2 -2
  77. package/admin/js/views/collection-editor.js +1 -1
  78. package/admin/js/views/collection-entries.js +1 -1
  79. package/admin/js/views/collections.js +1 -1
  80. package/admin/js/views/component-editor.js +1 -1
  81. package/admin/js/views/components.js +4 -4
  82. package/admin/js/views/dashboard/widgets/cache.js +1 -1
  83. package/admin/js/views/dashboard/widgets/journeys.js +1 -1
  84. package/admin/js/views/dashboard/widgets/kpi-strip.js +1 -1
  85. package/admin/js/views/form-editor.js +7 -7
  86. package/admin/js/views/form-submissions.js +1 -1
  87. package/admin/js/views/forms.js +1 -1
  88. package/admin/js/views/layouts.js +1 -1
  89. package/admin/js/views/login.js +2 -2
  90. package/admin/js/views/menu-editor.js +3 -3
  91. package/admin/js/views/menu-locations.js +1 -1
  92. package/admin/js/views/menus.js +3 -3
  93. package/admin/js/views/navigation.js +8 -8
  94. package/admin/js/views/page-editor.js +40 -40
  95. package/admin/js/views/pages.js +3 -3
  96. package/admin/js/views/plugin-code.js +2 -2
  97. package/admin/js/views/plugins.js +1 -1
  98. package/admin/js/views/project-settings.js +1 -1
  99. package/admin/js/views/projects.js +1 -1
  100. package/admin/js/views/roles.js +1 -1
  101. package/admin/js/views/user-editor.js +1 -1
  102. package/admin/js/views/users.js +3 -3
  103. package/admin/js/views/view-editor.js +1 -1
  104. package/admin/js/views/view-preview.js +1 -1
  105. package/admin/js/views/views-list.js +1 -1
  106. package/bin/cli.js +8 -8
  107. package/bin/lib/config-merge.js +44 -44
  108. package/bin/update.js +16 -16
  109. package/config/plugins.json +1 -1
  110. package/package.json +2 -2
  111. package/plugins/_template/admin/views/index.js +1 -1
  112. package/plugins/_template/config.js +1 -1
  113. package/plugins/_template/plugin.js +1 -1
  114. package/plugins/analytics/admin/templates/analytics.html +3 -3
  115. package/plugins/analytics/admin/views/analytics.js +1 -1
  116. package/plugins/analytics/config.js +1 -1
  117. package/plugins/analytics/plugin.js +4 -4
  118. package/plugins/analytics/plugin.json +1 -1
  119. package/plugins/analytics/public/inject-body.html +2 -2
  120. package/plugins/analytics/public/inject-head.html +1 -1
  121. package/plugins/blog/admin/templates/blog.html +4 -4
  122. package/plugins/blog/admin/views/blog.js +3 -3
  123. package/plugins/blog/admin/views/categories.js +2 -2
  124. package/plugins/blog/admin/views/comments.js +4 -4
  125. package/plugins/blog/admin/views/post-editor.js +1 -1
  126. package/plugins/blog/plugin.js +2 -2
  127. package/plugins/blog/plugin.json +4 -4
  128. package/plugins/contacts/admin/views/contacts.js +8 -8
  129. package/plugins/contacts/plugin.js +4 -4
  130. package/plugins/contacts/plugin.json +1 -1
  131. package/plugins/invoice/admin/templates/editor.html +6 -6
  132. package/plugins/invoice/admin/templates/index.html +4 -4
  133. package/plugins/invoice/admin/views/editor.js +3 -3
  134. package/plugins/invoice/admin/views/index.js +4 -4
  135. package/plugins/invoice/admin/views/issuers.js +1 -1
  136. package/plugins/invoice/admin/views/party-view.js +3 -3
  137. package/plugins/invoice/admin/views/receivers.js +1 -1
  138. package/plugins/invoice/config.js +1 -1
  139. package/plugins/invoice/plugin.js +1 -1
  140. package/plugins/invoice/plugin.json +4 -4
  141. package/plugins/notes/admin/views/notes.js +4 -4
  142. package/plugins/notes/plugin.js +1 -1
  143. package/plugins/notes/plugin.json +1 -1
  144. package/plugins/site-search/admin/templates/site-search.html +3 -3
  145. package/plugins/site-search/admin/views/site-search.js +1 -1
  146. package/plugins/site-search/config.js +1 -1
  147. package/plugins/site-search/plugin.js +4 -4
  148. package/plugins/site-search/plugin.json +1 -1
  149. package/plugins/surveys/admin/templates/results.html +1 -1
  150. package/plugins/surveys/admin/templates/survey-editor.html +4 -4
  151. package/plugins/surveys/admin/views/audience.js +6 -6
  152. package/plugins/surveys/admin/views/results.js +3 -3
  153. package/plugins/surveys/admin/views/survey-editor.js +1 -1
  154. package/plugins/surveys/admin/views/surveys.js +2 -2
  155. package/plugins/surveys/plugin.js +1 -1
  156. package/plugins/surveys/plugin.json +4 -4
  157. package/plugins/theme-switcher/admin/views/theme-switcher.js +1 -1
  158. package/plugins/theme-switcher/plugin.json +3 -3
  159. package/plugins/theme-switcher/public/inject-body.html +28 -28
  160. package/plugins/theme-switcher/public/inject-head.html +1 -1
  161. package/plugins/todo/plugin.js +1 -1
  162. package/public/css/forms.css +1 -1
  163. package/public/js/collection-browser.js +1 -1
  164. package/public/js/collection-context.js +1 -1
  165. package/public/js/form-logic-engine.js +1 -1
  166. package/public/js/forms.js +2 -1
  167. package/public/js/site.js +1 -1
  168. package/scripts/build.js +14 -14
  169. package/scripts/create-plugin.js +2 -2
  170. package/scripts/fresh.js +2 -2
  171. package/scripts/gen-instance-secret.js +1 -1
  172. package/scripts/pro.js +12 -12
  173. package/scripts/reset.js +2 -2
  174. package/scripts/setup.js +15 -15
  175. package/scripts/users.js +5 -5
  176. package/scripts/verify-assets.mjs +6 -6
  177. package/server/config.js +1 -1
  178. package/server/middleware/auth.js +253 -253
  179. package/server/middleware/managerAuth.js +3 -3
  180. package/server/routes/api/actions.js +4 -4
  181. package/server/routes/api/api-endpoints.js +7 -7
  182. package/server/routes/api/api-tokens.js +5 -5
  183. package/server/routes/api/auth.js +309 -309
  184. package/server/routes/api/blocks.js +3 -3
  185. package/server/routes/api/collections.js +18 -18
  186. package/server/routes/api/dashboard.js +5 -5
  187. package/server/routes/api/effects.js +2 -2
  188. package/server/routes/api/endpoints-public.js +5 -5
  189. package/server/routes/api/forms.js +47 -47
  190. package/server/routes/api/menu-locations.js +3 -3
  191. package/server/routes/api/menus.js +7 -7
  192. package/server/routes/api/navigation.js +42 -42
  193. package/server/routes/api/notifications.js +2 -2
  194. package/server/routes/api/pages.js +14 -2
  195. package/server/routes/api/plugin-marketplace.js +3 -3
  196. package/server/routes/api/plugins.js +6 -6
  197. package/server/routes/api/projects.js +9 -9
  198. package/server/routes/api/scaffold.js +7 -7
  199. package/server/routes/api/settings.js +6 -6
  200. package/server/routes/api/sidebar.js +1 -1
  201. package/server/routes/api/users.js +5 -5
  202. package/server/routes/api/versions.js +1 -1
  203. package/server/routes/api/views.js +1 -1
  204. package/server/routes/docs-public.js +3 -3
  205. package/server/routes/public.js +14 -14
  206. package/server/server.js +34 -16
  207. package/server/services/actions.js +19 -19
  208. package/server/services/adapters/FileAdapter.js +2 -2
  209. package/server/services/adapters/MongoAdapter.js +5 -5
  210. package/server/services/apiEndpoints.js +15 -15
  211. package/server/services/apiTokens.js +7 -7
  212. package/server/services/blocks.js +13 -13
  213. package/server/services/cache/drivers/MemoryDriver.js +3 -3
  214. package/server/services/cache/drivers/NoneDriver.js +1 -1
  215. package/server/services/cache/index.js +5 -5
  216. package/server/services/collections.js +14 -14
  217. package/server/services/components.js +9 -9
  218. package/server/services/connectionManager.js +4 -4
  219. package/server/services/content.js +25 -2
  220. package/server/services/docs.js +14 -14
  221. package/server/services/email.js +167 -167
  222. package/server/services/filterEngine.js +6 -6
  223. package/server/services/forms.js +18 -10
  224. package/server/services/health.js +1 -1
  225. package/server/services/hooks.js +7 -7
  226. package/server/services/images.js +4 -4
  227. package/server/services/managerClient.js +1 -1
  228. package/server/services/markdown.js +79 -79
  229. package/server/services/menuRender.js +5 -5
  230. package/server/services/menus-migration.js +3 -3
  231. package/server/services/menus.js +31 -31
  232. package/server/services/permissionRegistry.js +1 -1
  233. package/server/services/pluginFiles.js +5 -5
  234. package/server/services/pluginInstaller.js +17 -17
  235. package/server/services/pluginScaffold.js +1 -1
  236. package/server/services/plugins.js +17 -17
  237. package/server/services/presetCollections.js +2 -2
  238. package/server/services/projects.js +35 -26
  239. package/server/services/recipes/contact-list.json +1 -1
  240. package/server/services/recipes/onboarding.json +8 -8
  241. package/server/services/references.js +8 -8
  242. package/server/services/renderer.js +9 -9
  243. package/server/services/roles.js +9 -9
  244. package/server/services/rowAccess.js +10 -10
  245. package/server/services/scaffolder.js +30 -30
  246. package/server/services/sidebar-migration.js +10 -10
  247. package/server/services/sitemap.js +1 -1
  248. package/server/services/userProfiles.js +199 -199
  249. package/server/services/userRoles.js +7 -7
  250. package/server/services/users.js +302 -302
  251. package/server/services/versions.js +3 -3
  252. package/server/services/viewPipeline.js +6 -6
  253. package/server/services/views.js +11 -11
  254. package/server/templates/page.html +5 -5
@@ -25,8 +25,8 @@ import{api as S}from"../api.js";import{colourToCss as rt}from"/public/js/menu-de
25
25
  <button class="btn btn-sm btn-secondary me-edit" data-tooltip="Edit"><span data-icon="edit"></span></button>
26
26
  <button class="btn btn-sm btn-ghost me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
27
27
  </div>
28
- `),i(o.id)}}i(null),Domma.icons.scan(t.get(0)),t.get(0).querySelectorAll("[data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})}function F(t,l){t.find(".menu-tree-row").each(function(){const a=$(this).data("id"),i=l.find(k=>k.id===a);if(!i||i.type==="separator")return;const n=$(this).find(".me-text");n.length&&(i.text=n.val());const o=$(this).find(".me-url");o.length&&(i.url=o.val());const c=$(this).find(".me-icon");c.length&&(i.icon=c.val());const h=$(this).find(".me-vis");h.length&&(i.visibility=h.val());const x=$(this).find(".me-perm");x.length&&(i.permission=x.val())})}function _t(t,l){const a={};for(const i of l){const n=t.querySelector(`[name="${i}"]`);n&&(a[i]=n.type==="checkbox"?n.checked:n.value)}return a}function g(t,l={},...a){const i=document.createElement(t);Object.assign(i,l);for(const n of a)n!=null&&i.append(n);return i}function j(t,l,a){const i=g("label",{className:"form-field"},g("span",{className:"form-label",textContent:t}),l);return a&&i.append(g("small",{className:"form-hint",textContent:a,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),i}function ht(t,l){const a=[["","\u2014 Everyone \u2014"],["public","Public \u2014 anyone, incl. logged-out"],...(l||[]).map(n=>[n.name,`${n.label||n.name} and above`]),["private","Private \u2014 super-admin only"]];t&&!a.some(([n])=>n===t)&&a.push([t,`${t} (custom)`]);const i=B(a,t||"");return i.name="visibility",i}function B(t,l){const a=g("select",{className:"form-select"});return t.forEach(([i,n])=>a.add(new Option(n,i))),a.value=l??"",a}function K(t,l){const a=typeof l=="string"&&l.startsWith("#"),i=B([["","\u2014 none \u2014"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],a?"__custom__":l||""),n=g("input",{type:"color",className:"form-input me-colour-hex",value:a?l:"#000000"});n.style.display=a?"":"none",i.addEventListener("change",()=>{n.style.display=i.value==="__custom__"?"":"none"});const o=j(t,i);return o.append(n),o._read=()=>i.value==="__custom__"?n.value:i.value,o}function yt(t,l){const a=g("input",{className:"form-input me-icon",name:"icon",value:l||""}),i=g("span",{className:"me-icon-preview"});l&&i.setAttribute("data-icon",l);const n=g("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),o=c=>{c?i.setAttribute("data-icon",c):i.removeAttribute("data-icon"),Domma.icons.scan(i.parentNode||i)};return n.addEventListener("click",()=>ct(c=>{a.value=c,o(c)})),a.addEventListener("input",()=>o(a.value.trim())),j(t,g("div",{className:"me-icon-field"},a,n,i))}function Z(...t){return g("div",{className:"me-field-grid"},...t)}async function wt(t,l,a,i,n){const o=f=>g("h4",{className:"me-form-section",textContent:f}),c=j("Label",g("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),h=j("URL",g("input",{className:"form-input",name:"url",value:t.url||""}),"Where it links. Must start with /, #, https://, or mailto: . Leave blank for a parent that only groups sub-items."),x=yt("Icon",t.icon||""),k=j("Visibility",ht(t.visibility||"",i),"Who can see this item, by role seniority. Picking a role shows it to that role and anyone more senior. Blank = everyone."),D=B([["","\u2014 no permission gate \u2014"],...(l||[]).map(f=>[f.key,f.label||f.key])],t.permission||"");D.name="permission";const Y=j("Permission",D,"Hide unless the viewer holds this specific permission. Independent of Visibility \u2014 both must pass."),J=j("Badge text",g("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),X=B([["","\u2014 none \u2014"],...(a||[]).map(f=>[f.slug,f.title||f.slug])],t.badge?.countFrom||"");X.name="badgeCount";const V=j("Badge count from",X,"Show a live count of this collection\u2019s entries as the badge."),U=K("Badge colour",t.badge?.variant||""),d=B([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");d.name="renderAs";const w=j("Render as",d,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),v=j("Pill style",B([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));v.querySelector("select").name="pillStyle";const z=K("Pill colour",t.pill?.variant||""),C=g("div",{className:"me-pill-wrap"},Z(v,z));C.style.display=t.pill?"":"none",d.addEventListener("change",()=>{C.style.display=d.value==="pill"?"":"none"});const R=K("Text colour",t.colour||""),W=g("button",{className:"btn btn-primary",textContent:"Apply"}),A=g("div",{className:"me-item-form"},o("Link"),c,h,x,o("Access"),Z(k,Y),o("Badge"),Z(J,U),V,o("Appearance"),w,C,R,W),_=E.slideover({title:"Edit menu item",size:"lg",position:"right"});_.element.appendChild(A),_.open();const T=_.element.closest(".dm-slideover")||_.element.querySelector(".dm-slideover")||_.element;T.style.setProperty("width",ut+"px","important"),T.style.setProperty("max-width","95vw","important"),Domma.icons.scan(A);const I=f=>A.querySelector(`[name="${f}"]`)?.value||"";W.addEventListener("click",()=>{t.text=I("text"),t.url=I("url"),t.icon=I("icon"),t.visibility=I("visibility"),t.permission=I("permission");const f=I("badgeText"),L=I("badgeCount"),N=U._read();if(t.badge=f||L?{...f&&{text:f},...L&&{countFrom:L},...N&&{variant:N}}:null,I("renderAs")==="pill"){const P=z._read();t.pill={style:I("pillStyle")||"filled",...P&&{variant:P}}}else t.pill=null;t.colour=R._read(),_.close(),n()})}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const l=location.hash.match(/^#\/menus\/edit\/(.+)$/),a=l?decodeURIComponent(l[1]):null,i=!a,n=E.loader(t.get(0),{type:"dots"});let o,c,h,x,k,D,Y;try{if(i)o={slug:"",name:"",description:"",items:[]};else try{o=await S.menus.get(a)}catch(e){E.toast(`Failed to load menu: ${e.message||e}`,{type:"error"}),location.hash="#/menus";return}c=await S.projects.list().catch(()=>[]),h=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),x=await S.collections.list().catch(()=>[]),k=await S.get("/collections/roles/entries?limit=100").catch(()=>null),D=await S.menuLocations.registry().catch(()=>[]),Y=await S.pages.list().catch(()=>[])}finally{n.destroy()}const X=(Array.isArray(k)?k:k?.entries||[]).map(e=>({name:e.data?.name,label:e.data?.label,level:e.data?.level??99})).filter(e=>e.name).sort((e,s)=>s.level-e.level),V='<option value="">\u2014 no permission gate \u2014</option>',U=e=>V+(h.resources||[]).map(s=>{const r=(e||"")===s.key?" selected":"";return`<option value="${s.key}"${r}>${s.label||s.key}</option>`}).join("");t.find("#me-title").text(i?"New menu":`Edit "${o.slug}"`);let d=et(o.items);const w=t.find("#me-items-list");function v(){vt(w,d,U),gt(w,()=>d,v)}v(),t.find("#me-add-item").on("click",()=>{F(w,d),d.push({id:q(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),v()}),t.find("#me-add-separator").on("click",()=>{F(w,d),d.push({id:q(),parentId:null,type:"separator"}),v()});function z(e){return function(){F(w,d);const s=$(this).closest(".menu-tree-row").data("id"),r=d.findIndex(p=>p.id===s);if(r===-1)return;const m=d[r],u=e==="separator"?{id:q(),parentId:m.parentId,type:"separator"}:{id:q(),parentId:m.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};d.splice(r+1,0,u),v()}}t.off("click",".me-ins-item").on("click",".me-ins-item",z("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",z("separator")),t.off("click",".me-remove").on("click",".me-remove",function(){const e=$(this).closest(".menu-tree-row").data("id");d=d.filter(s=>s.id!==e&&s.parentId!==e),v()}),t.off("click",".me-indent").on("click",".me-indent",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(u=>u.id===e);if(!s)return;const r=d.filter(u=>u.parentId===s.parentId),m=r.findIndex(u=>u.id===e);m<=0||r[m-1].type!=="separator"&&(s.parentId=r[m-1].id,v())}),t.off("click",".me-outdent").on("click",".me-outdent",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(m=>m.id===e);if(!s||s.parentId==null)return;const r=d.find(m=>m.id===s.parentId);s.parentId=r?r.parentId:null,v()}),t.off("click",".me-up").on("click",".me-up",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.filter(p=>p.parentId===d.find(y=>y.id===e)?.parentId),r=s.findIndex(p=>p.id===e);if(r<=0)return;const m=d.indexOf(s[r]),u=d.indexOf(s[r-1]);[d[m],d[u]]=[d[u],d[m]],v()}),t.off("click",".me-down").on("click",".me-down",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.filter(p=>p.parentId===d.find(y=>y.id===e)?.parentId),r=s.findIndex(p=>p.id===e);if(r===-1||r>=s.length-1)return;const m=d.indexOf(s[r]),u=d.indexOf(s[r+1]);[d[m],d[u]]=[d[u],d[m]],v()}),t.off("click",".me-hidden").on("click",".me-hidden",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(r=>r.id===e);s&&(s.hidden=!s.hidden),v()}),t.off("click",".me-edit").on("click",".me-edit",function(){const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(r=>r.id===e);!s||s.type==="separator"||wt(s,h.resources||[],x,X,()=>v())}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(o.variant||""),t.find("#me-position").val(o.position||"");const C=t.find("#me-appearOnHover").get(0);C&&(C.checked=o.appearOnHover===!0),t.find("#me-float-anchor").val(o.float?.anchor||"TL"),t.find("#me-float-offsetX").val(o.float?.offsetX??""),t.find("#me-float-offsetY").val(o.float?.offsetY??""),t.find("#me-float-width").val(o.float?.width??""),t.find("#me-float-radius").val(o.float?.radius??""),t.find("#me-float-shadow").val(o.float?.shadow||""),t.find("#me-float-opacity").val(o.float?.opacity??"");const R=o.float?.accent||"",W=R.startsWith("#"),A=t.find("#me-float-accent"),_=t.find("#me-float-accent-hex");A.val(W?"__custom__":R),W&&_.val(R);const T=()=>{_.get(0).style.display=A.val()==="__custom__"?"":"none"};T(),A.on("change",T);const I=()=>A.val()==="__custom__"?_.val():A.val(),f=()=>{const e=t.find("#me-position").val()==="floating";t.find(".dm-float-field").each(function(){this.style.display=e?"":"none"}),e&&T();const s=t.find("#me-orientation").val()==="vertical";t.find(".dm-vertical-field").each(function(){this.style.display=s?"":"none"})};t.find("#me-orientation").val(o.orientation||""),t.find("#me-side").val(o.side||"left"),f(),t.find("#me-position").on("change",f),t.find("#me-orientation").on("change",f),t.find("#me-fontFamily").val(o.style?.fontFamily||""),t.find("#me-fontSize").val(o.style?.fontSize||""),t.find("#me-fontWeight").val(o.style?.fontWeight||""),t.find("#me-letterSpacing").val(o.style?.letterSpacing||""),t.find("#me-iconSize").val(o.style?.iconSize||"");const L=t.find("#me-bind-slot"),N=t.find("#me-bind-projects"),P=t.find("#me-bind-pages");L.html('<option value="">\u2014 not bound \u2014</option>'+(D||[]).map(e=>`<option value="${b(e.slot)}">${b(e.label||e.slot)} (${b(e.slot)})</option>`).join("")),N.html(c.map(e=>`<option value="${b(e.slug)}">${b(e.name||e.slug)}</option>`).join("")),L.val(o.binding?.slot||"");const ot=new Set(o.binding?.projects||[]);Array.from(N.get(0).options).forEach(e=>{e.selected=ot.has(e.value)}),P.val((o.binding?.pages||[]).join(`
29
- `)),t.find("#me-bind-page-pick").html('<option value="">\u2014 pick a page \u2014</option>'+Y.map(e=>{const s=e.urlPath||e.url||"";return s?`<option value="${b(s)}">${b(e.title||s)} \u2014 ${b(s)}</option>`:""}).join("")),t.find("#me-bind-page-add").on("click",()=>{const e=t.find("#me-bind-page-pick").val();if(!e)return;const s=P.val().split(`
28
+ `),i(o.id)}}i(null),Domma.icons.scan(t.get(0)),t.get(0).querySelectorAll("[data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})}function F(t,l){t.find(".menu-tree-row").each(function(){const a=$(this).data("id"),i=l.find(k=>k.id===a);if(!i||i.type==="separator")return;const n=$(this).find(".me-text");n.length&&(i.text=n.val());const o=$(this).find(".me-url");o.length&&(i.url=o.val());const c=$(this).find(".me-icon");c.length&&(i.icon=c.val());const h=$(this).find(".me-vis");h.length&&(i.visibility=h.val());const x=$(this).find(".me-perm");x.length&&(i.permission=x.val())})}function _t(t,l){const a={};for(const i of l){const n=t.querySelector(`[name="${i}"]`);n&&(a[i]=n.type==="checkbox"?n.checked:n.value)}return a}function g(t,l={},...a){const i=document.createElement(t);Object.assign(i,l);for(const n of a)n!=null&&i.append(n);return i}function j(t,l,a){const i=g("label",{className:"form-field"},g("span",{className:"form-label",textContent:t}),l);return a&&i.append(g("small",{className:"form-hint",textContent:a,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),i}function ht(t,l){const a=[["","- Everyone -"],["public","Public - anyone, incl. logged-out"],...(l||[]).map(n=>[n.name,`${n.label||n.name} and above`]),["private","Private - super-admin only"]];t&&!a.some(([n])=>n===t)&&a.push([t,`${t} (custom)`]);const i=B(a,t||"");return i.name="visibility",i}function B(t,l){const a=g("select",{className:"form-select"});return t.forEach(([i,n])=>a.add(new Option(n,i))),a.value=l??"",a}function K(t,l){const a=typeof l=="string"&&l.startsWith("#"),i=B([["","- none -"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],a?"__custom__":l||""),n=g("input",{type:"color",className:"form-input me-colour-hex",value:a?l:"#000000"});n.style.display=a?"":"none",i.addEventListener("change",()=>{n.style.display=i.value==="__custom__"?"":"none"});const o=j(t,i);return o.append(n),o._read=()=>i.value==="__custom__"?n.value:i.value,o}function yt(t,l){const a=g("input",{className:"form-input me-icon",name:"icon",value:l||""}),i=g("span",{className:"me-icon-preview"});l&&i.setAttribute("data-icon",l);const n=g("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),o=c=>{c?i.setAttribute("data-icon",c):i.removeAttribute("data-icon"),Domma.icons.scan(i.parentNode||i)};return n.addEventListener("click",()=>ct(c=>{a.value=c,o(c)})),a.addEventListener("input",()=>o(a.value.trim())),j(t,g("div",{className:"me-icon-field"},a,n,i))}function Z(...t){return g("div",{className:"me-field-grid"},...t)}async function wt(t,l,a,i,n){const o=f=>g("h4",{className:"me-form-section",textContent:f}),c=j("Label",g("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),h=j("URL",g("input",{className:"form-input",name:"url",value:t.url||""}),"Where it links. Must start with /, #, https://, or mailto: . Leave blank for a parent that only groups sub-items."),x=yt("Icon",t.icon||""),k=j("Visibility",ht(t.visibility||"",i),"Who can see this item, by role seniority. Picking a role shows it to that role and anyone more senior. Blank = everyone."),D=B([["","- no permission gate -"],...(l||[]).map(f=>[f.key,f.label||f.key])],t.permission||"");D.name="permission";const Y=j("Permission",D,"Hide unless the viewer holds this specific permission. Independent of Visibility - both must pass."),J=j("Badge text",g("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),X=B([["","- none -"],...(a||[]).map(f=>[f.slug,f.title||f.slug])],t.badge?.countFrom||"");X.name="badgeCount";const V=j("Badge count from",X,"Show a live count of this collection\u2019s entries as the badge."),U=K("Badge colour",t.badge?.variant||""),d=B([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");d.name="renderAs";const w=j("Render as",d,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),v=j("Pill style",B([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));v.querySelector("select").name="pillStyle";const z=K("Pill colour",t.pill?.variant||""),C=g("div",{className:"me-pill-wrap"},Z(v,z));C.style.display=t.pill?"":"none",d.addEventListener("change",()=>{C.style.display=d.value==="pill"?"":"none"});const R=K("Text colour",t.colour||""),W=g("button",{className:"btn btn-primary",textContent:"Apply"}),A=g("div",{className:"me-item-form"},o("Link"),c,h,x,o("Access"),Z(k,Y),o("Badge"),Z(J,U),V,o("Appearance"),w,C,R,W),_=E.slideover({title:"Edit menu item",size:"lg",position:"right"});_.element.appendChild(A),_.open();const T=_.element.closest(".dm-slideover")||_.element.querySelector(".dm-slideover")||_.element;T.style.setProperty("width",ut+"px","important"),T.style.setProperty("max-width","95vw","important"),Domma.icons.scan(A);const I=f=>A.querySelector(`[name="${f}"]`)?.value||"";W.addEventListener("click",()=>{t.text=I("text"),t.url=I("url"),t.icon=I("icon"),t.visibility=I("visibility"),t.permission=I("permission");const f=I("badgeText"),L=I("badgeCount"),N=U._read();if(t.badge=f||L?{...f&&{text:f},...L&&{countFrom:L},...N&&{variant:N}}:null,I("renderAs")==="pill"){const P=z._read();t.pill={style:I("pillStyle")||"filled",...P&&{variant:P}}}else t.pill=null;t.colour=R._read(),_.close(),n()})}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const l=location.hash.match(/^#\/menus\/edit\/(.+)$/),a=l?decodeURIComponent(l[1]):null,i=!a,n=E.loader(t.get(0),{type:"dots"});let o,c,h,x,k,D,Y;try{if(i)o={slug:"",name:"",description:"",items:[]};else try{o=await S.menus.get(a)}catch(e){E.toast(`Failed to load menu: ${e.message||e}`,{type:"error"}),location.hash="#/menus";return}c=await S.projects.list().catch(()=>[]),h=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),x=await S.collections.list().catch(()=>[]),k=await S.get("/collections/roles/entries?limit=100").catch(()=>null),D=await S.menuLocations.registry().catch(()=>[]),Y=await S.pages.list().catch(()=>[])}finally{n.destroy()}const X=(Array.isArray(k)?k:k?.entries||[]).map(e=>({name:e.data?.name,label:e.data?.label,level:e.data?.level??99})).filter(e=>e.name).sort((e,s)=>s.level-e.level),V='<option value="">- no permission gate -</option>',U=e=>V+(h.resources||[]).map(s=>{const r=(e||"")===s.key?" selected":"";return`<option value="${s.key}"${r}>${s.label||s.key}</option>`}).join("");t.find("#me-title").text(i?"New menu":`Edit "${o.slug}"`);let d=et(o.items);const w=t.find("#me-items-list");function v(){vt(w,d,U),gt(w,()=>d,v)}v(),t.find("#me-add-item").on("click",()=>{F(w,d),d.push({id:q(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),v()}),t.find("#me-add-separator").on("click",()=>{F(w,d),d.push({id:q(),parentId:null,type:"separator"}),v()});function z(e){return function(){F(w,d);const s=$(this).closest(".menu-tree-row").data("id"),r=d.findIndex(p=>p.id===s);if(r===-1)return;const m=d[r],u=e==="separator"?{id:q(),parentId:m.parentId,type:"separator"}:{id:q(),parentId:m.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};d.splice(r+1,0,u),v()}}t.off("click",".me-ins-item").on("click",".me-ins-item",z("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",z("separator")),t.off("click",".me-remove").on("click",".me-remove",function(){const e=$(this).closest(".menu-tree-row").data("id");d=d.filter(s=>s.id!==e&&s.parentId!==e),v()}),t.off("click",".me-indent").on("click",".me-indent",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(u=>u.id===e);if(!s)return;const r=d.filter(u=>u.parentId===s.parentId),m=r.findIndex(u=>u.id===e);m<=0||r[m-1].type!=="separator"&&(s.parentId=r[m-1].id,v())}),t.off("click",".me-outdent").on("click",".me-outdent",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(m=>m.id===e);if(!s||s.parentId==null)return;const r=d.find(m=>m.id===s.parentId);s.parentId=r?r.parentId:null,v()}),t.off("click",".me-up").on("click",".me-up",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.filter(p=>p.parentId===d.find(y=>y.id===e)?.parentId),r=s.findIndex(p=>p.id===e);if(r<=0)return;const m=d.indexOf(s[r]),u=d.indexOf(s[r-1]);[d[m],d[u]]=[d[u],d[m]],v()}),t.off("click",".me-down").on("click",".me-down",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.filter(p=>p.parentId===d.find(y=>y.id===e)?.parentId),r=s.findIndex(p=>p.id===e);if(r===-1||r>=s.length-1)return;const m=d.indexOf(s[r]),u=d.indexOf(s[r+1]);[d[m],d[u]]=[d[u],d[m]],v()}),t.off("click",".me-hidden").on("click",".me-hidden",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(r=>r.id===e);s&&(s.hidden=!s.hidden),v()}),t.off("click",".me-edit").on("click",".me-edit",function(){const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(r=>r.id===e);!s||s.type==="separator"||wt(s,h.resources||[],x,X,()=>v())}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(o.variant||""),t.find("#me-position").val(o.position||"");const C=t.find("#me-appearOnHover").get(0);C&&(C.checked=o.appearOnHover===!0),t.find("#me-float-anchor").val(o.float?.anchor||"TL"),t.find("#me-float-offsetX").val(o.float?.offsetX??""),t.find("#me-float-offsetY").val(o.float?.offsetY??""),t.find("#me-float-width").val(o.float?.width??""),t.find("#me-float-radius").val(o.float?.radius??""),t.find("#me-float-shadow").val(o.float?.shadow||""),t.find("#me-float-opacity").val(o.float?.opacity??"");const R=o.float?.accent||"",W=R.startsWith("#"),A=t.find("#me-float-accent"),_=t.find("#me-float-accent-hex");A.val(W?"__custom__":R),W&&_.val(R);const T=()=>{_.get(0).style.display=A.val()==="__custom__"?"":"none"};T(),A.on("change",T);const I=()=>A.val()==="__custom__"?_.val():A.val(),f=()=>{const e=t.find("#me-position").val()==="floating";t.find(".dm-float-field").each(function(){this.style.display=e?"":"none"}),e&&T();const s=t.find("#me-orientation").val()==="vertical";t.find(".dm-vertical-field").each(function(){this.style.display=s?"":"none"})};t.find("#me-orientation").val(o.orientation||""),t.find("#me-side").val(o.side||"left"),f(),t.find("#me-position").on("change",f),t.find("#me-orientation").on("change",f),t.find("#me-fontFamily").val(o.style?.fontFamily||""),t.find("#me-fontSize").val(o.style?.fontSize||""),t.find("#me-fontWeight").val(o.style?.fontWeight||""),t.find("#me-letterSpacing").val(o.style?.letterSpacing||""),t.find("#me-iconSize").val(o.style?.iconSize||"");const L=t.find("#me-bind-slot"),N=t.find("#me-bind-projects"),P=t.find("#me-bind-pages");L.html('<option value="">- not bound -</option>'+(D||[]).map(e=>`<option value="${b(e.slot)}">${b(e.label||e.slot)} (${b(e.slot)})</option>`).join("")),N.html(c.map(e=>`<option value="${b(e.slug)}">${b(e.name||e.slug)}</option>`).join("")),L.val(o.binding?.slot||"");const ot=new Set(o.binding?.projects||[]);Array.from(N.get(0).options).forEach(e=>{e.selected=ot.has(e.value)}),P.val((o.binding?.pages||[]).join(`
29
+ `)),t.find("#me-bind-page-pick").html('<option value="">- pick a page -</option>'+Y.map(e=>{const s=e.urlPath||e.url||"";return s?`<option value="${b(s)}">${b(e.title||s)} - ${b(s)}</option>`:""}).join("")),t.find("#me-bind-page-add").on("click",()=>{const e=t.find("#me-bind-page-pick").val();if(!e)return;const s=P.val().split(`
30
30
  `).map(r=>r.trim()).filter(Boolean);s.includes(e)||s.push(e),P.val(s.join(`
31
31
  `)),M()});function Q(){const e=L.val();return e?{slot:e,projects:Array.from(N.get(0).selectedOptions).map(s=>s.value),pages:P.val().split(`
32
- `).map(s=>s.trim()).filter(Boolean)}:null}function M(){const e=Q(),s=t.find("#me-bind-summary");if(!e){s.html('<span class="text-muted">Not bound \u2014 this menu renders only where it is mapped or placed by a shortcode.</span>');return}const r=[];e.projects.length&&r.push(`pages in ${e.projects.map(p=>`<code>${b(p)}</code>`).join(", ")}`),e.pages.length&&r.push(`pages matching ${e.pages.map(p=>`<code>${b(p)}</code>`).join(", ")}`);const m=e.pages.filter(p=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(p)),u=m.length?`<div class="text-danger" style="margin-top:.4rem">Not valid page patterns: ${m.map(p=>`<code>${b(p)}</code>`).join(", ")}</div>`:"";s.html(r.length?`Takes over the <code>${b(e.slot)}</code> slot on ${r.join(" and ")}.${u}`:`<span class="text-warning">Slot <code>${b(e.slot)}</code> selected but no project or page chosen \u2014 the binding will never match.</span>`)}L.on("change",M),N.on("change",M),P.on("input",M),M();const{getProjectFromHash:st}=await import("../lib/project-context.js"),at=i?st():null,tt=t.find("#me-project");tt.html('<option value="">\u2014 none \u2014</option>'+c.map(e=>`<option value="${b(e.slug)}">${b(e.name||e.slug)}</option>`).join("")),t.find("#me-slug").val(o.slug||""),t.find("#me-name").val(o.name||""),t.find("#me-description").val(o.description||""),tt.val(o.meta?.project||at||""),Domma.icons.scan(t.get(0)),t.find("#me-save").on("click",async()=>{F(w,d);const e={variant:t.find("#me-variant").val(),position:t.find("#me-position").val(),appearOnHover:!!(t.find("#me-appearOnHover").get(0)||{}).checked,floatAnchor:t.find("#me-float-anchor").val(),floatOffsetX:t.find("#me-float-offsetX").val(),floatOffsetY:t.find("#me-float-offsetY").val(),floatWidth:t.find("#me-float-width").val(),floatRadius:t.find("#me-float-radius").val(),floatShadow:t.find("#me-float-shadow").val(),floatAccent:I(),floatOpacity:t.find("#me-float-opacity").val(),orientation:t.find("#me-orientation").val(),side:t.find("#me-side").val(),fontFamily:t.find("#me-fontFamily").val(),fontSize:t.find("#me-fontSize").val(),fontWeight:t.find("#me-fontWeight").val(),letterSpacing:t.find("#me-letterSpacing").val(),iconSize:t.find("#me-iconSize").val()},s={slug:t.find("#me-slug").val().trim(),name:t.find("#me-name").val().trim(),description:t.find("#me-description").val().trim(),project:t.find("#me-project").val()},r=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"],m=Object.fromEntries(Object.entries(e).filter(([y,O])=>r.includes(y)&&O)),u={};if(e.position==="floating"){e.floatAnchor&&(u.anchor=e.floatAnchor);for(const[y,O]of[["offsetX",e.floatOffsetX],["offsetY",e.floatOffsetY],["width",e.floatWidth],["radius",e.floatRadius],["opacity",e.floatOpacity]])O!==""&&O!=null&&Number.isFinite(Number(O))&&(u[y]=Number(O));e.floatShadow&&(u.shadow=e.floatShadow),e.floatAccent&&(u.accent=e.floatAccent)}const p={slug:s.slug,name:s.name,description:s.description,...e.variant&&{variant:e.variant},...e.position&&{position:e.position},...e.orientation&&{orientation:e.orientation},...e.orientation==="vertical"&&e.side&&{side:e.side},...Object.keys(u).length&&{float:u},...e.appearOnHover&&{appearOnHover:!0},...Object.keys(m).length&&{style:m},binding:Q(),items:ft(d),meta:{...o.meta||{},project:s.project||null}};try{if(i)await S.menus.create(p);else if(p.slug!==o.slug){await S.menus.create(p);const y=await S.menuLocations.get();let O=!1;for(const[lt,dt]of Object.entries(y))dt===o.slug&&(y[lt]=p.slug,O=!0);O&&await S.menuLocations.save(y),await S.menus.remove(o.slug)}else await S.menus.update(o.slug,p);E.toast("Saved.",{type:"success"}),location.hash="#/menus/edit/"+encodeURIComponent(p.slug)}catch(y){E.toast(`Save failed: ${y.message||y}`,{type:"error"})}})}};
32
+ `).map(s=>s.trim()).filter(Boolean)}:null}function M(){const e=Q(),s=t.find("#me-bind-summary");if(!e){s.html('<span class="text-muted">Not bound - this menu renders only where it is mapped or placed by a shortcode.</span>');return}const r=[];e.projects.length&&r.push(`pages in ${e.projects.map(p=>`<code>${b(p)}</code>`).join(", ")}`),e.pages.length&&r.push(`pages matching ${e.pages.map(p=>`<code>${b(p)}</code>`).join(", ")}`);const m=e.pages.filter(p=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(p)),u=m.length?`<div class="text-danger" style="margin-top:.4rem">Not valid page patterns: ${m.map(p=>`<code>${b(p)}</code>`).join(", ")}</div>`:"";s.html(r.length?`Takes over the <code>${b(e.slot)}</code> slot on ${r.join(" and ")}.${u}`:`<span class="text-warning">Slot <code>${b(e.slot)}</code> selected but no project or page chosen - the binding will never match.</span>`)}L.on("change",M),N.on("change",M),P.on("input",M),M();const{getProjectFromHash:st}=await import("../lib/project-context.js"),at=i?st():null,tt=t.find("#me-project");tt.html('<option value="">- none -</option>'+c.map(e=>`<option value="${b(e.slug)}">${b(e.name||e.slug)}</option>`).join("")),t.find("#me-slug").val(o.slug||""),t.find("#me-name").val(o.name||""),t.find("#me-description").val(o.description||""),tt.val(o.meta?.project||at||""),Domma.icons.scan(t.get(0)),t.find("#me-save").on("click",async()=>{F(w,d);const e={variant:t.find("#me-variant").val(),position:t.find("#me-position").val(),appearOnHover:!!(t.find("#me-appearOnHover").get(0)||{}).checked,floatAnchor:t.find("#me-float-anchor").val(),floatOffsetX:t.find("#me-float-offsetX").val(),floatOffsetY:t.find("#me-float-offsetY").val(),floatWidth:t.find("#me-float-width").val(),floatRadius:t.find("#me-float-radius").val(),floatShadow:t.find("#me-float-shadow").val(),floatAccent:I(),floatOpacity:t.find("#me-float-opacity").val(),orientation:t.find("#me-orientation").val(),side:t.find("#me-side").val(),fontFamily:t.find("#me-fontFamily").val(),fontSize:t.find("#me-fontSize").val(),fontWeight:t.find("#me-fontWeight").val(),letterSpacing:t.find("#me-letterSpacing").val(),iconSize:t.find("#me-iconSize").val()},s={slug:t.find("#me-slug").val().trim(),name:t.find("#me-name").val().trim(),description:t.find("#me-description").val().trim(),project:t.find("#me-project").val()},r=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"],m=Object.fromEntries(Object.entries(e).filter(([y,O])=>r.includes(y)&&O)),u={};if(e.position==="floating"){e.floatAnchor&&(u.anchor=e.floatAnchor);for(const[y,O]of[["offsetX",e.floatOffsetX],["offsetY",e.floatOffsetY],["width",e.floatWidth],["radius",e.floatRadius],["opacity",e.floatOpacity]])O!==""&&O!=null&&Number.isFinite(Number(O))&&(u[y]=Number(O));e.floatShadow&&(u.shadow=e.floatShadow),e.floatAccent&&(u.accent=e.floatAccent)}const p={slug:s.slug,name:s.name,description:s.description,...e.variant&&{variant:e.variant},...e.position&&{position:e.position},...e.orientation&&{orientation:e.orientation},...e.orientation==="vertical"&&e.side&&{side:e.side},...Object.keys(u).length&&{float:u},...e.appearOnHover&&{appearOnHover:!0},...Object.keys(m).length&&{style:m},binding:Q(),items:ft(d),meta:{...o.meta||{},project:s.project||null}};try{if(i)await S.menus.create(p);else if(p.slug!==o.slug){await S.menus.create(p);const y=await S.menuLocations.get();let O=!1;for(const[lt,dt]of Object.entries(y))dt===o.slug&&(y[lt]=p.slug,O=!0);O&&await S.menuLocations.save(y),await S.menus.remove(o.slug)}else await S.menus.update(o.slug,p);E.toast("Saved.",{type:"success"}),location.hash="#/menus/edit/"+encodeURIComponent(p.slug)}catch(y){E.toast(`Save failed: ${y.message||y}`,{type:"error"})}})}};
@@ -1 +1 @@
1
- import{api as i}from"../api.js";function d(s){if(!Array.isArray(s)||s.length===0)return 0;let c=0;for(const l of s){const r=Array.isArray(l.items)&&l.items.length?d(l.items):0;r>c&&(c=r)}return c+1}function a(s){return String(s??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}export const menuLocationsView={templateUrl:"/admin/js/templates/menu-locations.html",async onMount(s){const c=E.loader(s.get(0),{type:"dots"});let l,r,p,m;try{[l,r,p]=await Promise.all([i.menuLocations.registry().catch(()=>[]),i.menuLocations.get().catch(()=>({})),i.menus.list().catch(()=>[])]),m=await Promise.all(p.map(e=>i.menus.get(e.slug).catch(()=>null)))}finally{c.destroy()}const u={};for(const e of m)e&&e.slug&&(u[e.slug]=d(e.items||[]));const g=(l||[]).map(e=>({slot:e.slot,label:e.label,source:e.source,description:e.description,maxDepth:e.maxDepth,menu:r[e.slot]||""}));T.create("#ml-table",{data:g,emptyMessage:"No menu slots registered.",columns:[{key:"slot",title:"Slot",render:e=>`<code>${a(e)}</code>`},{key:"label",title:"Label",render:e=>a(e)},{key:"source",title:"Source",render:e=>e?`<span class="badge badge-secondary">${a(e)}</span>`:""},{key:"description",title:"Description",render:e=>e?`<span class="text-muted">${a(e)}</span>`:""},{key:"menu",title:"Mapped menu",render:(e,t)=>{const n=['<option value="">&mdash; none &mdash;</option>'].concat(p.map(o=>{const h=o.slug===t.menu?" selected":"",y=a(o.name||o.slug);return`<option value="${a(o.slug)}"${h}>${y}</option>`}));return`<select class="form-select ml-select" data-slot="${a(t.slot)}">${n.join("")}</select>`}},{key:"_warning",title:"",render:(e,t)=>{if(!t.menu||t.maxDepth==null)return"";const n=u[t.menu]||0;if(n>t.maxDepth){const o=`This slot supports max depth ${t.maxDepth}; menu is depth ${n}`;return`<span class="badge badge-warning" data-tooltip="${a(o)}">&#9888; depth</span>`}return""}}]}),Domma.icons.scan("#ml-table"),document.querySelectorAll("#ml-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),s.find("#ml-save").on("click",async()=>{const e={};s.find(".ml-select").each(function(){const t=$(this).data("slot"),n=$(this).val();t&&n&&(e[t]=n)});try{await i.menuLocations.save(e),E.toast("Locations saved.",{type:"success"})}catch(t){E.toast(`Save failed: ${t.message||t}`,{type:"error"})}})}};
1
+ import{api as i}from"../api.js";function d(s){if(!Array.isArray(s)||s.length===0)return 0;let c=0;for(const l of s){const r=Array.isArray(l.items)&&l.items.length?d(l.items):0;r>c&&(c=r)}return c+1}function a(s){return String(s??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}export const menuLocationsView={templateUrl:"/admin/js/templates/menu-locations.html",async onMount(s){const c=E.loader(s.get(0),{type:"dots"});let l,r,p,u;try{[l,r,p]=await Promise.all([i.menuLocations.registry().catch(()=>[]),i.menuLocations.get().catch(()=>({})),i.menus.list().catch(()=>[])]),u=await Promise.all(p.map(e=>i.menus.get(e.slug).catch(()=>null)))}finally{c.destroy()}const m={};for(const e of u)e&&e.slug&&(m[e.slug]=d(e.items||[]));const g=(l||[]).map(e=>({slot:e.slot,label:e.label,source:e.source,description:e.description,maxDepth:e.maxDepth,menu:r[e.slot]||""}));T.create("#ml-table",{pageSize:25,data:g,emptyMessage:"No menu slots registered.",columns:[{key:"slot",title:"Slot",render:e=>`<code>${a(e)}</code>`},{key:"label",title:"Label",render:e=>a(e)},{key:"source",title:"Source",render:e=>e?`<span class="badge badge-secondary">${a(e)}</span>`:""},{key:"description",title:"Description",render:e=>e?`<span class="text-muted">${a(e)}</span>`:""},{key:"menu",title:"Mapped menu",render:(e,t)=>{const n=['<option value="">- none -</option>'].concat(p.map(o=>{const h=o.slug===t.menu?" selected":"",y=a(o.name||o.slug);return`<option value="${a(o.slug)}"${h}>${y}</option>`}));return`<select class="form-select ml-select" data-slot="${a(t.slot)}">${n.join("")}</select>`}},{key:"_warning",title:"",render:(e,t)=>{if(!t.menu||t.maxDepth==null)return"";const n=m[t.menu]||0;if(n>t.maxDepth){const o=`This slot supports max depth ${t.maxDepth}; menu is depth ${n}`;return`<span class="badge badge-warning" data-tooltip="${a(o)}">&#9888; depth</span>`}return""}}]}),Domma.icons.scan("#ml-table"),document.querySelectorAll("#ml-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),s.find("#ml-save").on("click",async()=>{const e={};s.find(".ml-select").each(function(){const t=$(this).data("slot"),n=$(this).val();t&&n&&(e[t]=n)});try{await i.menuLocations.save(e),E.toast("Locations saved.",{type:"success"})}catch(t){E.toast(`Save failed: ${t.message||t}`,{type:"error"})}})}};
@@ -1,5 +1,5 @@
1
- import{api as i}from"../api.js";import{filterByProject as b,getProjectFromHash as y}from"../lib/project-context.js";import{keepTableIconsAlive as f}from"../lib/table-icons.js";function o(s){return String(s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}export const menusView={templateUrl:"/admin/js/templates/menus.html",async onMount(s){const m=E.loader(s.get(0),{type:"dots"});let r,u;try{[r,u]=await Promise.all([i.menus.list().catch(()=>[]),i.menuLocations.get().catch(()=>({}))])}finally{m.destroy()}const p=y(),g=p?b(r,p):r,l={};for(const[e,t]of Object.entries(u||{}))t&&(l[t]||(l[t]=[]),l[t].push(e));T.create("#menus-table",{data:g,emptyMessage:'No menus yet. Click "New menu" to create one.',columns:[{key:"slug",title:"Slug",sortable:!0,render:e=>`<code>${o(e)}</code>`},{key:"name",title:"Name",sortable:!0,render:(e,t)=>`<a href="#/menus/edit/${encodeURIComponent(t.slug)}" style="font-weight:600;">${o(e||t.slug)}</a>`},{key:"itemsCount",title:"Items",render:(e,t)=>String(t.itemsCount??(Array.isArray(t.items)?t.items.length:0))},{key:"mapped",title:"Mapped",render:(e,t)=>{const n=l[t.slug]||[];return n.length?n.map(a=>`<span class="badge badge-info">${o(a)}</span>`).join(" "):'<span class="text-muted">\u2014</span>'}},{key:"binding",title:"Bound to",render:(e,t)=>{const n=t.binding;if(!n?.slot)return'<span class="text-muted">\u2014</span>';const a=[...(n.projects||[]).map(c=>`project:${c}`),...n.pages||[]],d=a.length?a.join(", "):"nothing \u2014 binding never matches";return`<span class="badge badge-warning" data-tooltip="${o(d)}">${o(n.slot)}</span>`}},{key:"bundled",title:"Bundled",render:(e,t)=>t.meta?.bundled?'<span class="badge badge-secondary">bundled</span>':""},{key:"_actions",title:"Actions",render:(e,t)=>{const n=(l[t.slug]||[]).length>0,a=n?'data-tooltip="Unmap this menu before deleting"':"",d=n?"disabled":"",c=o(t.slug);return`
1
+ import{api as i}from"../api.js";import{filterByProject as b,getProjectFromHash as y}from"../lib/project-context.js";import{keepTableIconsAlive as f}from"../lib/table-icons.js";function o(a){return String(a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}export const menusView={templateUrl:"/admin/js/templates/menus.html",async onMount(a){const m=E.loader(a.get(0),{type:"dots"});let r,u;try{[r,u]=await Promise.all([i.menus.list().catch(()=>[]),i.menuLocations.get().catch(()=>({}))])}finally{m.destroy()}const p=y(),g=p?b(r,p):r,l={};for(const[e,t]of Object.entries(u||{}))t&&(l[t]||(l[t]=[]),l[t].push(e));T.create("#menus-table",{pageSize:25,data:g,emptyMessage:'No menus yet. Click "New menu" to create one.',columns:[{key:"slug",title:"Slug",sortable:!0,render:e=>`<code>${o(e)}</code>`},{key:"name",title:"Name",sortable:!0,render:(e,t)=>`<a href="#/menus/edit/${encodeURIComponent(t.slug)}" style="font-weight:600;">${o(e||t.slug)}</a>`},{key:"itemsCount",title:"Items",render:(e,t)=>String(t.itemsCount??(Array.isArray(t.items)?t.items.length:0))},{key:"mapped",title:"Mapped",render:(e,t)=>{const n=l[t.slug]||[];return n.length?n.map(s=>`<span class="badge badge-info">${o(s)}</span>`).join(" "):'<span class="text-muted">-</span>'}},{key:"binding",title:"Bound to",render:(e,t)=>{const n=t.binding;if(!n?.slot)return'<span class="text-muted">-</span>';const s=[...(n.projects||[]).map(c=>`project:${c}`),...n.pages||[]],d=s.length?s.join(", "):"nothing - binding never matches";return`<span class="badge badge-warning" data-tooltip="${o(d)}">${o(n.slot)}</span>`}},{key:"bundled",title:"Bundled",render:(e,t)=>t.meta?.bundled?'<span class="badge badge-secondary">bundled</span>':""},{key:"_actions",title:"Actions",render:(e,t)=>{const n=(l[t.slug]||[]).length>0,s=n?'data-tooltip="Unmap this menu before deleting"':"",d=n?"disabled":"",c=o(t.slug);return`
2
2
  <a class="btn btn-sm btn-ghost" href="#/menus/edit/${encodeURIComponent(t.slug)}" data-tooltip="Edit"><span data-icon="edit" data-icon-size="14"></span></a>
3
3
  <button class="btn btn-sm btn-ghost btn-duplicate" data-slug="${c}" data-tooltip="Duplicate"><span data-icon="copy" data-icon-size="14"></span></button>
4
- <button class="btn btn-sm btn-ghost btn-delete" data-slug="${c}" ${d} ${a}><span data-icon="trash" data-icon-size="14"></span></button>
5
- `}}]}),Domma.icons.scan("#menus-table"),f(s.get(0)),document.querySelectorAll("#menus-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),s.off("click",".btn-duplicate").on("click",".btn-duplicate",async function(){const e=$(this).data("slug");try{const t=await i.menus.duplicate(e);E.toast(`Duplicated as "${t.slug}".`,{type:"success"}),location.reload()}catch(t){E.toast(`Duplicate failed: ${t.message||t}`,{type:"error"})}}),s.off("click",".btn-delete").on("click",".btn-delete",async function(){if(this.disabled)return;const e=$(this).data("slug");if(await E.confirm(`Delete menu "${e}"? This cannot be undone.`))try{await i.menus.remove(e),E.toast("Menu deleted.",{type:"success"}),location.reload()}catch(n){E.toast(`Delete failed: ${n.message||n}`,{type:"error"})}})}};
4
+ <button class="btn btn-sm btn-ghost btn-delete" data-slug="${c}" ${d} ${s}><span data-icon="trash" data-icon-size="14"></span></button>
5
+ `}}]}),Domma.icons.scan("#menus-table"),f(a.get(0)),document.querySelectorAll("#menus-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),a.off("click",".btn-duplicate").on("click",".btn-duplicate",async function(){const e=$(this).data("slug");try{const t=await i.menus.duplicate(e);E.toast(`Duplicated as "${t.slug}".`,{type:"success"}),location.reload()}catch(t){E.toast(`Duplicate failed: ${t.message||t}`,{type:"error"})}}),a.off("click",".btn-delete").on("click",".btn-delete",async function(){if(this.disabled)return;const e=$(this).data("slug");if(await E.confirm(`Delete menu "${e}"? This cannot be undone.`))try{await i.menus.remove(e),E.toast("Menu deleted.",{type:"success"}),location.reload()}catch(n){E.toast(`Delete failed: ${n.message||n}`,{type:"error"})}})}};
@@ -1,9 +1,9 @@
1
- import{api as x}from"../api.js";let I=1;const u=t=>String(t||"").replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;");function S(t){const d=[];return(t||[]).forEach(l=>{const o=I++;d.push({_id:o,text:l.text||"",url:l.url||"",icon:l.icon||"",hidden:l.hidden||!1,bundled:l.bundled||!1,parentId:null}),(l.items||l.children||[]).forEach(a=>{d.push({_id:I++,text:a.text||"",url:a.url||"",icon:a.icon||"",hidden:a.hidden||!1,bundled:a.bundled||!1,parentId:o})})}),d}function F(t){return t.filter(d=>d.parentId===null).map(d=>{const l=t.filter(a=>a.parentId===d._id).map(a=>({text:a.text,url:a.url,...a.icon&&{icon:a.icon},...a.hidden&&{hidden:!0},...a.bundled&&{bundled:!0}})),o={text:d.text,url:d.url,...d.icon&&{icon:d.icon},...d.hidden&&{hidden:!0},...d.bundled&&{bundled:!0}};return l.length&&(o.items=l),o})}function A(t){const d=[];return t.filter(l=>l.parentId===null).forEach(l=>{d.push(l),t.filter(o=>o.parentId===l._id).forEach(o=>d.push(o))}),d}export const navigationView={templateUrl:"/admin/js/templates/navigation.html",async onMount(t){let[d,l]=await Promise.all([x.navigation.get().catch(()=>({brand:{},items:[]})),x.settings.get().catch(()=>({}))]),o=S(d.items),a=(l.footer?.links||[]).map(e=>({text:e.text||"",url:e.url||"",...e.hidden&&{hidden:!0}}));const m=()=>{t.find("#nav-items-list .nav-item-row").each(function(){const i=parseInt($(this).data("id"),10),n=o.find(f=>f._id===i);if(!n)return;n.text=$(this).find(".item-text").val(),n.url=$(this).find(".item-url").val(),n.icon=$(this).find(".item-icon").val(),n.hidden=$(this).find(".btn-toggle-hidden").attr("data-hidden")==="true",n.bundled=$(this).find(".btn-toggle-bundled").attr("data-bundled")==="true";const r=$(this).find(".item-parent").val();n.parentId=r?parseInt(r,10):null});const e=new Set(o.filter(i=>i.parentId!==null).map(i=>i._id));o.forEach(i=>{i.parentId!==null&&e.has(i.parentId)&&(i.parentId=null)})},p=()=>{const e=t.find("#nav-items-list").empty(),i=o.filter(n=>n.parentId===null);A(o).forEach(n=>{const r=n.parentId!==null,f=n.hidden===!0,g='<option value="">\u2014 top-level \u2014</option>'+i.filter(c=>c._id!==n._id).map(c=>`<option value="${c._id}"${c._id===n.parentId?" selected":""}>${u(c.text)||"(untitled)"}</option>`).join("");e.append(`
1
+ import{api as x}from"../api.js";let I=1;const v=t=>String(t||"").replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;");function S(t){const d=[];return(t||[]).forEach(l=>{const o=I++;d.push({_id:o,text:l.text||"",url:l.url||"",icon:l.icon||"",hidden:l.hidden||!1,bundled:l.bundled||!1,parentId:null}),(l.items||l.children||[]).forEach(a=>{d.push({_id:I++,text:a.text||"",url:a.url||"",icon:a.icon||"",hidden:a.hidden||!1,bundled:a.bundled||!1,parentId:o})})}),d}function F(t){return t.filter(d=>d.parentId===null).map(d=>{const l=t.filter(a=>a.parentId===d._id).map(a=>({text:a.text,url:a.url,...a.icon&&{icon:a.icon},...a.hidden&&{hidden:!0},...a.bundled&&{bundled:!0}})),o={text:d.text,url:d.url,...d.icon&&{icon:d.icon},...d.hidden&&{hidden:!0},...d.bundled&&{bundled:!0}};return l.length&&(o.items=l),o})}function A(t){const d=[];return t.filter(l=>l.parentId===null).forEach(l=>{d.push(l),t.filter(o=>o.parentId===l._id).forEach(o=>d.push(o))}),d}export const navigationView={templateUrl:"/admin/js/templates/navigation.html",async onMount(t){let[d,l]=await Promise.all([x.navigation.get().catch(()=>({brand:{},items:[]})),x.settings.get().catch(()=>({}))]),o=S(d.items),a=(l.footer?.links||[]).map(e=>({text:e.text||"",url:e.url||"",...e.hidden&&{hidden:!0}}));const m=()=>{t.find("#nav-items-list .nav-item-row").each(function(){const i=parseInt($(this).data("id"),10),n=o.find(f=>f._id===i);if(!n)return;n.text=$(this).find(".item-text").val(),n.url=$(this).find(".item-url").val(),n.icon=$(this).find(".item-icon").val(),n.hidden=$(this).find(".btn-toggle-hidden").attr("data-hidden")==="true",n.bundled=$(this).find(".btn-toggle-bundled").attr("data-bundled")==="true";const r=$(this).find(".item-parent").val();n.parentId=r?parseInt(r,10):null});const e=new Set(o.filter(i=>i.parentId!==null).map(i=>i._id));o.forEach(i=>{i.parentId!==null&&e.has(i.parentId)&&(i.parentId=null)})},p=()=>{const e=t.find("#nav-items-list").empty(),i=o.filter(n=>n.parentId===null);A(o).forEach(n=>{const r=n.parentId!==null,f=n.hidden===!0,g='<option value="">- top-level -</option>'+i.filter(c=>c._id!==n._id).map(c=>`<option value="${c._id}"${c._id===n.parentId?" selected":""}>${v(c.text)||"(untitled)"}</option>`).join("");e.append(`
2
2
  <div class="nav-item-row${r?" nav-item-row--child":""}${f?" nav-item-row--hidden":""}" data-id="${n._id}">
3
3
  <span class="nav-col-indent">${r?"\u21B3":""}</span>
4
- <input type="text" class="form-input item-text nav-col-main" value="${u(n.text)}" placeholder="Label">
5
- <input type="text" class="form-input item-url nav-col-main" value="${u(n.url)}" placeholder="/url">
6
- <input type="text" class="form-input item-icon nav-col-icon" value="${u(n.icon)}" placeholder="icon">
4
+ <input type="text" class="form-input item-text nav-col-main" value="${v(n.text)}" placeholder="Label">
5
+ <input type="text" class="form-input item-url nav-col-main" value="${v(n.url)}" placeholder="/url">
6
+ <input type="text" class="form-input item-icon nav-col-icon" value="${v(n.icon)}" placeholder="icon">
7
7
  <select class="form-select item-parent nav-col-parent">${g}</select>
8
8
  <span class="nav-col-action">
9
9
  <button class="btn btn-sm btn-ghost btn-toggle-bundled${n.bundled?" active":""}" data-id="${n._id}" data-bundled="${!!n.bundled}" data-tooltip="${n.bundled?"Unbundle":"Bundle (seed on install)"}"><span data-icon="package"></span></button>
@@ -11,13 +11,13 @@ import{api as x}from"../api.js";let I=1;const u=t=>String(t||"").replace(/&/g,"&
11
11
  <button class="btn btn-sm btn-ghost btn-remove-item" data-id="${n._id}" data-tooltip="Remove"><span data-icon="trash"></span></button>
12
12
  </span>
13
13
  </div>
14
- `)}),Domma.icons.scan("#nav-items-list"),document.querySelectorAll("#nav-items-list [data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})},v=()=>{const e=t.find("#footer-links-list").empty();a.forEach((i,n)=>{const r=i.hidden===!0;e.append(`
14
+ `)}),Domma.icons.scan("#nav-items-list"),document.querySelectorAll("#nav-items-list [data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})},u=()=>{const e=t.find("#footer-links-list").empty();a.forEach((i,n)=>{const r=i.hidden===!0;e.append(`
15
15
  <div class="nav-item-row${r?" nav-item-row--hidden":""}" data-footer-idx="${n}">
16
- <input type="text" class="form-input footer-link-text nav-col-main" value="${u(i.text)}" placeholder="Label">
17
- <input type="text" class="form-input footer-link-url nav-col-main" value="${u(i.url)}" placeholder="/url">
16
+ <input type="text" class="form-input footer-link-text nav-col-main" value="${v(i.text)}" placeholder="Label">
17
+ <input type="text" class="form-input footer-link-url nav-col-main" value="${v(i.url)}" placeholder="/url">
18
18
  <span class="nav-col-action">
19
19
  <button class="btn btn-sm btn-ghost btn-toggle-hidden${r?" active":""}" data-idx="${n}" data-hidden="${r}" data-tooltip="${r?"Show":"Hide"}"><span data-icon="eye-off"></span></button>
20
20
  <button class="btn btn-sm btn-ghost btn-remove-footer" data-idx="${n}" data-tooltip="Remove"><span data-icon="trash"></span></button>
21
21
  </span>
22
22
  </div>
23
- `)}),Domma.icons.scan("#footer-links-list"),document.querySelectorAll("#footer-links-list [data-tooltip]").forEach(i=>{E.tooltip(i,{content:i.getAttribute("data-tooltip"),position:"top"})})},h=()=>{a=[],t.find("#footer-links-list .nav-item-row").each(function(){const e=$(this).find(".btn-toggle-hidden").attr("data-hidden")==="true",i={text:$(this).find(".footer-link-text").val().trim(),url:$(this).find(".footer-link-url").val().trim()};e&&(i.hidden=!0),a.push(i)})};t.find("#add-footer-link").on("click",()=>{h(),a.push({text:"",url:"",hidden:!1}),v()}),t.off("click",".btn-remove-footer").on("click",".btn-remove-footer",function(){h();const e=parseInt($(this).data("idx"),10);a.splice(e,1),v()}),t.find("#field-brand-text").val(d.brand?.text||""),t.find("#field-brand-url").val(d.brand?.url||"/"),t.find("#field-brand-icon").val(d.brand?.icon||""),t.find("#field-nav-variant").val(d.variant||"dark"),t.find("#field-brand-tagline").val(d.brand?.tagline||""),t.find("#field-brand-logo").val(d.brand?.logo||""),t.find("#field-nav-position").val(d.position||"sticky"),t.find("#field-brand-size").val(d.brand?.size||"md"),t.find("#field-nav-font-family").val(d.style?.fontFamily||""),t.find("#field-nav-font-size").val(d.style?.fontSize||""),t.find("#field-nav-font-weight").val(d.style?.fontWeight||""),t.find("#field-nav-letter-spacing").val(d.style?.letterSpacing||""),t.find("#field-nav-icon-size").val(d.style?.iconSize||""),t.find("#field-admin-brand-title").val(l.adminBrand?.title||""),t.find("#field-admin-brand-icon").val(l.adminBrand?.icon||""),t.find("#field-admin-brand-logo").val(l.adminBrand?.logo||""),p(),v(),E.tabs(t.find("#nav-tabs").get(0)),t.find("#add-nav-item").on("click",()=>{m(),o.push({_id:I++,text:"",url:"",icon:"",hidden:!1,parentId:null}),p()}),t.off("click",".btn-remove-item").on("click",".btn-remove-item",function(){m();const e=parseInt($(this).data("id"),10);o=o.filter(i=>i._id!==e&&i.parentId!==e),p()}),t.off("click","#nav-items-list .btn-toggle-bundled").on("click","#nav-items-list .btn-toggle-bundled",function(e){e.stopPropagation(),m();const i=parseInt($(this).data("id"),10),n=o.find(r=>r._id===i);n&&(n.bundled=!n.bundled),p()}),t.off("click","#nav-items-list .btn-toggle-hidden").on("click","#nav-items-list .btn-toggle-hidden",function(e){e.stopPropagation(),m();const i=parseInt($(this).data("id"),10),n=o.find(r=>r._id===i);n&&(n.hidden=!n.hidden),p()}),t.off("click","#footer-links-list .btn-toggle-hidden").on("click","#footer-links-list .btn-toggle-hidden",function(e){e.stopPropagation(),h();const i=parseInt($(this).data("idx"),10);a[i]&&(a[i].hidden=!a[i].hidden),v()}),t.off("change",".item-parent").on("change",".item-parent",function(){m(),p()}),t.find("#save-nav-btn").on("click",async()=>{m(),h();const e=F(o.map(s=>({...s,text:s.text.trim(),url:s.url.trim(),icon:s.icon.trim()}))).filter(s=>s.text||s.url),i=t.find("#field-brand-icon").val().trim(),n=t.find("#field-brand-tagline").val().trim(),r=t.find("#field-brand-logo").val().trim(),f=t.find("#field-brand-size").val(),g={brand:{text:t.find("#field-brand-text").val().trim(),url:t.find("#field-brand-url").val().trim()||"/",...r&&{logo:r},...i&&!r&&{icon:i},...n&&{tagline:n},...f!=="md"&&{size:f}},items:e,variant:t.find("#field-nav-variant").val(),position:t.find("#field-nav-position").val()||"sticky",style:{...t.find("#field-nav-font-family").val()&&{fontFamily:t.find("#field-nav-font-family").val()},...t.find("#field-nav-font-size").val()&&{fontSize:t.find("#field-nav-font-size").val()},...t.find("#field-nav-font-weight").val()&&{fontWeight:t.find("#field-nav-font-weight").val()},...t.find("#field-nav-letter-spacing").val()&&{letterSpacing:t.find("#field-nav-letter-spacing").val()},...t.find("#field-nav-icon-size").val()&&{iconSize:t.find("#field-nav-icon-size").val()}}},c=a.filter(s=>s.text||s.url);try{await x.navigation.save(g),d=g,o=S(d.items),p(),E.toast("Navigation saved.",{type:"success"})}catch(s){console.error("[navigation] save failed:",s),E.toast("Failed to save navigation.",{type:"error"});return}const y=t.find("#field-admin-brand-logo").val().trim(),k=t.find("#field-admin-brand-icon").val().trim(),_=t.find("#field-admin-brand-title").val().trim(),b={..._&&{title:_},...y&&{logo:y},...!y&&k&&{icon:k}};try{await x.settings.save({adminBrand:b,footer:{links:c}}),l={...l,adminBrand:b},a=c,v();const s=b.title||"CMS Admin",B=b.icon||"layout",w=b.logo||"",z=$("#admin-topbar .topbar-brand");w?z.html(`<img src="${w}" class="topbar-brand-logo" alt="${s}"><span class="topbar-brand-text">${s}</span>`):z.html(`<span data-icon="${B}"></span><span class="topbar-brand-text">${s}</span>`),Domma.icons.scan("#admin-topbar .topbar-brand")}catch(s){console.error("[navigation] admin brand / footer save failed:",s),E.toast(`Admin brand save failed: ${s.message||s}`,{type:"error"})}})}};
23
+ `)}),Domma.icons.scan("#footer-links-list"),document.querySelectorAll("#footer-links-list [data-tooltip]").forEach(i=>{E.tooltip(i,{content:i.getAttribute("data-tooltip"),position:"top"})})},h=()=>{a=[],t.find("#footer-links-list .nav-item-row").each(function(){const e=$(this).find(".btn-toggle-hidden").attr("data-hidden")==="true",i={text:$(this).find(".footer-link-text").val().trim(),url:$(this).find(".footer-link-url").val().trim()};e&&(i.hidden=!0),a.push(i)})};t.find("#add-footer-link").on("click",()=>{h(),a.push({text:"",url:"",hidden:!1}),u()}),t.off("click",".btn-remove-footer").on("click",".btn-remove-footer",function(){h();const e=parseInt($(this).data("idx"),10);a.splice(e,1),u()}),t.find("#field-brand-text").val(d.brand?.text||""),t.find("#field-brand-url").val(d.brand?.url||"/"),t.find("#field-brand-icon").val(d.brand?.icon||""),t.find("#field-nav-variant").val(d.variant||"dark"),t.find("#field-brand-tagline").val(d.brand?.tagline||""),t.find("#field-brand-logo").val(d.brand?.logo||""),t.find("#field-nav-position").val(d.position||"sticky"),t.find("#field-brand-size").val(d.brand?.size||"md"),t.find("#field-nav-font-family").val(d.style?.fontFamily||""),t.find("#field-nav-font-size").val(d.style?.fontSize||""),t.find("#field-nav-font-weight").val(d.style?.fontWeight||""),t.find("#field-nav-letter-spacing").val(d.style?.letterSpacing||""),t.find("#field-nav-icon-size").val(d.style?.iconSize||""),t.find("#field-admin-brand-title").val(l.adminBrand?.title||""),t.find("#field-admin-brand-icon").val(l.adminBrand?.icon||""),t.find("#field-admin-brand-logo").val(l.adminBrand?.logo||""),p(),u(),E.tabs(t.find("#nav-tabs").get(0)),t.find("#add-nav-item").on("click",()=>{m(),o.push({_id:I++,text:"",url:"",icon:"",hidden:!1,parentId:null}),p()}),t.off("click",".btn-remove-item").on("click",".btn-remove-item",function(){m();const e=parseInt($(this).data("id"),10);o=o.filter(i=>i._id!==e&&i.parentId!==e),p()}),t.off("click","#nav-items-list .btn-toggle-bundled").on("click","#nav-items-list .btn-toggle-bundled",function(e){e.stopPropagation(),m();const i=parseInt($(this).data("id"),10),n=o.find(r=>r._id===i);n&&(n.bundled=!n.bundled),p()}),t.off("click","#nav-items-list .btn-toggle-hidden").on("click","#nav-items-list .btn-toggle-hidden",function(e){e.stopPropagation(),m();const i=parseInt($(this).data("id"),10),n=o.find(r=>r._id===i);n&&(n.hidden=!n.hidden),p()}),t.off("click","#footer-links-list .btn-toggle-hidden").on("click","#footer-links-list .btn-toggle-hidden",function(e){e.stopPropagation(),h();const i=parseInt($(this).data("idx"),10);a[i]&&(a[i].hidden=!a[i].hidden),u()}),t.off("change",".item-parent").on("change",".item-parent",function(){m(),p()}),t.find("#save-nav-btn").on("click",async()=>{m(),h();const e=F(o.map(s=>({...s,text:s.text.trim(),url:s.url.trim(),icon:s.icon.trim()}))).filter(s=>s.text||s.url),i=t.find("#field-brand-icon").val().trim(),n=t.find("#field-brand-tagline").val().trim(),r=t.find("#field-brand-logo").val().trim(),f=t.find("#field-brand-size").val(),g={brand:{text:t.find("#field-brand-text").val().trim(),url:t.find("#field-brand-url").val().trim()||"/",...r&&{logo:r},...i&&!r&&{icon:i},...n&&{tagline:n},...f!=="md"&&{size:f}},items:e,variant:t.find("#field-nav-variant").val(),position:t.find("#field-nav-position").val()||"sticky",style:{...t.find("#field-nav-font-family").val()&&{fontFamily:t.find("#field-nav-font-family").val()},...t.find("#field-nav-font-size").val()&&{fontSize:t.find("#field-nav-font-size").val()},...t.find("#field-nav-font-weight").val()&&{fontWeight:t.find("#field-nav-font-weight").val()},...t.find("#field-nav-letter-spacing").val()&&{letterSpacing:t.find("#field-nav-letter-spacing").val()},...t.find("#field-nav-icon-size").val()&&{iconSize:t.find("#field-nav-icon-size").val()}}},c=a.filter(s=>s.text||s.url);try{await x.navigation.save(g),d=g,o=S(d.items),p(),E.toast("Navigation saved.",{type:"success"})}catch(s){console.error("[navigation] save failed:",s),E.toast("Failed to save navigation.",{type:"error"});return}const y=t.find("#field-admin-brand-logo").val().trim(),k=t.find("#field-admin-brand-icon").val().trim(),_=t.find("#field-admin-brand-title").val().trim(),b={..._&&{title:_},...y&&{logo:y},...!y&&k&&{icon:k}};try{await x.settings.save({adminBrand:b,footer:{links:c}}),l={...l,adminBrand:b},a=c,u();const s=b.title||"CMS Admin",B=b.icon||"layout",w=b.logo||"",z=$("#admin-topbar .topbar-brand");w?z.html(`<img src="${w}" class="topbar-brand-logo" alt="${s}"><span class="topbar-brand-text">${s}</span>`):z.html(`<span data-icon="${B}"></span><span class="topbar-brand-text">${s}</span>`),Domma.icons.scan("#admin-topbar .topbar-brand")}catch(s){console.error("[navigation] admin brand / footer save failed:",s),E.toast(`Admin brand save failed: ${s.message||s}`,{type:"error"})}})}};