domma-cms 0.10.0 → 0.13.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 (121) 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/documentation.html +611 -2
  9. package/admin/js/templates/layouts.html +5 -4
  10. package/admin/js/templates/notifications.html +14 -0
  11. package/admin/js/templates/plugin-marketplace.html +16 -0
  12. package/admin/js/templates/plugins.html +17 -5
  13. package/admin/js/views/index.js +1 -1
  14. package/admin/js/views/layouts.js +1 -16
  15. package/admin/js/views/notifications.js +1 -0
  16. package/admin/js/views/plugin-marketplace.js +1 -0
  17. package/admin/js/views/plugins.js +16 -16
  18. package/config/navigation.json +5 -72
  19. package/config/plugins.json +10 -14
  20. package/config/presets.json +50 -13
  21. package/config/site.json +11 -63
  22. package/package.json +2 -1
  23. package/plugins/_template/admin/templates/index.html +17 -0
  24. package/plugins/_template/admin/views/index.js +19 -0
  25. package/plugins/_template/config.js +8 -0
  26. package/plugins/_template/plugin.js +23 -0
  27. package/plugins/_template/plugin.json +34 -0
  28. package/plugins/analytics/plugin.json +41 -31
  29. package/plugins/blog/admin/templates/blog.html +22 -0
  30. package/plugins/blog/admin/templates/categories.html +7 -0
  31. package/plugins/blog/admin/templates/comments.html +11 -0
  32. package/plugins/blog/admin/templates/post-editor.html +97 -0
  33. package/plugins/blog/admin/templates/settings.html +11 -0
  34. package/plugins/blog/admin/views/blog.js +183 -0
  35. package/plugins/blog/admin/views/categories.js +235 -0
  36. package/plugins/blog/admin/views/comments.js +187 -0
  37. package/plugins/blog/admin/views/post-editor.js +291 -0
  38. package/plugins/blog/admin/views/settings.js +100 -0
  39. package/plugins/blog/collections/categories/schema.json +12 -0
  40. package/plugins/blog/collections/comments/schema.json +16 -0
  41. package/plugins/blog/collections/posts/schema.json +19 -0
  42. package/plugins/blog/config.js +8 -0
  43. package/plugins/blog/plugin.js +352 -0
  44. package/plugins/blog/plugin.json +96 -0
  45. package/plugins/blog/roles/blog-author.json +10 -0
  46. package/plugins/blog/roles/blog-editor.json +12 -0
  47. package/plugins/blog/templates/author.html +9 -0
  48. package/plugins/blog/templates/category.html +9 -0
  49. package/plugins/blog/templates/index.html +9 -0
  50. package/plugins/blog/templates/post.html +17 -0
  51. package/plugins/blog/templates/tag.html +9 -0
  52. package/plugins/contacts/collections/user-contact-groups/schema.json +1 -1
  53. package/plugins/contacts/collections/user-contacts/schema.json +1 -1
  54. package/plugins/contacts/plugin.js +4 -10
  55. package/plugins/contacts/plugin.json +13 -3
  56. package/plugins/notes/collections/user-notes/schema.json +1 -1
  57. package/plugins/notes/plugin.js +3 -9
  58. package/plugins/notes/plugin.json +13 -3
  59. package/plugins/site-search/plugin.json +5 -2
  60. package/plugins/theme-switcher/plugin.json +1 -1
  61. package/plugins/todo/collections/todos/schema.json +1 -1
  62. package/plugins/todo/plugin.js +3 -9
  63. package/plugins/todo/plugin.json +13 -3
  64. package/public/css/site.css +1 -1
  65. package/scripts/build.js +48 -0
  66. package/scripts/create-plugin.js +113 -0
  67. package/scripts/fresh.js +6 -7
  68. package/scripts/gen-instance-secret.js +46 -0
  69. package/scripts/reset.js +3 -3
  70. package/scripts/setup.js +31 -13
  71. package/server/middleware/auth.js +48 -0
  72. package/server/middleware/managerAuth.js +36 -0
  73. package/server/routes/api/actions.js +1 -1
  74. package/server/routes/api/auth.js +4 -3
  75. package/server/routes/api/layouts.js +173 -49
  76. package/server/routes/api/notifications.js +155 -0
  77. package/server/routes/api/plugin-marketplace.js +75 -0
  78. package/server/routes/api/users.js +1 -1
  79. package/server/routes/api/views.js +1 -1
  80. package/server/routes/public.js +4 -9
  81. package/server/server.js +32 -3
  82. package/server/services/actions.js +1 -1
  83. package/server/services/managerClient.js +182 -0
  84. package/server/services/markdown.js +52 -14
  85. package/server/services/permissionRegistry.js +245 -173
  86. package/server/services/pluginInstaller.js +301 -0
  87. package/server/services/plugins.js +117 -10
  88. package/server/services/presetCollections.js +66 -251
  89. package/server/services/renderer.js +99 -0
  90. package/server/services/roles.js +191 -39
  91. package/server/services/users.js +1 -1
  92. package/server/services/views.js +1 -1
  93. package/server/templates/page.html +2 -2
  94. package/plugins/docs/admin/templates/docs.html +0 -69
  95. package/plugins/docs/admin/views/docs.js +0 -276
  96. package/plugins/docs/config.js +0 -8
  97. package/plugins/docs/data/documents/57e003f0-68f2-47dc-9c36-ed4b10ed3deb.json +0 -11
  98. package/plugins/docs/data/folders.json +0 -9
  99. package/plugins/docs/data/templates.json +0 -1
  100. package/plugins/docs/data/versions/57e003f0-68f2-47dc-9c36-ed4b10ed3deb/1.json +0 -5
  101. package/plugins/docs/plugin.js +0 -375
  102. package/plugins/docs/plugin.json +0 -23
  103. package/plugins/form-builder/data/forms/contacts.json +0 -66
  104. package/plugins/form-builder/data/forms/enquiries.json +0 -103
  105. package/plugins/form-builder/data/forms/feedback.json +0 -131
  106. package/plugins/form-builder/data/forms/notes.json +0 -79
  107. package/plugins/form-builder/data/forms/to-do.json +0 -100
  108. package/plugins/form-builder/data/submissions/contacts.json +0 -1
  109. package/plugins/form-builder/data/submissions/enquiries.json +0 -1
  110. package/plugins/form-builder/data/submissions/feedback.json +0 -1
  111. package/plugins/form-builder/data/submissions/notes.json +0 -1
  112. package/plugins/form-builder/data/submissions/to-do.json +0 -1
  113. package/plugins/garage/admin/templates/garage.html +0 -111
  114. package/plugins/garage/admin/views/garage.js +0 -622
  115. package/plugins/garage/collections/garage-vehicles/schema.json +0 -101
  116. package/plugins/garage/config.js +0 -18
  117. package/plugins/garage/data/vehicles.json +0 -70
  118. package/plugins/garage/plugin.js +0 -398
  119. package/plugins/garage/plugin.json +0 -33
  120. package/scripts/seed.js +0 -1996
  121. package/server/services/userTypes.js +0 -227
