domma-cms 0.10.0 → 0.12.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 (119) hide show
  1. package/CLAUDE.md +248 -159
  2. package/admin/css/admin.css +1 -1
  3. package/admin/js/api.js +1 -1
  4. package/admin/js/app.js +7 -3
  5. package/admin/js/config/sidebar-config.js +1 -1
  6. package/admin/js/http-interceptor.js +1 -0
  7. package/admin/js/lib/safe-html.js +1 -0
  8. package/admin/js/templates/layouts.html +5 -4
  9. package/admin/js/templates/notifications.html +14 -0
  10. package/admin/js/templates/plugin-marketplace.html +16 -0
  11. package/admin/js/templates/plugins.html +17 -5
  12. package/admin/js/views/index.js +1 -1
  13. package/admin/js/views/layouts.js +1 -16
  14. package/admin/js/views/notifications.js +1 -0
  15. package/admin/js/views/plugin-marketplace.js +1 -0
  16. package/admin/js/views/plugins.js +16 -16
  17. package/config/navigation.json +5 -72
  18. package/config/plugins.json +10 -14
  19. package/config/presets.json +50 -13
  20. package/config/site.json +11 -63
  21. package/package.json +2 -1
  22. package/plugins/_template/admin/templates/index.html +17 -0
  23. package/plugins/_template/admin/views/index.js +19 -0
  24. package/plugins/_template/config.js +8 -0
  25. package/plugins/_template/plugin.js +23 -0
  26. package/plugins/_template/plugin.json +34 -0
  27. package/plugins/analytics/plugin.json +41 -31
  28. package/plugins/blog/admin/templates/blog.html +22 -0
  29. package/plugins/blog/admin/templates/categories.html +7 -0
  30. package/plugins/blog/admin/templates/comments.html +11 -0
  31. package/plugins/blog/admin/templates/post-editor.html +97 -0
  32. package/plugins/blog/admin/templates/settings.html +11 -0
  33. package/plugins/blog/admin/views/blog.js +183 -0
  34. package/plugins/blog/admin/views/categories.js +235 -0
  35. package/plugins/blog/admin/views/comments.js +187 -0
  36. package/plugins/blog/admin/views/post-editor.js +291 -0
  37. package/plugins/blog/admin/views/settings.js +100 -0
  38. package/plugins/blog/collections/categories/schema.json +12 -0
  39. package/plugins/blog/collections/comments/schema.json +16 -0
  40. package/plugins/blog/collections/posts/schema.json +19 -0
  41. package/plugins/blog/config.js +8 -0
  42. package/plugins/blog/plugin.js +352 -0
  43. package/plugins/blog/plugin.json +96 -0
  44. package/plugins/blog/roles/blog-author.json +10 -0
  45. package/plugins/blog/roles/blog-editor.json +12 -0
  46. package/plugins/blog/templates/author.html +9 -0
  47. package/plugins/blog/templates/category.html +9 -0
  48. package/plugins/blog/templates/index.html +9 -0
  49. package/plugins/blog/templates/post.html +17 -0
  50. package/plugins/blog/templates/tag.html +9 -0
  51. package/plugins/contacts/collections/user-contact-groups/schema.json +1 -1
  52. package/plugins/contacts/collections/user-contacts/schema.json +1 -1
  53. package/plugins/contacts/plugin.js +4 -10
  54. package/plugins/contacts/plugin.json +13 -3
  55. package/plugins/notes/collections/user-notes/schema.json +1 -1
  56. package/plugins/notes/plugin.js +3 -9
  57. package/plugins/notes/plugin.json +13 -3
  58. package/plugins/site-search/plugin.json +5 -2
  59. package/plugins/theme-switcher/plugin.json +1 -1
  60. package/plugins/todo/collections/todos/schema.json +1 -1
  61. package/plugins/todo/plugin.js +3 -9
  62. package/plugins/todo/plugin.json +13 -3
  63. package/public/css/site.css +1 -1
  64. package/scripts/build.js +48 -0
  65. package/scripts/create-plugin.js +113 -0
  66. package/scripts/fresh.js +6 -7
  67. package/scripts/gen-instance-secret.js +46 -0
  68. package/scripts/reset.js +3 -3
  69. package/scripts/setup.js +31 -13
  70. package/server/middleware/auth.js +48 -0
  71. package/server/middleware/managerAuth.js +36 -0
  72. package/server/routes/api/actions.js +1 -1
  73. package/server/routes/api/auth.js +4 -3
  74. package/server/routes/api/layouts.js +173 -49
  75. package/server/routes/api/notifications.js +155 -0
  76. package/server/routes/api/plugin-marketplace.js +75 -0
  77. package/server/routes/api/users.js +1 -1
  78. package/server/routes/api/views.js +1 -1
  79. package/server/routes/public.js +4 -9
  80. package/server/server.js +32 -3
  81. package/server/services/actions.js +1 -1
  82. package/server/services/managerClient.js +182 -0
  83. package/server/services/permissionRegistry.js +245 -173
  84. package/server/services/pluginInstaller.js +301 -0
  85. package/server/services/plugins.js +117 -10
  86. package/server/services/presetCollections.js +66 -251
  87. package/server/services/renderer.js +99 -0
  88. package/server/services/roles.js +191 -39
  89. package/server/services/users.js +1 -1
  90. package/server/services/views.js +1 -1
  91. package/server/templates/page.html +2 -2
  92. package/plugins/docs/admin/templates/docs.html +0 -69
  93. package/plugins/docs/admin/views/docs.js +0 -276
  94. package/plugins/docs/config.js +0 -8
  95. package/plugins/docs/data/documents/57e003f0-68f2-47dc-9c36-ed4b10ed3deb.json +0 -11
  96. package/plugins/docs/data/folders.json +0 -9
  97. package/plugins/docs/data/templates.json +0 -1
  98. package/plugins/docs/data/versions/57e003f0-68f2-47dc-9c36-ed4b10ed3deb/1.json +0 -5
  99. package/plugins/docs/plugin.js +0 -375
  100. package/plugins/docs/plugin.json +0 -23
  101. package/plugins/form-builder/data/forms/contacts.json +0 -66
  102. package/plugins/form-builder/data/forms/enquiries.json +0 -103
  103. package/plugins/form-builder/data/forms/feedback.json +0 -131
  104. package/plugins/form-builder/data/forms/notes.json +0 -79
  105. package/plugins/form-builder/data/forms/to-do.json +0 -100
  106. package/plugins/form-builder/data/submissions/contacts.json +0 -1
  107. package/plugins/form-builder/data/submissions/enquiries.json +0 -1
  108. package/plugins/form-builder/data/submissions/feedback.json +0 -1
  109. package/plugins/form-builder/data/submissions/notes.json +0 -1
  110. package/plugins/form-builder/data/submissions/to-do.json +0 -1
  111. package/plugins/garage/admin/templates/garage.html +0 -111
  112. package/plugins/garage/admin/views/garage.js +0 -622
  113. package/plugins/garage/collections/garage-vehicles/schema.json +0 -101
  114. package/plugins/garage/config.js +0 -18
  115. package/plugins/garage/data/vehicles.json +0 -70
  116. package/plugins/garage/plugin.js +0 -398
  117. package/plugins/garage/plugin.json +0 -33
  118. package/scripts/seed.js +0 -1996
  119. package/server/services/userTypes.js +0 -227