@@ -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
  }
package/config/site.json CHANGED
@@ -1,27 +1,14 @@
1
1
  {
2
- "title": "My Nice Little Super Blog!",
3
- "tagline": "A nice little bloggy type thingy.",
4
- "fontFamily": "Roboto",
5
- "fontSize": 16,
6
- "theme": "dreamy-light",
7
- "autoTheme": {
8
- "enabled": false,
9
- "dayTheme": "charcoal-light",
10
- "nightTheme": "charcoal-dark",
11
- "dayStart": "07:00",
12
- "nightStart": "19:00"
13
- },
14
- "adminTheme": "charcoal-dark",
15
- "layoutOptions": {
16
- "spacerSize": 40
17
- },
2
+ "title": "My Bloggy Thing",
3
+ "tagline": "This is the Domma CMS Testbed",
4
+ "theme": "charcoal-dark",
18
5
  "seo": {
19
- "defaultTitle": "My Nice Little Super Blog!",
6
+ "defaultTitle": "My Bloggy Thing",
20
7
  "titleSeparator": " | ",
21
8
  "defaultDescription": "A site built with Domma CMS"
22
9
  },
23
10
  "footer": {
24
- "copyright": "© 2026 DCBW Consulting Ltd",
11
+ "copyright": "© My Site. All rights reserved.",
25
12
  "links": [
26
13
  {
27
14
  "text": "Privacy Policy",
@@ -30,59 +17,20 @@
30
17
  {
31
18
  "text": "Contact",
32
19
  "url": "/contact"
33
- },
34
- {
35
- "text": "GDPR",
36
- "url": "/gdpr"
37
20
  }
38
21
  ]
39
22
  },
40
- "social": {
41
- "twitter": "",
42
- "facebook": "",
43
- "instagram": "",
44
- "linkedin": "",
45
- "github": "https://github.com/pinpointzero73/",
46
- "youtube": ""
47
- },
23
+ "social": {},
48
24
  "smtp": {
49
- "host": "192.168.1.27",
50
- "port": 1025,
25
+ "host": "",
26
+ "port": 587,
51
27
  "user": "",
52
28
  "pass": "",
53
29
  "secure": false,
54
- "fromAddress": "noreply@dwit.uk",
55
- "fromName": "My Big Boss Website"
56
- },
57
- "backToTop": {
58
- "enabled": true,
59
- "scrollThreshold": 180,
60
- "position": "bottom-right",
61
- "offset": 16,
62
- "bottomOffset": 16,
63
- "label": "",
64
- "smooth": true
65
- },
66
- "cookieConsent": {
67
- "enabled": true,
68
- "message": "We use cookies to enhance your browsing experience, serve personalised content, and analyse our traffic. By clicking \"Accept All\", you consent to our use of cookies.",
69
- "acceptAllText": "Accept All",
70
- "rejectAllText": "Reject All",
71
- "customizeText": "Customize",
72
- "savePreferencesText": "Save Preferences",
73
- "privacyPolicyText": "Privacy Policy",
74
- "privacyPolicyUrl": "/privacy-policy",
75
- "cookiePolicyText": "Cookie Policy",
76
- "cookiePolicyUrl": "",
77
- "position": "bottom",
78
- "layout": "bar",
79
- "theme": "dark",
80
- "showFunctional": true,
81
- "showAnalytics": true,
82
- "showMarketing": true,
83
- "consentVersion": "1.0"
30
+ "fromAddress": "",
31
+ "fromName": ""
84
32
  },
85
33
  "adminBrand": {
86
- "title": "Domma Admin"
34
+ "title": "Domma CMS"
87
35
  }
88
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.10.0",
3
+ "version": "0.13.0",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
@@ -27,6 +27,7 @@
27
27
  ],
28
28
  "scripts": {
29
29
  "build": "node scripts/build.js",
30
+ "create-plugin": "node scripts/create-plugin.js",
30
31
  "delete-users": "node -e \"import('fs').then(({readdirSync,rmSync})=>{const d='content/users';readdirSync(d).filter(f=>f.endsWith('.json')).forEach(f=>{rmSync(d+'/'+f);console.log('deleted',f)})})\"",
31
32
  "start": "node server/server.js",
32
33
  "pm2:start": "pm2 start ecosystem.config.cjs --env production",
@@ -0,0 +1,17 @@
1
+ <div class="view-header d-flex justify-content-between align-items-center mb-4">
2
+ <div>
3
+ <h1 class="view-title">PLUGIN_DISPLAY_NAME</h1>
4
+ <p class="view-subtitle text-muted">Manage PLUGIN_DISPLAY_NAME.</p>
5
+ </div>
6
+ <div class="d-flex gap-2">
7
+ <button id="btn-new-item" class="btn btn-primary">
8
+ <span data-icon="plus"></span> New Item
9
+ </button>
10
+ </div>
11
+ </div>
12
+
13
+ <div class="card">
14
+ <div class="card-body">
15
+ <div id="items-table"></div>
16
+ </div>
17
+ </div>
@@ -0,0 +1,19 @@
1
+ /**
2
+ * PLUGIN_DISPLAY_NAME — admin view.
3
+ * Route: #/plugins/PLUGIN_SLUG
4
+ */
5
+ export const indexView = {
6
+ templateUrl: '/plugins/PLUGIN_SLUG/admin/templates/index.html',
7
+
8
+ async onMount($container) {
9
+ // Your view logic here.
10
+ // Available globals: $, _, M, D, S, H, F, E, I, T, R
11
+ //
12
+ // Fetch data: const data = await H.get('/api/plugins/PLUGIN_SLUG/items');
13
+ // Show toast: E.toast('Saved!', { type: 'success' });
14
+ // Confirm: if (!await E.confirm('Are you sure?')) return;
15
+ // Create table: T.create(el, { data, columns: [...] });
16
+ // Create form: F.create({ fields }, defaults, { showSubmitButton: false }).renderTo(el);
17
+ I.scan();
18
+ }
19
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * PLUGIN_DISPLAY_NAME plugin — default configuration.
3
+ * Merged with user overrides from config/plugins.json.
4
+ */
5
+ export default {
6
+ // Add your config keys and defaults here.
7
+ // Example: itemsPerPage: 20
8
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * PLUGIN_DISPLAY_NAME plugin — server-side routes and lifecycle hooks.
3
+ */
4
+
5
+ export default async function plugin(fastify, options) {
6
+ const { authenticate, requireAdmin } = options.auth;
7
+ const BASE = `/api/plugins/PLUGIN_SLUG`;
8
+
9
+ // Add your API routes here.
10
+ // Example:
11
+ // fastify.get(`${BASE}/items`, { preHandler: [authenticate] }, async (request, reply) => {
12
+ // return { items: [] };
13
+ // });
14
+ }
15
+
16
+ export async function onEnable({ fastify, services }) {
17
+ // Called when the plugin is enabled via admin.
18
+ // Create collections, pages, or forms here if needed.
19
+ }
20
+
21
+ export async function onDisable({ fastify, services }) {
22
+ // Called when the plugin is disabled via admin.
23
+ }