package/admin/js/app.js CHANGED
@@ -1,7 +1,11 @@
1
- import{getSidebarConfig as Y}from"./config/sidebar-config.js";import{views as q}from"./views/index.js?v=4";import{api as n,getUser as m,isAuthenticated as c,logout as z}from"./api.js";$(()=>{(async()=>{try{const t=c()?await n.settings.get():null;Domma.theme.init({theme:t?.adminTheme||"charcoal-dark",persist:!0})}catch{Domma.theme.init({theme:"charcoal-dark",persist:!0})}})();const N=["jb-company","jb-agent","jb-candidate"],W=["/job-board","/my-profile"];function b(t){return t&&N.includes(t.role)}function w(t){return W.some(e=>t===e||t.startsWith(e+"/"))}R.use(async(t,e,a)=>{if(t.path==="/login"||t.path==="/reset-password")return a();if(!c()){R.navigate("/login");return}if(b(m())&&!w(t.path)){R.navigate("/job-board");return}a()});let p=null;async function I(){if(!c())return{};try{const[t,e,a,i,s,o,h,g,u,C,r,d,F]=await Promise.all([n.pages.list().catch(()=>[]),n.media.list().catch(()=>[]),n.users.list().catch(()=>[]),n.plugins.list().catch(()=>[]),n.collections.list().catch(()=>[]),n.forms.list().catch(()=>[]),n.themes.list().catch(()=>[]),n.views.list().catch(()=>[]),n.actions.list().catch(()=>[]),n.blocks.list().catch(()=>[]),n.navigation.get().catch(()=>({})),n.layouts.get().catch(()=>({})),n.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]}))]),T=r.items||[],J=T.filter(D=>!D.hidden).length,L=T.length,H=i.filter(D=>D.enabled).length,B=i.length;return{pages:t.length,media:e.length,users:a.length,plugins:B>0?`${H}/${B}`:null,collections:s.length,forms:o.length,themes:h.length,views:g.length,actions:u.length,blocks:C.length,navigation:L>0?`${J}/${L}`:null,layouts:Object.keys(d).length,roles:(F.entries||[]).length}}catch{return{}}}async function y(){try{return(await n.get("/auth/permissions")).permissions||[]}catch{return[]}}async function V(){try{return await n.get("/settings/db-status")}catch{return{configured:!1}}}async function k(t){p&&$("#admin-sidebar").empty();const[e,a,i]=await Promise.all([I(),V(),c()?n.settings.get().catch(()=>({})):Promise.resolve({})]),s=i.adminBrand||{},o=s.title||"CMS Admin",h=s.icon||"layout",g=s.logo||"",u=$("#admin-topbar .topbar-brand");g?u.html(`<img src="${l(g)}" class="topbar-brand-logo" alt="${l(o)}"><span class="topbar-brand-text">${l(o)}</span>`):u.html(`<span data-icon="${l(h)}"></span><span class="topbar-brand-text">${l(o)}</span>`),Domma.icons.scan("#admin-topbar .topbar-brand");const C=A.map(r=>{if(!r.countKey)return r;const d=e[r.countKey];return{...r,badge:d!=null&&d>0?String(d):null}});if(p=Domma.elements.sidebar("#admin-sidebar",{header:{title:o,icon:h},items:Y(t,e,C),collapsible:!0,collapseAt:992,push:!0,contentSelector:".dashboard-main",top:"60px"}),K(),O(),a?.configured){const r=$("#admin-sidebar").find(".sidebar-header-title").first();r.length&&!r.find(".db-badge").length&&r.append('<span class="db-badge" title="MongoDB connected" style="display:inline-flex;align-items:center;background:#16a34a;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:9999px;letter-spacing:.5px;margin-left:6px;vertical-align:middle;line-height:1;">DB</span>')}}function K(){$("#admin-sidebar .sidebar-link").each(function(){const t=$(this).find(".sidebar-text").text().trim();t&&E.tooltip(this,{content:t,position:"right"})})}function O(){const t="sidebar_collapsed_sections",e=new Set(S.get(t)||[]);$("#admin-sidebar .sidebar-heading").each(function(){const a=$(this);a.addClass("sidebar-heading--collapsible"),a.append('<span class="sidebar-heading-toggle"><span data-icon="chevron-down"></span></span>');const i=a.text().trim();if(e.has(i)){a.addClass("is-collapsed");let s=a.next();for(;s.length&&!s.hasClass("sidebar-heading")&&!s.hasClass("sidebar-divider");)s.hide(),s=s.next()}a.on("click",function(){const s=!a.hasClass("is-collapsed");a.toggleClass("is-collapsed",s);let o=a.next();for(;o.length&&!o.hasClass("sidebar-heading")&&!o.hasClass("sidebar-divider");)o.toggle(!s),o=o.next();s?e.add(i):e.delete(i),S.set(t,[...e])})}),Domma.icons.scan("#admin-sidebar")}M.subscribe("router:afterChange",({to:t})=>{p&&t.path!=="/login"&&t.path!=="/reset-password"&&p.setActive("#"+t.path)});const U=[{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:"/navigation",view:"navigation",title:"Navigation - 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:"/documentation",view:"documentation",title:"Usage - Domma CMS"},{path:"/tutorials",view:"tutorials",title:"Tutorials - 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:"/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:"/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(b(m())&&!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 j();const a=await y();k(a)}_()}}),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 x="cms_card_states",f=S.get(x)||{};M.subscribe("router:afterChange",()=>{setTimeout(()=>{$("#view-container .card-collapsible").each(function(){const t=$(this).find(".card-header h2, .card-header h3").first().text().trim();t&&f[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&&(f[a]=e.hasClass("card-collapsed")?"collapsed":"open",S.set(x,f))}),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 P={...q},v=[...U];let A=[];async function j(){if(c())try{const t=await n.plugins.adminConfig();A=t.sidebar||[],t.routes?.length&&v.push(...t.routes);for(const[e,a]of Object.entries(t.views||{}))try{const i=await import(`/plugins/${a.entry}`);P[e]=i[a.exportName]}catch{}}catch{}}function _(){const t=m();if(!t||$("#topbar-user-name").length)return;const a={admin:"Admin",manager:"Manager",editor:"Editor",subscriber:"Subscriber","jb-company":"Company","jb-agent":"Agent","jb-candidate":"Candidate"}[t.role]||t.role;$("#topbar-user").html(`
1
+ import{getSidebarConfig as G}from"./config/sidebar-config.js";import{views as Q}from"./views/index.js?v=4";import{api as o,getUser as u,isAuthenticated as c,logout as X}from"./api.js";import{installHttpInterceptor as Z}from"./http-interceptor.js";$(()=>{Z(),(async()=>{try{const t=c()?await o.settings.get():null;Domma.theme.init({theme:t?.adminTheme||"charcoal-dark",persist:!0})}catch{Domma.theme.init({theme:"charcoal-dark",persist:!0})}})();const W=["jb-company","jb-agent","jb-candidate"],U=["/job-board","/my-profile"];function f(t){return t&&W.includes(t.role)}function w(t){return U.some(e=>t===e||t.startsWith(e+"/"))}R.use(async(t,e,i)=>{if(t.path==="/login"||t.path==="/reset-password")return i();if(!c()){R.navigate("/login");return}if(f(u())&&!w(t.path)){R.navigate("/job-board");return}i()});let g=null;async function V(){if(!c())return{};try{const[t,e,i,a,n,s,d,p,b,y,r,h,J,q]=await Promise.all([o.pages.list().catch(()=>[]),o.media.list().catch(()=>[]),o.users.list().catch(()=>[]),o.plugins.list().catch(()=>[]),o.collections.list().catch(()=>[]),o.forms.list().catch(()=>[]),o.themes.list().catch(()=>[]),o.views.list().catch(()=>[]),o.actions.list().catch(()=>[]),o.blocks.list().catch(()=>[]),o.navigation.get().catch(()=>({})),o.layouts.get().catch(()=>({})),o.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]})),o.system.notifications.unreadCount().catch(()=>({count:0}))]),T=r.items||[],Y=T.filter(D=>!D.hidden).length,L=T.length,z=a.filter(D=>D.enabled).length,j=a.length;return{pages:t.length,media:e.length,users:i.length,plugins:j>0?`${z}/${j}`:null,collections:n.length,forms:s.length,themes:d.length,views:p.length,actions:b.length,blocks:y.length,navigation:L>0?`${Y}/${L}`:null,layouts:Object.keys(h).length,roles:(J.entries||[]).length,unreadNotifications:q.count||0}}catch{return{}}}async function k(){try{return(await o.get("/auth/permissions")).permissions||[]}catch{return[]}}async function K(){try{return await o.get("/settings/db-status")}catch{return{configured:!1}}}async function x(t){g&&$("#admin-sidebar").empty();const[e,i,a]=await Promise.all([V(),K(),c()?o.settings.get().catch(()=>({})):Promise.resolve({})]),n=a.adminBrand||{},s=n.title||"CMS Admin",d=n.icon||"layout",p=n.logo||"",b=$("#admin-topbar .topbar-brand");p?b.html(`<img src="${l(p)}" class="topbar-brand-logo" alt="${l(s)}"><span class="topbar-brand-text">${l(s)}</span>`):b.html(`<span data-icon="${l(d)}"></span><span class="topbar-brand-text">${l(s)}</span>`),Domma.icons.scan("#admin-topbar .topbar-brand");const y=N.map(r=>{if(!r.countKey)return r;const h=e[r.countKey];return{...r,badge:h!=null&&h>0?String(h):null}});if(g=Domma.elements.sidebar("#admin-sidebar",{header:{title:s,icon:d},items:G(t,e,y),collapsible:!0,collapseAt:992,push:!0,contentSelector:".dashboard-main",top:"60px"}),O(),F(),i?.configured){const r=$("#admin-sidebar").find(".sidebar-header-title").first();r.length&&!r.find(".db-badge").length&&r.append('<span class="db-badge" title="MongoDB connected" style="display:inline-flex;align-items:center;background:#16a34a;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:9999px;letter-spacing:.5px;margin-left:6px;vertical-align:middle;line-height:1;">DB</span>')}}function O(){$("#admin-sidebar .sidebar-link").each(function(){const t=$(this).find(".sidebar-text").text().trim();t&&E.tooltip(this,{content:t,position:"right"})})}function F(){const t="sidebar_collapsed_sections",e=new Set(S.get(t)||[]);$("#admin-sidebar .sidebar-heading").each(function(){const i=$(this);i.addClass("sidebar-heading--collapsible"),i.append('<span class="sidebar-heading-toggle"><span data-icon="chevron-down"></span></span>');const a=i.text().trim();if(e.has(a)){i.addClass("is-collapsed");let n=i.next();for(;n.length&&!n.hasClass("sidebar-heading")&&!n.hasClass("sidebar-divider");)n.hide(),n=n.next()}i.on("click",function(){const n=!i.hasClass("is-collapsed");i.toggleClass("is-collapsed",n);let s=i.next();for(;s.length&&!s.hasClass("sidebar-heading")&&!s.hasClass("sidebar-divider");)s.toggle(!n),s=s.next();n?e.add(a):e.delete(a),S.set(t,[...e])})}),Domma.icons.scan("#admin-sidebar")}M.subscribe("router:afterChange",({to:t})=>{(t.path==="/login"||t.path==="/reset-password")&&m&&(clearInterval(m),m=null),g&&t.path!=="/login"&&t.path!=="/reset-password"&&g.setActive("#"+t.path)});const H=[{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:"/navigation",view:"navigation",title:"Navigation - 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:"/documentation",view:"documentation",title:"Usage - Domma CMS"},{path:"/tutorials",view:"tutorials",title:"Tutorials - 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:"/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:"/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(f(u())&&!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 _();const i=await k();x(i);try{const a=await o.system.notifications.list().catch(()=>[]),n=(Array.isArray(a)?a:[]).filter(s=>s.unread&&["warning","critical"].includes(s.data?.severity)).slice(0,3);for(const s of n){const d=l(s.data?.title||""),p=l((s.data?.body||"").slice(0,120));E.toast(`${d} \u2014 ${p}`,{type:s.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}B()}}),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 P="cms_card_states",v=S.get(P)||{};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&&v[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 i=$(this).find("h2, h3").first().text().trim();i&&(v[i]=e.hasClass("card-collapsed")?"collapsed":"open",S.set(P,v))}),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 A={...Q},C=[...H];let N=[];async function _(){if(c())try{const t=await o.plugins.adminConfig();N=t.sidebar||[],t.routes?.length&&C.push(...t.routes);for(const[e,i]of Object.entries(t.views||{}))try{const a=await import(`/plugins/${i.entry}`);A[e]=a[i.exportName]}catch{}for(const{id:e,href:i}of t.css||[])if(!document.getElementById(e)){const a=document.createElement("link");a.id=e,a.rel="stylesheet",a.href=i,document.head.appendChild(a)}}catch{}}function B(){const t=u();if(!t||$("#topbar-user-name").length)return;const i={"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">${l(t.name)}</span>
3
- <span class="topbar-role-badge topbar-role-badge--${l(t.role)}">${a}</span>
3
+ <span class="topbar-role-badge topbar-role-badge--${l(t.role)}">${i}</span>
4
4
  `),$("#topbar-actions").html(`
5
+ <a href="#/system/notifications" id="topbar-bell" class="topbar-action-link" data-tooltip="Notifications" data-tooltip-placement="bottom" hidden>
6
+ <span data-icon="bell"></span>
7
+ <span id="topbar-bell-badge" class="topbar-bell-badge" hidden></span>
8
+ </a>
5
9
  <a href="#/my-profile" class="topbar-action-link" data-tooltip="My Profile" data-tooltip-placement="bottom">
6
10
  <span data-icon="user"></span>
7
11
  <span>My Profile</span>
@@ -14,4 +18,4 @@ import{getSidebarConfig as Y}from"./config/sidebar-config.js";import{views as q}
14
18
  <span data-icon="log-out"></span>
15
19
  <span>Sign out</span>
16
20
  </a>
17
- `),$("#topbar-logout-btn").on("click",i=>{i.preventDefault(),z()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"})}function l(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}(async()=>{if(!c()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash="#/login";else{const i=(window.location.hash||"#/").slice(1)||"/";if(b(m())&&!w(i)&&(window.location.hash="#/job-board"),await j(),m()){const o=await y();await k(o),_()}}R.init({container:"#view-container",routes:v,views:P,default:"/",transitions:{enter:"fadeIn",leave:"fadeOut",duration:150}});const t=R._extractParams.bind(R);R._extractParams=function(i,s){if(i.endsWith("/*")){const o=i.slice(0,-2);return s.startsWith(o+"/")?{}: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",a=>{a.preventDefault(),X()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"}),I(),m||(m=setInterval(I,6e4))}function l(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}let m=null;async function I(){if(c())try{const{count:t}=await o.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(!c()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash="#/login";else{const a=(window.location.hash||"#/").slice(1)||"/";if(f(u())&&!w(a)&&(window.location.hash="#/job-board"),await _(),u()){const s=await k();await x(s),B()}}R.init({container:"#view-container",routes:C,views:A,default:"/",transitions:{enter:"fadeIn",leave:"fadeOut",duration:150}});const t=R._extractParams.bind(R);R._extractParams=function(a,n){if(a.endsWith("/*")){const s=a.slice(0,-2);return n.startsWith(s+"/")?{}:null}return t(a,n)};const e=(window.location.hash||"#/").slice(1)||"/";C.filter(a=>a.path.endsWith("/*")).some(a=>e.startsWith(a.path.slice(0,-2)+"/"))&&R._handleRouteChange()})()});
@@ -1 +1 @@
1
- export function getSidebarConfig(o=[],t={},r=[]){const e=n=>o.includes(n)||["read","create","update","delete"].some(l=>o.includes(`${n}.${l}`)),s=n=>n==null?null:typeof n=="string"?n.length>0?n:null:n>0?String(n):null,i=[],u=o.length>0&&o.every(n=>n.startsWith("jb-"));u||i.push({heading:"Overview"},{id:"dashboard",text:"Dashboard",icon:"home",url:"#/",section:"#/"},{divider:!0}),(e("navigation")||e("layouts"))&&i.push({heading:"Structure"},...e("navigation")?[{id:"navigation",text:"Navigation",icon:"menu",url:"#/navigation",section:"#/navigation",badge:s(t.navigation)}]:[],...e("layouts")?[{id:"layouts",text:"Layouts",icon:"layout",url:"#/layouts",section:"#/layouts",badge:s(t.layouts)}]:[],{divider:!0}),(e("pages")||e("media"))&&(i.push({heading:"Content"}),e("pages")&&i.push({id:"pages",text:"Pages",icon:"file-text",url:"#/pages",section:"#/pages",badge:s(t.pages)}),e("media")&&i.push({id:"media",text:"Media",icon:"image",url:"#/media",section:"#/media",badge:s(t.media)})),(e("collections")||e("views")||e("actions"))&&(i.push({divider:!0},{heading:"Data"}),e("collections")&&(i.push({id:"collections",text:"Collections",icon:"database",url:"#/collections",section:"#/collections",badge:s(t.collections)}),i.push({id:"forms",text:"Forms",icon:"layout",url:"#/forms",section:"#/forms",badge:s(t.forms)})),e("views")&&i.push({id:"views",text:"Views",icon:"eye",url:"#/views",section:"#/views",badge:s(t.views)}),e("actions")&&i.push({id:"actions",text:"Actions",icon:"zap",url:"#/actions",section:"#/actions",badge:s(t.actions)}),e("pages")&&i.push({id:"blocks",text:"Blocks",icon:"box",url:"#/blocks",section:"#/blocks",badge:s(t.blocks)})),(e("plugins")||e("users")||e("settings"))&&(i.push({divider:!0},{heading:"Configuration"}),e("plugins")&&i.push({id:"roles",text:"Roles",icon:"shield",url:"#/roles",section:"#/roles",badge:s(t.roles)}),e("users")&&i.push({id:"users",text:"Users",icon:"users",url:"#/users",section:"#/users",badge:s(t.users)}),e("settings")&&(i.push({id:"settings",text:"Site Settings",icon:"settings",url:"#/settings",section:"#/settings"}),i.push({id:"effects",text:"Effects",icon:"sparkles",url:"#/effects",section:"#/effects"})));const a=r.filter(n=>n.permissions?.length?n.permissions.some(l=>o.includes(l)):!0);return e("plugins")?i.push({divider:!0},{heading:"Plugins"},{id:"plugins",text:"Plugins",icon:"package",url:"#/plugins",section:"#/plugins",badge:s(t.plugins)},...a):a.length&&i.push({divider:!0},...a),i.push({divider:!0},{heading:"Account"},{id:"my-profile",text:"My Profile",icon:"user",url:"#/my-profile",section:"#/my-profile"}),u||i.push({divider:!0},{heading:"View Site"},{id:"view-site",text:"View Site",icon:"external-link",url:"/",section:"/"}),(e("pages")||e("collections")||e("plugins")||e("settings"))&&i.push({divider:!0},{heading:"Documentation"},{id:"documentation",text:"Usage",icon:"book",url:"#/documentation",section:"#/documentation",badge:s(t.documents)},{id:"tutorials",text:"Tutorials",icon:"document",url:"#/tutorials",section:"#/documentation",badge:s(t.tutorials)},{id:"api-reference",text:"API Reference",icon:"code",url:"#/api-reference",section:"#/documentation"}),i}
1
+ export function getSidebarConfig(n=[],t={},u=[]){const i=o=>n.includes(o)||["read","create","update","delete"].some(a=>n.includes(`${o}.${a}`)),s=o=>o==null?null:typeof o=="string"?o.length>0?o:null:o>0?String(o):null,e=[];e.push({heading:"Overview"},{id:"dashboard",text:"Dashboard",icon:"home",url:"#/",section:"#/"});const l=u.filter(o=>o.permissions?.length?o.permissions.some(a=>n.includes(a)):!0);return l.length&&e.push(...l),e.push({divider:!0}),(i("navigation")||i("layouts"))&&e.push({heading:"Structure"},...i("navigation")?[{id:"navigation",text:"Navigation",icon:"menu",url:"#/navigation",section:"#/navigation",badge:s(t.navigation)}]:[],...i("layouts")?[{id:"layouts",text:"Layouts",icon:"layout",url:"#/layouts",section:"#/layouts",badge:s(t.layouts)}]:[],{divider:!0}),(i("pages")||i("media"))&&(e.push({heading:"Content"}),i("pages")&&e.push({id:"pages",text:"Pages",icon:"file-text",url:"#/pages",section:"#/pages",badge:s(t.pages)}),i("media")&&e.push({id:"media",text:"Media",icon:"image",url:"#/media",section:"#/media",badge:s(t.media)})),(i("collections")||i("views")||i("actions"))&&(e.push({divider:!0},{heading:"Data"}),i("collections")&&(e.push({id:"collections",text:"Collections",icon:"database",url:"#/collections",section:"#/collections",badge:s(t.collections)}),e.push({id:"forms",text:"Forms",icon:"layout",url:"#/forms",section:"#/forms",badge:s(t.forms)})),i("views")&&e.push({id:"views",text:"Views",icon:"eye",url:"#/views",section:"#/views",badge:s(t.views)}),i("actions")&&e.push({id:"actions",text:"Actions",icon:"zap",url:"#/actions",section:"#/actions",badge:s(t.actions)}),i("pages")&&e.push({id:"blocks",text:"Blocks",icon:"box",url:"#/blocks",section:"#/blocks",badge:s(t.blocks)})),e.push({divider:!0},{heading:"System"}),i("notifications")&&e.push({id:"notifications",text:"Notifications",icon:"bell",url:"#/system/notifications",section:"#/system/notifications",badge:s(t.unreadNotifications)}),i("plugins")&&e.push({id:"roles",text:"Roles",icon:"shield",url:"#/roles",section:"#/roles",badge:s(t.roles)}),i("users")&&e.push({id:"users",text:"Users",icon:"users",url:"#/users",section:"#/users",badge:s(t.users)}),i("settings")&&(e.push({id:"settings",text:"Site Settings",icon:"settings",url:"#/settings",section:"#/settings"}),e.push({id:"effects",text:"Effects",icon:"sparkles",url:"#/effects",section:"#/effects"})),i("plugins")&&e.push({id:"plugins",text:"Plugins",icon:"package",url:"#/plugins",section:"#/plugins",badge:s(t.plugins)}),e.push({id:"my-profile",text:"My Profile",icon:"user",url:"#/my-profile",section:"#/my-profile"}),e.push({divider:!0},{heading:"Documentation"},{id:"documentation",text:"Usage",icon:"book",url:"#/documentation",section:"#/documentation",badge:s(t.documents)},{id:"tutorials",text:"Tutorials",icon:"document",url:"#/tutorials",section:"#/documentation",badge:s(t.tutorials)},{id:"api-reference",text:"API Reference",icon:"code",url:"#/api-reference",section:"#/documentation"}),e}
@@ -0,0 +1 @@
1
+ import{clearAuth as p,getToken as u,refreshAccessToken as l}from"./api.js";let i=null;export function installHttpInterceptor(){if(!window.Domma?.http||Domma.http.__intercepted)return;const n=Domma.http.request.bind(Domma.http);Domma.http.request=async function(s,e,a,r={}){if(!(typeof e=="string"&&e.startsWith("/api/")))return n(s,e,a,r);const o=t=>({...r.headers||{},...t?{Authorization:`Bearer ${t}`}:{}});try{return await n(s,e,a,{...r,headers:o(u())})}catch(t){const c=t&&t.message||"";if(!(c.includes("Invalid or missing authentication token")||c.includes("HTTP Error: 401")))throw t;i||(i=l().catch(m=>{throw p(),R.navigate("/login"),m}).finally(()=>{i=null}));let h;try{h=await i}catch{return}return n(s,e,a,{...r,headers:o(h)})}},Domma.http.__intercepted=!0}
@@ -0,0 +1 @@
1
+ const o={ADD_ATTR:["style","target"],ADD_TAGS:["button"],ALLOW_DATA_ATTR:!0},s=new DOMParser;export function safeSetHTML(t,e){const r=DOMPurify.sanitize(e,o),n=s.parseFromString(r,"text/html");t.replaceChildren(...Array.from(n.body.childNodes))}
@@ -10,10 +10,11 @@
10
10
  <div class="tab-content">
11
11
 
12
12
  <div class="tab-panel active" id="panel-presets">
13
- <div class="view-actions mb-4">
14
- <p class="text-muted">Layout presets define which structural elements appear on pages using that layout. Assign
15
- a layout to each page via the page editor.</p>
16
- <button id="save-layouts-btn" class="btn btn-primary"><span data-icon="save"></span> Save Presets</button>
13
+ <div class="view-actions mb-4"
14
+ style="display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;">
15
+ <p class="text-muted" style="margin:0;">Layout presets define the structure, width, and background of pages.
16
+ Assign a layout to each page via the page editor.</p>
17
+ <button id="create-layout-btn" class="btn btn-primary"><span data-icon="plus"></span> New Layout</button>
17
18
  </div>
18
19
  <div id="presets-grid" class="presets-grid"></div>
19
20
  </div>
@@ -0,0 +1,14 @@
1
+ <div class="view-container">
2
+ <div class="view-header">
3
+ <h1><span data-icon="bell"></span> Notifications</h1>
4
+ </div>
5
+
6
+ <div id="notif-list"></div>
7
+
8
+ <div id="notif-empty" hidden class="card">
9
+ <div class="card-body" style="text-align:center;padding:3rem;color:var(--dm-color-text-muted)">
10
+ <span data-icon="check-circle" style="font-size:2rem;display:block;margin-bottom:.5rem"></span>
11
+ <p>No notifications — you're all caught up.</p>
12
+ </div>
13
+ </div>
14
+ </div>
@@ -0,0 +1,16 @@
1
+ <div class="view-header">
2
+ <h2><span data-icon="store"></span> Plugin Marketplace</h2>
3
+ <p class="text-muted">Install premium plugins from the Domma manager.</p>
4
+ </div>
5
+
6
+ <div id="marketplace-unavailable" style="display:none">
7
+ <div class="card">
8
+ <div class="card-body text-center">
9
+ <p>The Domma Manager is not configured. Set <code>MANAGER_URL</code> and <code>LICENCE_TOKEN</code> in your <code>.env</code> to enable the marketplace.</p>
10
+ </div>
11
+ </div>
12
+ </div>
13
+
14
+ <div id="marketplace-available" style="display:none">
15
+ <div id="marketplace-grid" class="card-grid"></div>
16
+ </div>
@@ -3,10 +3,22 @@
3
3
  <p class="text-muted" style="margin:0;font-size:.875rem">Plugins extend the CMS with new features. Changes take effect after a server restart.</p>
4
4
  </div>
5
5
 
6
- <div id="plugins-empty" class="card" style="display:none">
7
- <div class="card-body text-muted">
8
- No plugins found. Drop a plugin folder into the <code>plugins/</code> directory to get started.
6
+ <div id="plugin-tabs" class="tabs">
7
+ <div class="tab-list">
8
+ <button class="tab-item active" data-tab="installed">Installed Plugins</button>
9
+ <button class="tab-item" data-tab="marketplace">Marketplace</button>
10
+ </div>
11
+
12
+ <div class="tab-panel active" data-panel="installed">
13
+ <div id="plugins-empty" class="card" style="display:none">
14
+ <div class="card-body text-muted">
15
+ No plugins found. Drop a plugin folder into the <code>plugins/</code> directory to get started.
16
+ </div>
17
+ </div>
18
+ <div id="plugins-grid" class="plugins-grid"></div>
9
19
  </div>
10
- </div>
11
20
 
12
- <div id="plugins-grid" class="plugins-grid"></div>
21
+ <div class="tab-panel" data-panel="marketplace">
22
+ <div id="marketplace-mount"></div>
23
+ </div>
24
+ </div>
@@ -1 +1 @@
1
- import{dashboardView as o}from"./dashboard.js";import{pagesView as i}from"./pages.js";import{pageEditorView as r}from"./page-editor.js?v=4";import{settingsView as e}from"./settings.js";import{navigationView as t}from"./navigation.js";import{layoutsView as m}from"./layouts.js";import{mediaView as s}from"./media.js";import{loginView as f}from"./login.js";import{usersView as p}from"./users.js";import{userEditorView as w}from"./user-editor.js";import{pluginsView as V}from"./plugins.js";import{documentationView as n}from"./documentation.js";import{tutorialsView as l}from"./tutorials.js";import{apiReferenceView as c}from"./api-reference.js";import{collectionsView as a}from"./collections.js";import{collectionEditorView as d}from"./collection-editor.js";import{collectionEntriesView as E}from"./collection-entries.js";import{formsView as u}from"./forms.js";import{formEditorView as g}from"./form-editor.js";import{formSubmissionsView as v}from"./form-submissions.js";import{viewsListView as b}from"./views-list.js";import{viewEditorView as k}from"./view-editor.js";import{viewPreviewView as y}from"./view-preview.js";import{actionsListView as L}from"./actions-list.js";import{actionEditorView as P}from"./action-editor.js";import{proDocsView as h}from"./pro-docs.js";import{blocksView as D}from"./blocks.js";import{blockEditorView as R}from"./block-editor.js";import{myProfileView as S}from"./my-profile.js";import{rolesView as x}from"./roles.js";import{roleEditorView as j}from"./role-editor.js";import{effectsView as q}from"./effects.js";export const views={dashboard:o,pages:i,pageEditor:r,settings:e,navigation:t,layouts:m,media:s,login:f,users:p,userEditor:w,plugins:V,documentation:n,tutorials:l,apiReference:c,collections:a,collectionEditor:d,collectionEntries:E,forms:u,formEditor:g,formSubmissions:v,viewsList:b,viewEditor:k,viewPreview:y,actionsList:L,actionEditor:P,proDocs:h,blocks:D,blockEditor:R,myProfile:S,roles:x,roleEditor:j,effects:q};
1
+ import{dashboardView as o}from"./dashboard.js";import{pagesView as i}from"./pages.js";import{pageEditorView as r}from"./page-editor.js?v=4";import{settingsView as t}from"./settings.js";import{navigationView as e}from"./navigation.js";import{notificationsView as m}from"./notifications.js";import{layoutsView as s}from"./layouts.js";import{mediaView as f}from"./media.js";import{loginView as p}from"./login.js";import{usersView as w}from"./users.js";import{userEditorView as n}from"./user-editor.js";import{pluginsView as V}from"./plugins.js";import{documentationView as c}from"./documentation.js";import{tutorialsView as l}from"./tutorials.js";import{apiReferenceView as a}from"./api-reference.js";import{collectionsView as d}from"./collections.js";import{collectionEditorView as E}from"./collection-editor.js";import{collectionEntriesView as u}from"./collection-entries.js";import{formsView as g}from"./forms.js";import{formEditorView as v}from"./form-editor.js";import{formSubmissionsView as b}from"./form-submissions.js";import{viewsListView as k}from"./views-list.js";import{viewEditorView as y}from"./view-editor.js";import{viewPreviewView as L}from"./view-preview.js";import{actionsListView as P}from"./actions-list.js";import{actionEditorView as h}from"./action-editor.js";import{proDocsView as D}from"./pro-docs.js";import{blocksView as R}from"./blocks.js";import{blockEditorView as S}from"./block-editor.js";import{myProfileView as x}from"./my-profile.js";import{rolesView as j}from"./roles.js";import{roleEditorView as q}from"./role-editor.js";import{effectsView as z}from"./effects.js";export const views={dashboard:o,pages:i,pageEditor:r,settings:t,navigation:e,layouts:s,media:f,login:p,users:w,userEditor:n,plugins:V,documentation:c,tutorials:l,apiReference:a,collections:d,collectionEditor:E,collectionEntries:u,forms:g,formEditor:v,formSubmissions:b,viewsList:k,viewEditor:y,viewPreview:L,actionsList:P,actionEditor:h,proDocs:D,blocks:R,blockEditor:S,myProfile:x,roles:j,roleEditor:q,effects:z,notifications:m};
@@ -1,16 +1 @@
1
- import{api as c}from"../api.js";export const layoutsView={templateUrl:"/admin/js/templates/layouts.html",async onMount(a){E.tabs(a.find("#layouts-tabs").get(0));let t=await c.layouts.get().catch(()=>({}));const r=a.find("#presets-grid").empty();Object.entries(t).forEach(([s,e])=>{r.append(`
2
- <div class="preset-card card" data-key="${s}">
3
- <div class="card-header">
4
- <h3>${e.label||s}</h3>
5
- <span class="badge">${s}</span>
6
- </div>
7
- <div class="card-body">
8
- <p class="text-muted">${e.description||""}</p>
9
- <div class="preset-toggles">
10
- <label class="toggle-label"><input type="checkbox" class="form-check preset-navbar" ${e.navbar?"checked":""}> Navbar</label>
11
- <label class="toggle-label"><input type="checkbox" class="form-check preset-footer" ${e.footer?"checked":""}> Footer</label>
12
- <label class="toggle-label"><input type="checkbox" class="form-check preset-sidebar" ${e.sidebar?"checked":""}> Sidebar</label>
13
- </div>
14
- </div>
15
- </div>
16
- `)}),a.find("#save-layouts-btn").on("click",async()=>{a.find(".preset-card").each(function(){const s=$(this).data("key");t[s]&&(t[s].navbar=$(this).find(".preset-navbar").is(":checked"),t[s].footer=$(this).find(".preset-footer").is(":checked"),t[s].sidebar=$(this).find(".preset-sidebar").is(":checked"))});try{await c.layouts.save(t),E.toast("Layouts saved.",{type:"success"})}catch{E.toast("Failed to save layouts.",{type:"error"})}});const l=await c.layouts.getOptions().catch(()=>({spacerSize:8,spacerClass:""})),o=a.find("#spacer-size-input"),i=a.find("#spacer-class-input");o.val(l.spacerSize??8),i.val(l.spacerClass??""),a.find("#save-options-btn").on("click",async()=>{const s=parseInt(o.val(),10)||8,e=i.val().trim();try{await c.layouts.saveOptions({spacerSize:s,spacerClass:e}),E.toast("Layout options saved.",{type:"success"})}catch{E.toast("Failed to save layout options.",{type:"error"})}})}};
1
+ import{api as f}from"../api.js";const N={narrow:"Narrow",normal:"Normal",wide:"Wide",full:"Full"},B="display:block;font-size:.7rem;font-weight:600;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;margin-bottom:.25rem;",S="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;box-sizing:border-box;";function x(n,e){const t=document.createElement("div");t.style.cssText="display:flex;flex-direction:column;gap:.25rem;";const a=document.createElement("label");return a.style.cssText=B,a.textContent=n,t.appendChild(a),t.appendChild(e),t}function T(n,e){const t=document.createElement("input");return t.type="text",t.placeholder=n||"",t.value=e||"",t.style.cssText=S,t}function L(n,e){const t=document.createElement("label");t.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!!e;const o=document.createElement("span");return o.style.cssText="font-size:.9em;color:var(--dm-text,#eee);",o.textContent=n,t.appendChild(a),t.appendChild(o),{wrap:t,cb:a}}function F(n){const e=document.createElement("select");return e.style.cssText=S,[["narrow","Narrow (768px)"],["normal","Normal (1100px)"],["wide","Wide (1280px)"],["full","Full width"]].forEach(([t,a])=>{const o=document.createElement("option");o.value=t,o.textContent=a,t===(n||"normal")&&(o.selected=!0),e.appendChild(o)}),e}function D(n){const e=document.createElement("div");e.style.cssText="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;";const t=document.createElement("input");t.type="color",t.value=n||"#ffffff",t.disabled=!n,t.style.cssText="width:38px;height:32px;padding:2px;border:1px solid var(--dm-border,#333);border-radius:4px;background:transparent;cursor:pointer;";const a=document.createElement("span");a.style.cssText="font-size:.85em;color:var(--dm-text-muted,#aaa);min-width:4rem;",a.textContent=n||"None";const o=document.createElement("button");o.type="button",o.textContent="Choose",o.style.cssText="padding:.25rem .6rem;font-size:.8em;background:transparent;border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text-muted,#aaa);cursor:pointer;";const s=document.createElement("button");return s.type="button",s.textContent="Clear",s.style.cssText=o.style.cssText,s.disabled=!n,o.addEventListener("click",()=>{t.disabled=!1,s.disabled=!1,t.click()}),t.addEventListener("input",()=>{a.textContent=t.value,s.disabled=!1}),s.addEventListener("click",()=>{t.value="#ffffff",t.disabled=!0,s.disabled=!0,a.textContent="None"}),e.appendChild(t),e.appendChild(a),e.appendChild(o),e.appendChild(s),{wrap:e,getValue(){return t.disabled?"":t.value}}}function O(n,e,t){const a=document.createElement("div");a.className="card preset-card",a.style.cssText="cursor:pointer;transition:box-shadow .15s;",a.addEventListener("mouseenter",()=>{a.style.boxShadow="0 0 0 2px var(--dm-primary,#4f7fff)"}),a.addEventListener("mouseleave",()=>{a.style.boxShadow=""}),a.addEventListener("click",()=>t(n,e));const o=document.createElement("div");o.className="card-header",o.style.cssText="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;";const s=document.createElement("strong");if(s.textContent=e.label||n,o.appendChild(s),e.builtin){const r=document.createElement("span");r.className="badge",r.textContent="built-in",r.style.cssText="font-size:.65rem;",o.appendChild(r)}const d=document.createElement("span");d.className="badge badge-outline",d.textContent=N[e.width]||"Normal",d.style.cssText="margin-left:auto;font-size:.65rem;",o.appendChild(d),a.appendChild(o);const l=document.createElement("div");if(l.className="card-body",e.description){const r=document.createElement("p");r.className="text-muted",r.style.cssText="font-size:.85em;margin:0 0 .5rem;",r.textContent=e.description,l.appendChild(r)}const c=document.createElement("div");if(c.style.cssText="display:flex;gap:.75rem;flex-wrap:wrap;",[["Navbar",e.navbar],["Footer",e.footer],["Sidebar",e.sidebar]].forEach(([r,u])=>{const p=document.createElement("span");p.style.cssText=`font-size:.75em;color:${u?"var(--dm-success,#4caf50)":"var(--dm-text-muted,#666)"};display:flex;align-items:center;gap:.25rem;`,p.textContent=(u?"\u25CF ":"\u25CB ")+r,c.appendChild(p)}),l.appendChild(c),e.bgColor){const r=document.createElement("span");r.title=e.bgColor,r.style.cssText=`display:inline-block;width:14px;height:14px;border-radius:3px;background:${e.bgColor};border:1px solid var(--dm-border,#333);margin-top:.4rem;vertical-align:middle;`,l.appendChild(r)}return a.appendChild(l),a}function z(n,e,t,a,o){const s=E.slideover({title:t?"New Layout":"Edit Layout",size:"md"}),d=document.createElement("div");d.style.cssText="display:flex;flex-direction:column;gap:1rem;padding:1rem;";const l=T("e.g. Dark Landing",e.label||""),c=T("Optional description",e.description||""),r=F(e.width),u=L("Show Navbar",e.navbar!==!1),p=L("Show Footer",e.footer!==!1),g=L("Show Sidebar",e.sidebar===!0),b=D(e.bgColor||""),C=T("https://...",e.bgImage||""),k=T("e.g. no-hero wide-content",e.class||"");d.appendChild(x("Label",l)),d.appendChild(x("Description",c)),d.appendChild(x("Width",r));const h=document.createElement("div");h.style.cssText="display:flex;gap:1.5rem;flex-wrap:wrap;",h.appendChild(u.wrap),h.appendChild(p.wrap),h.appendChild(g.wrap),d.appendChild(h),d.appendChild(x("Background Colour",b.wrap)),d.appendChild(x("Background Image URL",C)),d.appendChild(x("Custom CSS Class",k));const v=document.createElement("div");v.style.cssText="display:flex;gap:.5rem;flex-wrap:wrap;padding-top:.5rem;border-top:1px solid var(--dm-border,#333);";const y=document.createElement("button");if(y.type="button",y.className="btn btn-primary",y.textContent="Save",v.appendChild(y),!t&&!e.builtin){const i=document.createElement("button");i.type="button",i.className="btn btn-danger",i.style.marginLeft="auto",i.textContent="Delete",i.addEventListener("click",async()=>{if(await E.confirm(`Delete the "${e.label||n}" layout?`))try{await f.layouts.remove(n),E.toast("Layout deleted.",{type:"success"}),s.close(),o(n)}catch(m){E.toast(m?.message||"Failed to delete layout.",{type:"error"})}}),v.appendChild(i)}d.appendChild(v),y.addEventListener("click",async()=>{const i=l.value.trim();if(!i){E.toast("Label is required.",{type:"warning"});return}const w={label:i,description:c.value.trim(),width:r.value,navbar:u.cb.checked,footer:p.cb.checked,sidebar:g.cb.checked,bgColor:b.getValue(),bgImage:C.value.trim(),class:k.value.trim()};try{let m;t?m=await f.layouts.create(w):m=await f.layouts.update(n,w),E.toast("Layout saved.",{type:"success"}),s.close(),a(t?m.key:n,m.preset||{...e,...w})}catch(m){E.toast(m?.message||"Failed to save layout.",{type:"error"})}}),s.element.appendChild(d),s.open()}export const layoutsView={templateUrl:"/admin/js/templates/layouts.html",async onMount(n){E.tabs(n.find("#layouts-tabs").get(0));let e=await f.layouts.get().catch(()=>({}));const t=n.find("#presets-grid");function a(){t.empty();const l=document.createElement("div");l.style.cssText="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:1rem;",Object.entries(e).forEach(([c,r])=>{const u=O(c,r,(p,g)=>{z(p,g,!1,(b,C)=>{e[b]={...e[b],...C},a()},b=>{delete e[b],a()})});l.appendChild(u)}),t.get(0).appendChild(l),I.scan(t.get(0))}a(),n.find("#create-layout-btn").on("click",()=>{z(null,{},!0,(l,c)=>{if(!l){E.toast("Layout saved but key missing \u2014 please refresh.",{type:"warning"});return}e[l]=c,a()},()=>{})});const o=await f.layouts.getOptions().catch(()=>({spacerSize:8,spacerClass:""})),s=n.find("#spacer-size-input"),d=n.find("#spacer-class-input");s.val(o.spacerSize??8),d.val(o.spacerClass??""),n.find("#save-options-btn").on("click",async()=>{const l=parseInt(s.val(),10)||8,c=d.val().trim();try{await f.layouts.saveOptions({spacerSize:l,spacerClass:c}),E.toast("Layout options saved.",{type:"success"})}catch{E.toast("Failed to save layout options.",{type:"error"})}})}};
@@ -0,0 +1 @@
1
+ import{api as p}from"../api.js";export const notificationsView={templateUrl:"/admin/js/templates/notifications.html",async onMount(r){const f=r.find("#notif-list"),C=r.find("#notif-empty");let o=[];try{o=await p.system.notifications.list()}catch{E.toast("Could not load notifications.",{type:"error"});return}if(!Array.isArray(o)||o.length===0){C.removeAttr("hidden"),Domma.icons.scan(r.get(0));return}o.forEach(e=>{e.unread&&p.system.notifications.markRead(e.id).catch(()=>{})}),o.forEach(e=>{const t=e.data||{},s=t.severity||"info",l=t.createdAt?new Date(t.createdAt).toLocaleString():"",c=s==="critical"?"danger":s==="warning"?"warning":s==="success"?"success":"info",n=document.createElement("div");n.className="card mb-3 notif-card",n.dataset.id=String(e.id);const i=document.createElement("div");i.className="card-header",i.style.cssText="display:flex;align-items:center;gap:.5rem;";const y=document.createElement("span");y.className=`badge badge-${c}`,y.textContent=s.toUpperCase();const g=document.createElement("strong");g.style.flex="1",g.textContent=t.title||"";const h=document.createElement("small");h.className="text-muted",h.textContent=l,i.appendChild(y),i.appendChild(g),i.appendChild(h);const d=document.createElement("div");d.className="card-body";const u=document.createElement("p");if(u.style.cssText="white-space:pre-wrap;margin:0 0 .75rem",u.textContent=t.body||"",d.appendChild(u),t.link){const a=document.createElement("a");a.href=t.link,a.target="_blank",a.rel="noopener",a.className="btn btn-sm btn-secondary",a.textContent="Open Link ";const x=document.createElement("span");x.dataset.icon="external-link",a.appendChild(x),d.appendChild(a)}const b=document.createElement("div");b.style.cssText="margin-top:.5rem;display:flex;gap:.5rem;";const m=document.createElement("button");m.className="btn btn-sm btn-secondary js-dismiss",m.dataset.id=String(e.id),m.textContent="Dismiss",b.appendChild(m),d.appendChild(b),n.appendChild(i),n.appendChild(d),f.get(0).appendChild(n)}),f.get(0).addEventListener("click",async e=>{const t=e.target.closest(".js-dismiss");if(!t)return;const s=t.dataset.id;try{await p.system.notifications.dismiss(s);const l=t.closest(".notif-card");l&&l.remove(),f.get(0).querySelectorAll(".notif-card").length===0&&C.removeAttr("hidden");const{count:c}=await p.system.notifications.unreadCount().catch(()=>({count:0})),n=$("#topbar-bell-badge");c>0?n.text(c>99?"99+":String(c)).removeAttr("hidden"):n.attr("hidden","").text("")}catch{E.toast("Could not dismiss notification.",{type:"error"})}}),Domma.icons.scan(r.get(0))}};
@@ -0,0 +1 @@
1
+ import{api as i}from"../api.js";export const pluginMarketplaceView={templateUrl:"/admin/js/templates/plugin-marketplace.html",async onMount(e){const a=E.loader(e.get(0),{type:"dots"});let t;try{t=await i.marketplace.catalogue()}catch(n){E.toast(`Failed to load marketplace: ${n.message}`,{type:"error"}),a.destroy();return}if(a.destroy(),!t.available){e.find("#marketplace-unavailable").show();return}e.find("#marketplace-available").show(),b(e,t.catalogue),Domma.icons.scan()}};function b(e,a){const t=e.find("#marketplace-grid").get(0);if(t){for(;t.firstChild;)t.removeChild(t.firstChild);if(!a||a.length===0){const n=document.createElement("p");n.className="text-muted",n.textContent="No plugins available in the marketplace.",t.appendChild(n);return}a.forEach(n=>{const u=$(n,e);t.appendChild(u)})}}function U(e){if(!e||e===0||e==="0")return"Free";const a=parseFloat(e);return isNaN(a)||a===0?"Free":`\xA3${a.toFixed(2)}/mo`}function $(e,a){const{slug:t,version:n,displayName:u,description:w,price:D,entitled:x,installed:F}=e,s=document.createElement("div");s.className="card plugin-card",s.dataset.slug=t;const o=document.createElement("div");o.className="card-body";const c=document.createElement("div");c.className="plugin-header";const h=document.createElement("div");h.className="plugin-icon";const k=document.createElement("span");k.dataset.icon="package",h.appendChild(k);const d=document.createElement("div");d.className="plugin-meta";const g=document.createElement("div");g.className="plugin-name",g.textContent=u||t;const f=document.createElement("div");f.className="plugin-version",f.textContent=`v${n}`,d.appendChild(g),d.appendChild(f);const r=document.createElement("div");r.style.cssText="display:flex;gap:.35rem;align-items:center;";const C=document.createElement("span");if(C.className="badge badge-outline",C.textContent=U(D),r.appendChild(C),x){const N=document.createElement("span");N.className="badge badge-success",N.textContent="Entitled",r.appendChild(N)}c.appendChild(h),c.appendChild(d),c.appendChild(r);const v=document.createElement("p");v.className="plugin-desc",v.textContent=w||"",o.appendChild(c),o.appendChild(v);const m=document.createElement("div");m.className="plugin-footer";const p=document.createElement("span");p.className="text-muted",p.style.fontSize=".8rem",p.textContent="Restart server after changes",m.appendChild(p);const y=document.createElement("div");y.className="plugin-footer-actions";const l=document.createElement("button");return l.className="btn btn-sm",F?(l.className+=" btn-ghost",l.textContent="Uninstall",l.onclick=()=>R(t,a)):x?(l.className+=" btn-primary",l.textContent="Install",l.onclick=()=>I(t,n,a)):(l.className+=" btn-ghost",l.textContent="Not entitled",l.disabled=!0),y.appendChild(l),m.appendChild(y),s.appendChild(o),s.appendChild(m),s}async function I(e,a,t){if(await E.confirm(`Install ${e} v${a}?`))try{await i.marketplace.install(e,a),E.toast(`${e} installed. Restart the server to activate.`,{type:"success"});const n=await i.marketplace.catalogue();b(t,n.catalogue),Domma.icons.scan()}catch(n){E.toast(`Install failed: ${n.message}`,{type:"error"})}}async function R(e,a){if(await E.confirm(`Uninstall ${e}? This will remove its files.`))try{await i.marketplace.uninstall(e),E.toast(`${e} uninstalled. Restart to apply.`,{type:"success"});const t=await i.marketplace.catalogue();b(a,t.catalogue),Domma.icons.scan()}catch(t){E.toast(`Uninstall failed: ${t.message}`,{type:"error"})}}
@@ -1,38 +1,38 @@
1
- import{api as r}from"../api.js";export const pluginsView={templateUrl:"/admin/js/templates/plugins.html",async onMount(o){const m=E.loader(o.get(0),{type:"dots"});let i=[];try{i=await r.plugins.list()}catch(e){E.toast(`Failed to load plugins: ${e.message}`,{type:"error"})}if(m.destroy(),!i.length){o.find("#plugins-empty").show();return}g(o,i),Domma.icons.scan()}};function g(o,m){const i=o.find("#plugins-grid").empty();m.forEach(e=>{const n=e.enabled?"badge-success":"badge-secondary",l=e.enabled?"Enabled":"Disabled",s=e.enabled?"Disable":"Enable",v=e.enabled?"btn-ghost":"btn-primary",c=e.bundled?"var(--dm-info,#2563eb)":"var(--dm-secondary,#6b7280)",u=e.bundled?"Bundled \u2014 click to unbundle":"Not bundled \u2014 click to bundle",t=`<button class="badge badge-outline btn-bundle-plugin" data-name="${e.name}" data-bundled="${e.bundled?"1":"0"}" title="${u}" style="font-size:0.65rem;padding:1px 6px;cursor:pointer;background:none;border:1px solid;color:${c};border-color:${c};">${e.bundled?"Bundled":"Unbundled"}</button>`,d=e.author?`by ${a(e.author)}`:"",p=e.date?a(e.date):"",b=`
1
+ import{api as i}from"../api.js";import{pluginMarketplaceView as f}from"./plugin-marketplace.js";export const pluginsView={templateUrl:"/admin/js/templates/plugins.html",async onMount(l){let c=!1;l.find('[data-tab="marketplace"]').on("click",async function(){if(c)return;const a=l.find("#marketplace-mount");try{const s=await H.get(f.templateUrl),u=new DOMParser().parseFromString(s,"text/html");for(;u.body.firstChild;)a.get(0).appendChild(u.body.firstChild);await f.onMount(a),c=!0}catch{E.toast("Failed to load marketplace.",{type:"error"})}}),E.tabs(l.find("#plugin-tabs").get(0));const r=E.loader(l.get(0),{type:"dots"});let e=[];try{e=await i.plugins.list()}catch(a){E.toast(`Failed to load plugins: ${a.message}`,{type:"error"})}if(r.destroy(),!e.length){l.find("#plugins-empty").show();return}y(l,e),Domma.icons.scan()}};function y(l,c){const r=l.find("#plugins-grid").empty();c.forEach(e=>{const a=e.enabled?"badge-success":"badge-secondary",s=e.enabled?"Enabled":"Disabled",o=e.enabled?"Disable":"Enable",u=e.enabled?"btn-ghost":"btn-primary",m=e.bundled?"var(--dm-info,#2563eb)":"var(--dm-secondary,#6b7280)",p=e.bundled?"Bundled \u2014 click to unbundle":"Not bundled \u2014 click to bundle",t=`<button class="badge badge-outline btn-bundle-plugin" data-name="${e.name}" data-bundled="${e.bundled?"1":"0"}" title="${p}" style="font-size:0.65rem;padding:1px 6px;cursor:pointer;background:none;border:1px solid;color:${m};border-color:${m};">${e.bundled?"Bundled":"Unbundled"}</button>`,d=e.author?`by ${n(e.author)}`:"",g=e.date?n(e.date):"",b=`
2
2
  <div class="card plugin-card" data-plugin="${e.name}">
3
3
  <div class="card-body">
4
4
  <div class="plugin-header">
5
5
  <div class="plugin-icon"><span data-icon="${e.icon||"package"}"></span></div>
6
6
  <div class="plugin-meta">
7
- <div class="plugin-name">${a(e.displayName)}</div>
8
- <div class="plugin-version">v${a(e.version)}${d?` &middot; ${d}`:""}</div>
7
+ <div class="plugin-name">${n(e.displayName)}</div>
8
+ <div class="plugin-version">v${n(e.version)}${d?` &middot; ${d}`:""}</div>
9
9
  </div>
10
10
  <div style="display:flex;gap:.35rem;align-items:center;">
11
11
  ${t}
12
- <span class="badge ${n}">${l}</span>
12
+ <span class="badge ${a}">${s}</span>
13
13
  </div>
14
14
  </div>
15
- <p class="plugin-desc">${a(e.description)}</p>
16
- ${p?`<p class="plugin-date text-muted" style="font-size:.8rem;margin:0">Released ${p}</p>`:""}
15
+ <p class="plugin-desc">${n(e.description)}</p>
16
+ ${g?`<p class="plugin-date text-muted" style="font-size:.8rem;margin:0">Released ${g}</p>`:""}
17
17
  </div>
18
18
  <div class="plugin-footer">
19
19
  <span class="text-muted" style="font-size:.8rem">Restart server after changes</span>
20
20
  <div class="plugin-footer-actions">
21
21
  ${e.settingsSchema?`<button class="btn btn-sm btn-ghost btn-plugin-settings" data-name="${e.name}">Settings</button>`:""}
22
- <button class="btn btn-sm ${v} btn-toggle-plugin"
22
+ <button class="btn btn-sm ${u} btn-toggle-plugin"
23
23
  data-name="${e.name}"
24
24
  data-enabled="${e.enabled?"1":"0"}">
25
- ${s}
25
+ ${o}
26
26
  </button>
27
27
  </div>
28
28
  </div>
29
- </div>`;i.append(b)}),Domma.icons.scan(),Domma.effects.reveal(".plugin-card",{animation:"fade",stagger:60,duration:400}),Domma.effects.ripple(".btn-toggle-plugin"),i.on("click",".btn-plugin-settings",async function(){const e=$(this).data("name"),n=m.find(t=>t.name===e);if(!n?.settingsSchema)return;const s=`<form id="plugin-settings-form">${n.settingsSchema.map(t=>{const d=n.settings?.[t.key]??t.default??"";if(t.type==="select"){const p=t.options.map(b=>`<option value="${a(b.value)}"${String(d)===String(b.value)?" selected":""}>${a(b.label)}</option>`).join("");return`<div class="form-group">
30
- <label class="form-label">${a(t.label)}</label>
31
- <select class="form-control" name="${a(t.key)}">${p}</select>
29
+ </div>`;r.append(b)}),Domma.icons.scan(),Domma.effects.reveal(".plugin-card",{animation:"fade",stagger:60,duration:400}),Domma.effects.ripple(".btn-toggle-plugin"),r.on("click",".btn-plugin-settings",async function(){const e=$(this).data("name"),a=c.find(t=>t.name===e);if(!a?.settingsSchema)return;const o=`<form id="plugin-settings-form">${a.settingsSchema.map(t=>{const d=a.settings?.[t.key]??t.default??"";if(t.type==="select"){const g=t.options.map(b=>`<option value="${n(b.value)}"${String(d)===String(b.value)?" selected":""}>${n(b.label)}</option>`).join("");return`<div class="form-group">
30
+ <label class="form-label">${n(t.label)}</label>
31
+ <select class="form-control" name="${n(t.key)}">${g}</select>
32
32
  </div>`}return t.type==="number"?`<div class="form-group">
33
- <label class="form-label">${a(t.label)}</label>
34
- <input class="form-control" type="number" name="${a(t.key)}" value="${a(String(d))}"${t.min!=null?` min="${t.min}"`:""}${t.max!=null?` max="${t.max}"`:""}>
33
+ <label class="form-label">${n(t.label)}</label>
34
+ <input class="form-control" type="number" name="${n(t.key)}" value="${n(String(d))}"${t.min!=null?` min="${t.min}"`:""}${t.max!=null?` max="${t.max}"`:""}>
35
35
  </div>`:`<div class="form-group">
36
- <label class="form-label">${a(t.label)}</label>
37
- <input class="form-control" type="text" name="${a(t.key)}" value="${a(String(d))}">
38
- </div>`}).join("")}</form>`;if(!await E.confirm(s,{title:`${a(n.displayName)} Settings`,confirmText:"Save",cancelText:"Cancel",html:!0}))return;const c=document.getElementById("plugin-settings-form");if(!c)return;const u={};n.settingsSchema.forEach(t=>{const d=c.elements[t.key];d&&(u[t.key]=t.type==="number"?Number(d.value):d.value)});try{await r.plugins.update(e,{settings:u}),n.settings={...n.settings,...u},E.toast("Settings saved.",{type:"success"})}catch(t){E.toast(`Failed to save: ${t.message}`,{type:"error"})}}),i.on("click",".btn-bundle-plugin",async function(){const e=$(this),n=e.data("name"),l=e.data("bundled")!==1&&e.data("bundled")!=="1";e.prop("disabled",!0).text(l?"Bundling\u2026":"Unbundling\u2026");try{await r.plugins.update(n,{bundled:l}),E.toast(`Plugin ${l?"marked as bundled":"unbundled"}.`,{type:"success"});const s=await r.plugins.list();g(o,s)}catch(s){E.toast(`Failed: ${s.message}`,{type:"error"}),e.prop("disabled",!1)}}),i.on("click",".btn-toggle-plugin",async function(){const e=$(this),n=e.data("name"),l=e.data("enabled")!==1&&e.data("enabled")!=="1";e.prop("disabled",!0).text(l?"Enabling\u2026":"Disabling\u2026");try{await r.plugins.update(n,{enabled:l}),E.toast(`Plugin ${l?"enabled":"disabled"}. Restart the server to apply changes.`,{type:"success"});const s=await r.plugins.list();g(o,s)}catch(s){E.toast(`Failed: ${s.message}`,{type:"error"}),e.prop("disabled",!1)}})}function a(o){return String(o).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}
36
+ <label class="form-label">${n(t.label)}</label>
37
+ <input class="form-control" type="text" name="${n(t.key)}" value="${n(String(d))}">
38
+ </div>`}).join("")}</form>`;if(!await E.confirm(o,{title:`${n(a.displayName)} Settings`,confirmText:"Save",cancelText:"Cancel",html:!0}))return;const m=document.getElementById("plugin-settings-form");if(!m)return;const p={};a.settingsSchema.forEach(t=>{const d=m.elements[t.key];d&&(p[t.key]=t.type==="number"?Number(d.value):d.value)});try{await i.plugins.update(e,{settings:p}),a.settings={...a.settings,...p},E.toast("Settings saved.",{type:"success"})}catch(t){E.toast(`Failed to save: ${t.message}`,{type:"error"})}}),r.on("click",".btn-bundle-plugin",async function(){const e=$(this),a=e.data("name"),s=e.data("bundled")!==1&&e.data("bundled")!=="1";e.prop("disabled",!0).text(s?"Bundling\u2026":"Unbundling\u2026");try{await i.plugins.update(a,{bundled:s}),E.toast(`Plugin ${s?"marked as bundled":"unbundled"}.`,{type:"success"});const o=await i.plugins.list();y(l,o)}catch(o){E.toast(`Failed: ${o.message}`,{type:"error"}),e.prop("disabled",!1)}}),r.on("click",".btn-toggle-plugin",async function(){const e=$(this),a=e.data("name"),s=e.data("enabled")!==1&&e.data("enabled")!=="1";e.prop("disabled",!0).text(s?"Enabling\u2026":"Disabling\u2026");try{await i.plugins.update(a,{enabled:s}),E.toast(`Plugin ${s?"enabled":"disabled"}. Restart the server to apply changes.`,{type:"success"});const o=await i.plugins.list();y(l,o)}catch(o){E.toast(`Failed: ${o.message}`,{type:"error"}),e.prop("disabled",!1)}})}function n(l){return String(l).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "brand": {
3
- "text": "Domma CMS",
4
- "url": "/",
5
- "icon": "home",
6
- "size": "lg"
3
+ "text": "My Bloggy Thing",
4
+ "logo": null,
5
+ "url": "/"
7
6
  },
8
7
  "items": [
9
8
  {
@@ -16,78 +15,12 @@
16
15
  "url": "/about",
17
16
  "icon": "info"
18
17
  },
19
- {
20
- "text": "Blog",
21
- "url": "/blog",
22
- "icon": "book-open"
23
- },
24
18
  {
25
19
  "text": "Contact",
26
20
  "url": "/contact",
27
21
  "icon": "mail"
28
- },
29
- {
30
- "text": "Feedback",
31
- "url": "/feedback",
32
- "icon": "message-square"
33
- },
34
- {
35
- "text": "Resources",
36
- "url": "/resources",
37
- "icon": "book-open",
38
- "hidden": true,
39
- "items": [
40
- {
41
- "text": "Overview",
42
- "url": "/resources",
43
- "icon": "grid"
44
- },
45
- {
46
- "text": "Typography",
47
- "url": "/resources/typography",
48
- "icon": "type"
49
- },
50
- {
51
- "text": "Grid System",
52
- "url": "/resources/grid",
53
- "icon": "layout"
54
- },
55
- {
56
- "text": "Cards",
57
- "url": "/resources/cards",
58
- "icon": "credit-card"
59
- },
60
- {
61
- "text": "Shortcode Reference",
62
- "url": "/resources/shortcodes",
63
- "icon": "code"
64
- },
65
- {
66
- "text": "Effects",
67
- "url": "/resources/effects",
68
- "icon": "zap"
69
- },
70
- {
71
- "text": "Interactive",
72
- "url": "/resources/interactive",
73
- "icon": "mouse-pointer"
74
- }
75
- ]
76
- },
77
- {
78
- "text": "Scratch",
79
- "url": "/scratch",
80
- "icon": "home",
81
- "hidden": true
82
22
  }
83
23
  ],
84
- "variant": "transparent",
85
- "position": "sticky",
86
- "style": {
87
- "fontFamily": "Roboto",
88
- "fontSize": "16px",
89
- "fontWeight": "400",
90
- "letterSpacing": "0em",
91
- "iconSize": "16px"
92
- }
24
+ "variant": "dark",
25
+ "position": "sticky"
93
26
  }
@@ -1,31 +1,27 @@
1
1
  {
2
- "theme-switcher": {
3
- "enabled": false,
4
- "settings": {},
5
- "bundled": true
6
- },
7
- "contacts": {
8
- "enabled": true,
9
- "settings": {}
10
- },
11
- "garage": {
2
+ "todo": {
12
3
  "enabled": true,
4
+ "bundled": true,
13
5
  "settings": {}
14
6
  },
15
- "site-search": {
7
+ "notes": {
16
8
  "enabled": true,
9
+ "bundled": true,
17
10
  "settings": {}
18
11
  },
19
- "notes": {
12
+ "contacts": {
20
13
  "enabled": true,
14
+ "bundled": true,
21
15
  "settings": {}
22
16
  },
23
- "todo": {
17
+ "site-search": {
24
18
  "enabled": true,
19
+ "bundled": true,
25
20
  "settings": {}
26
21
  },
27
- "docs": {
22
+ "blog": {
28
23
  "enabled": true,
24
+ "bundled": false,
29
25
  "settings": {}
30
26
  }
31
27
  }
@@ -1,30 +1,67 @@
1
1
  {
2
2
  "default": {
3
+ "key": "default",
3
4
  "label": "Default",
4
- "description": "Standard page with navbar and footer",
5
+ "description": "Standard page with navbar and footer.",
6
+ "builtin": true,
5
7
  "navbar": true,
6
8
  "footer": true,
7
- "sidebar": false
9
+ "sidebar": false,
10
+ "width": "normal",
11
+ "bgColor": "",
12
+ "bgImage": "",
13
+ "class": ""
14
+ },
15
+ "landing": {
16
+ "key": "landing",
17
+ "label": "Landing Page",
18
+ "description": "Full-width landing page with navbar, no footer.",
19
+ "builtin": true,
20
+ "navbar": true,
21
+ "footer": false,
22
+ "sidebar": false,
23
+ "width": "full",
24
+ "bgColor": "",
25
+ "bgImage": "",
26
+ "class": ""
27
+ },
28
+ "blank": {
29
+ "key": "blank",
30
+ "label": "Blank",
31
+ "description": "Minimal page with no navbar or footer.",
32
+ "builtin": true,
33
+ "navbar": false,
34
+ "footer": false,
35
+ "sidebar": false,
36
+ "width": "normal",
37
+ "bgColor": "",
38
+ "bgImage": "",
39
+ "class": ""
8
40
  },
9
41
  "with-sidebar": {
42
+ "key": "with-sidebar",
10
43
  "label": "With Sidebar",
11
- "description": "Page with navbar, sidebar, and footer",
44
+ "description": "Page with navbar, sidebar, and footer.",
45
+ "builtin": false,
12
46
  "navbar": true,
13
47
  "footer": true,
14
- "sidebar": true
48
+ "sidebar": true,
49
+ "width": "normal",
50
+ "bgColor": "",
51
+ "bgImage": "",
52
+ "class": ""
15
53
  },
16
54
  "minimal": {
55
+ "key": "minimal",
17
56
  "label": "Minimal",
18
- "description": "Clean page with no navbar or footer",
57
+ "description": "Clean page with no navbar or footer.",
58
+ "builtin": false,
19
59
  "navbar": false,
20
60
  "footer": false,
21
- "sidebar": false
22
- },
23
- "landing": {
24
- "label": "Landing Page",
25
- "description": "Full-width landing page layout",
26
- "navbar": true,
27
- "footer": true,
28
- "sidebar": false
61
+ "sidebar": false,
62
+ "width": "normal",
63
+ "bgColor": "",
64
+ "bgImage": "",
65
+ "class": ""
29
66
  }
30
67
  }