domma-cms 0.2.1 → 0.5.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 (166) hide show
  1. package/README.md +3 -3
  2. package/admin/css/admin.css +1 -1200
  3. package/admin/dist/domma/domma-tools.css +2313 -0
  4. package/admin/dist/domma/domma-tools.min.js +10 -0
  5. package/admin/index.html +4 -0
  6. package/admin/js/api.js +1 -242
  7. package/admin/js/app.js +9 -279
  8. package/admin/js/config/sidebar-config.js +1 -115
  9. package/admin/js/lib/card.js +1 -63
  10. package/admin/js/lib/image-editor.js +1 -869
  11. package/admin/js/lib/markdown-toolbar.js +54 -421
  12. package/admin/js/templates/action-editor.html +171 -0
  13. package/admin/js/templates/actions-list.html +19 -0
  14. package/admin/js/templates/api-reference.html +1411 -0
  15. package/admin/js/templates/block-editor.html +158 -0
  16. package/admin/js/templates/blocks.html +8 -0
  17. package/admin/js/templates/collection-editor.html +47 -0
  18. package/admin/js/templates/collection-entries.html +3 -0
  19. package/admin/js/templates/collections.html +51 -4
  20. package/admin/js/templates/documentation.html +258 -0
  21. package/admin/js/templates/form-editor.html +238 -0
  22. package/{plugins/form-builder/admin → admin/js}/templates/form-submissions.html +30 -30
  23. package/{plugins/form-builder/admin/templates/forms-list.html → admin/js/templates/forms.html} +17 -17
  24. package/admin/js/templates/layouts.html +44 -7
  25. package/admin/js/templates/login.html +29 -4
  26. package/admin/js/templates/my-profile.html +17 -0
  27. package/admin/js/templates/page-editor.html +48 -0
  28. package/admin/js/templates/pages.html +6 -1
  29. package/admin/js/templates/pro-docs.html +259 -0
  30. package/admin/js/templates/role-editor.html +59 -0
  31. package/admin/js/templates/roles.html +10 -0
  32. package/admin/js/templates/settings.html +137 -18
  33. package/admin/js/templates/tutorials.html +81 -0
  34. package/admin/js/templates/user-editor.html +7 -0
  35. package/admin/js/templates/users.html +3 -1
  36. package/admin/js/templates/view-editor.html +201 -0
  37. package/admin/js/templates/view-preview.html +51 -0
  38. package/admin/js/templates/views-list.html +19 -0
  39. package/admin/js/views/action-editor.js +1 -0
  40. package/admin/js/views/actions-list.js +1 -0
  41. package/admin/js/views/api-reference.js +1 -0
  42. package/admin/js/views/block-editor.js +8 -0
  43. package/admin/js/views/blocks.js +4 -0
  44. package/admin/js/views/collection-editor.js +3 -487
  45. package/admin/js/views/collection-entries.js +1 -484
  46. package/admin/js/views/collections.js +1 -153
  47. package/admin/js/views/dashboard.js +1 -56
  48. package/admin/js/views/documentation.js +1 -12
  49. package/admin/js/views/form-editor.js +8 -0
  50. package/admin/js/views/form-submissions.js +1 -0
  51. package/admin/js/views/forms.js +1 -0
  52. package/admin/js/views/index.js +1 -39
  53. package/admin/js/views/layouts.js +9 -42
  54. package/admin/js/views/login.js +7 -251
  55. package/admin/js/views/media.js +1 -240
  56. package/admin/js/views/my-profile.js +1 -0
  57. package/admin/js/views/navigation.js +14 -212
  58. package/admin/js/views/page-editor.js +72 -661
  59. package/admin/js/views/pages.js +5 -72
  60. package/admin/js/views/plugins.js +13 -90
  61. package/admin/js/views/pro-docs.js +1 -0
  62. package/admin/js/views/role-editor.js +1 -0
  63. package/admin/js/views/roles.js +4 -0
  64. package/admin/js/views/settings.js +3 -199
  65. package/admin/js/views/tutorials.js +1 -12
  66. package/admin/js/views/user-editor.js +1 -88
  67. package/admin/js/views/users.js +4 -76
  68. package/admin/js/views/view-editor.js +1 -0
  69. package/admin/js/views/view-preview.js +1 -0
  70. package/admin/js/views/views-list.js +1 -0
  71. package/bin/cli.js +1 -1
  72. package/config/auth.json +2 -17
  73. package/config/connections.json.bak +9 -0
  74. package/config/connections.json.example +9 -0
  75. package/config/navigation.json +15 -0
  76. package/config/plugins.json +19 -29
  77. package/config/server.json +6 -6
  78. package/config/site.json +17 -6
  79. package/package.json +24 -10
  80. package/plugins/domma-effects/public/celebrations/core/canvas.js +2 -104
  81. package/plugins/domma-effects/public/celebrations/core/particles.js +1 -144
  82. package/plugins/domma-effects/public/celebrations/core/physics.js +1 -166
  83. package/plugins/domma-effects/public/celebrations/index.js +1 -535
  84. package/plugins/domma-effects/public/celebrations/themes/christmas.js +1 -1805
  85. package/plugins/domma-effects/public/celebrations/themes/guy-fawkes.js +1 -1477
  86. package/plugins/domma-effects/public/celebrations/themes/halloween.js +1 -1837
  87. package/plugins/domma-effects/public/celebrations/themes/st-andrews.js +1 -1175
  88. package/plugins/domma-effects/public/celebrations/themes/st-davids.js +1 -1258
  89. package/plugins/domma-effects/public/celebrations/themes/st-georges.js +1 -1754
  90. package/plugins/domma-effects/public/celebrations/themes/st-patricks.js +1 -1290
  91. package/plugins/domma-effects/public/celebrations/themes/valentines.js +1 -1361
  92. package/plugins/example-analytics/stats.json +21 -12
  93. package/plugins/theme-roller/admin/templates/theme-roller.html +71 -0
  94. package/plugins/theme-roller/admin/views/theme-roller-view.js +403 -0
  95. package/plugins/theme-roller/config.js +1 -0
  96. package/plugins/theme-roller/plugin.js +233 -0
  97. package/plugins/theme-roller/plugin.json +31 -0
  98. package/plugins/theme-roller/public/active-theme.css +0 -0
  99. package/plugins/theme-roller/public/inject-head-late.html +1 -0
  100. package/public/css/forms.css +1 -0
  101. package/public/css/site.css +1 -302
  102. package/public/js/btt.js +1 -90
  103. package/public/js/cookie-consent.js +1 -61
  104. package/public/js/form-logic-engine.js +1 -0
  105. package/public/js/forms.js +1 -0
  106. package/public/js/site.js +1 -204
  107. package/scripts/build.js +194 -129
  108. package/scripts/pro.js +254 -0
  109. package/scripts/reset.js +33 -8
  110. package/scripts/seed.js +343 -78
  111. package/scripts/setup.js +5 -4
  112. package/server/middleware/auth.js +136 -97
  113. package/server/routes/api/actions.js +200 -0
  114. package/server/routes/api/auth.js +292 -116
  115. package/server/routes/api/blocks.js +84 -0
  116. package/server/routes/api/collections.js +88 -23
  117. package/{plugins/form-builder/plugin.js → server/routes/api/forms.js} +483 -505
  118. package/server/routes/api/layouts.js +49 -25
  119. package/server/routes/api/media.js +118 -93
  120. package/server/routes/api/navigation.js +40 -37
  121. package/server/routes/api/pages.js +132 -118
  122. package/server/routes/api/plugins.js +6 -3
  123. package/server/routes/api/settings.js +104 -89
  124. package/server/routes/api/users.js +27 -21
  125. package/server/routes/api/views.js +148 -0
  126. package/server/routes/public.js +124 -108
  127. package/server/server.js +269 -173
  128. package/server/services/actions.js +387 -0
  129. package/server/services/adapterRegistry.js +98 -0
  130. package/server/services/adapters/FileAdapter.js +192 -0
  131. package/server/services/adapters/MongoAdapter.js +220 -0
  132. package/server/services/blocks.js +162 -0
  133. package/server/services/collections.js +74 -86
  134. package/server/services/connectionManager.js +102 -0
  135. package/server/services/content.js +312 -307
  136. package/{plugins/form-builder → server/services}/email.js +126 -103
  137. package/server/services/forms.js +173 -0
  138. package/server/services/markdown.js +1378 -648
  139. package/server/services/permissionRegistry.js +173 -0
  140. package/server/services/presetCollections.js +251 -0
  141. package/server/services/renderer.js +75 -1
  142. package/server/services/roles.js +227 -0
  143. package/server/services/rowAccess.js +104 -0
  144. package/server/services/userProfiles.js +199 -0
  145. package/server/services/users.js +281 -212
  146. package/server/services/views.js +280 -0
  147. package/server/templates/page.html +119 -113
  148. package/plugins/form-builder/admin/templates/form-editor.html +0 -171
  149. package/plugins/form-builder/admin/templates/form-settings.html +0 -29
  150. package/plugins/form-builder/admin/views/form-editor.js +0 -1442
  151. package/plugins/form-builder/admin/views/form-settings.js +0 -38
  152. package/plugins/form-builder/admin/views/form-submissions.js +0 -295
  153. package/plugins/form-builder/admin/views/forms-list.js +0 -164
  154. package/plugins/form-builder/config.js +0 -9
  155. package/plugins/form-builder/data/forms/consent.json +0 -104
  156. package/plugins/form-builder/data/forms/contact-details.json +0 -63
  157. package/plugins/form-builder/data/forms/contacts.json +0 -66
  158. package/plugins/form-builder/data/submissions/consent.json +0 -13
  159. package/plugins/form-builder/data/submissions/contact-details.json +0 -1
  160. package/plugins/form-builder/data/submissions/contacts.json +0 -26
  161. package/plugins/form-builder/plugin.json +0 -52
  162. package/plugins/form-builder/public/form-logic-engine.js +0 -568
  163. package/plugins/form-builder/public/inject-body.html +0 -352
  164. package/plugins/form-builder/public/inject-head.html +0 -58
  165. package/plugins/form-builder/public/package.json +0 -1
  166. package/scripts/copy-domma.js +0 -48
@@ -1,76 +1,4 @@
1
- /**
2
- * Users List View
3
- * Shows all users in a Domma table with role badges.
4
- */
5
- import {api, getUser} from '../api.js';
6
-
7
- const ROLE_BADGE = {
8
- admin: 'badge-danger',
9
- manager: 'badge-warning',
10
- editor: 'badge-info',
11
- subscriber: 'badge-secondary'
12
- };
13
-
14
- export const usersView = {
15
- templateUrl: '/admin/js/templates/users.html',
16
-
17
- async onMount($container) {
18
- const currentUser = getUser();
19
- let users = await api.users.list().catch(() => []);
20
-
21
- const renderTable = (data) => {
22
- T.create('#users-table', {
23
- data,
24
- columns: [
25
- {key: 'name', title: 'Name'},
26
- {key: 'email', title: 'Email'},
27
- {
28
- key: 'role', title: 'Role',
29
- render: (val) => `<span class="badge ${ROLE_BADGE[val] || 'badge-secondary'}">${val}</span>` },
30
- {
31
- key: 'isActive', title: 'Status',
32
- render: (val) => val
33
- ? '<span class="badge badge-success">Active</span>'
34
- : '<span class="badge badge-secondary">Inactive</span>' },
35
- {
36
- key: 'lastLogin', title: 'Last login',
37
- render: (val) => val ? D(val).format('DD MMM YYYY HH:mm') : 'Never' },
38
- {
39
- key: 'actions', title: 'Actions',
40
- render: (_, row) => {
41
- const isSelf = row.id === currentUser?.id;
42
- return `
43
- <a href="#/users/edit/${row.id}" class="btn btn-sm btn-outline">Edit</a>
44
- ${!isSelf ? `<button class="btn btn-sm btn-danger btn-delete-user" data-id="${row.id}" data-name="${escapeAttr(row.name)}">Delete</button>` : ''}
45
- `;
46
- }
47
- }
48
- ],
49
- emptyMessage: 'No users found.'
50
- });
51
- Domma.icons.scan();
52
- Domma.effects.reveal('.card', { animation: 'fade', duration: 350 });
53
- };
54
-
55
- renderTable(users);
56
-
57
- $container.off('click', '.btn-delete-user').on('click', '.btn-delete-user', async function () {
58
- const id = $(this).data('id');
59
- const name = $(this).data('name');
60
- const confirmed = await E.confirm(`Delete user <strong>${name}</strong>? This cannot be undone.`);
61
- if (!confirmed) return;
62
- try {
63
- await api.users.delete(id);
64
- E.toast('User deleted.', { type: 'success' });
65
- users = users.filter(u => u.id !== id);
66
- renderTable(users);
67
- } catch (err) {
68
- E.toast(`Failed to delete: ${err.message}`, { type: 'error' });
69
- }
70
- });
71
- }
72
- };
73
-
74
- function escapeAttr(str) {
75
- return String(str).replace(/"/g, '&quot;');
76
- }
1
+ import{api as c,getUser as p}from"../api.js";export const usersView={templateUrl:"/admin/js/templates/users.html",async onMount(a){const i=p(),b=E.loader(a.get(0),{type:"dots"});let[n,g]=await Promise.all([c.users.list().catch(()=>[]),c.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]}))]);b.destroy();const o=g.entries||[],d=r=>{T.create("#users-table",{data:r,columns:[{key:"name",title:"Name"},{key:"email",title:"Email"},{key:"role",title:"Role",render:e=>{const t=o.find(m=>m.data?.name===e),s=t?.data?.badgeClass||"badge-secondary",l=t?.data?.label||e;return`<span class="badge ${s}">${l}</span>`}},{key:"isActive",title:"Status",render:e=>e?'<span class="badge badge-success">Active</span>':'<span class="badge badge-secondary">Inactive</span>'},{key:"lastLogin",title:"Last login",render:e=>e?D(e).format("DD MMM YYYY HH:mm"):"Never"},{key:"actions",title:"Actions",render:(e,t)=>{const s=t.id===i?.id,l=Object.fromEntries(o.map(u=>[u.data?.name,u.data?.level??99]));return(l[i?.role]??99)<(l[t.role]??99)?`
2
+ <a href="#/users/edit/${t.id}" class="btn btn-sm btn-primary">Edit</a>
3
+ ${s?"":`<button class="btn btn-sm btn-danger btn-delete-user" data-id="${t.id}" data-name="${y(t.name)}">Delete</button>`}
4
+ `:""}}],emptyMessage:"No users found."}),Domma.icons.scan()};d(n),a.off("click",".btn-delete-user").on("click",".btn-delete-user",async function(){const r=$(this).data("id"),e=$(this).data("name");if(await E.confirm(`Delete user <strong>${e}</strong>? This cannot be undone.`))try{await c.delete(`/users/${r}`),E.toast("User deleted.",{type:"success"}),n=n.filter(s=>s.id!==r),d(n)}catch(s){E.toast(`Failed to delete: ${s.message}`,{type:"error"})}})}};function y(a){return String(a).replace(/"/g,"&quot;")}
@@ -0,0 +1 @@
1
+ import{api as h}from"../api.js";let b=null,f=null;export const viewEditorView={templateUrl:"/admin/js/templates/view-editor.html",async onMount(e){b=null,f=null;const l=window.location.hash.match(/\/views\/edit\/([^/?#]+)/);l&&(b=l[1]),E.tabs(e.find("#view-editor-tabs").get(0)),await O(e),await D(e),await j(e),await T(e),e.find("#view-source").get(0)?.addEventListener("change",async()=>{await x(e)}),b&&(e.find("#view-editor-title").text("Edit View"),await I(e,b)),e.find("#add-stage-btn").off("click").on("click",()=>{const n=e.find("#add-stage-type").val()||"$match";q(e,{type:n,config:{}})}),e.find("#save-view-btn").off("click").on("click",async()=>{await Q(e)}),H(e),Y(e),Domma.icons.scan()}};async function O(e){const t=e.find("#view-source").get(0);if(t)try{(await h.collections.list()).forEach(n=>{const a=document.createElement("option");a.value=n.slug,a.textContent=`${n.title} (${n.slug})`,t.appendChild(a)})}catch{}}async function T(e){const t=e.find("#view-block-name").get(0);if(t)try{(await h.blocks.list()).forEach(n=>{const a=document.createElement("option");a.value=n.name,a.textContent=`${n.name}.html`,t.appendChild(a)})}catch{}}async function D(e){const t=e.find("#view-connection").get(0);if(t)try{const l=await h.collections.getConnections();Object.keys(l).forEach(n=>{if(!t.querySelector(`option[value="${n}"]`)){const a=document.createElement("option");a.value=n,a.textContent=n,t.appendChild(a)}})}catch{}}async function j(e){const t=e.find("#view-roles-checkboxes").get(0);if(!t)return;["admin","manager","editor","subscriber"].forEach(n=>{const a=document.createElement("label");a.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const s=document.createElement("input");s.type="checkbox",s.value=n,s.dataset.role=n,s.className="view-role-cb",s.checked=n==="admin",a.appendChild(s),a.appendChild(document.createTextNode(n)),t.appendChild(a)})}async function x(e){const t=e.find("#view-source").val();if(!t){f=null;return}try{f=(await h.collections.get(t))?.fields||[]}catch{f=null}e.find(".stage-card[data-guided]").each(function(){const l=this.dataset.stageType,n=this.querySelector(".stage-guided");n&&(l==="$match"&&M(n),l==="$sort"&&z(n))}),A(e,null)}async function I(e,t){try{const l=await h.views.get(t);if(!l){E.toast("View not found.",{type:"error"}),R.navigate("/views");return}const n=l.pipeline?.source;n&&(e.find("#view-source").val(n),await x(e)),F(e,l)}catch(l){E.toast(l.message||"Failed to load view.",{type:"error"}),R.navigate("/views")}}function F(e,t){e.find("#view-title").val(t.title||""),e.find("#view-slug").val(t.slug||""),e.find("#view-description").val(t.description||""),e.find("#view-source").val(t.pipeline?.source||""),e.find("#view-connection").val(t.connection||"default"),e.find("#view-display-mode").val(t.display?.mode||"table"),e.find("#view-page-size").val(t.display?.pageSize||25),e.find("#view-block-name").val(t.display?.block||"");const l=t.access?.roles||["admin"];e.find(".view-role-cb").each(function(){this.checked=l.includes(this.value)}),e.find("#view-public").prop("checked",t.access?.public||!1);const n=t.access?.rowLevel;n&&(e.find("#view-rowlevel-enabled").prop("checked",!0),e.find("#view-rowlevel-config").css("display","flex"),e.find("#view-rowlevel-mode").val(n.mode||"owner"),e.find("#view-rowlevel-userkey").val(n.userKey||"id"),n.mode==="field"&&(e.find("#view-rowlevel-field-group").css("display",""),e.find("#view-rowlevel-field").val(n.field||"")));const a=e.find("#pipeline-stages-list").get(0);if(a)for(;a.firstChild;)a.removeChild(a.firstChild);(t.pipeline?.stages||[]).forEach(s=>q(e,s)),A(e,t.display?.columns||[]),S(e)}const _=[{value:"eq",label:"= equals"},{value:"ne",label:"\u2260 not equals"},{value:"gt",label:"> greater than"},{value:"lt",label:"< less than"},{value:"gte",label:"\u2265 greater or equal"},{value:"lte",label:"\u2264 less or equal"},{value:"contains",label:"~ contains (regex)"},{value:"in",label:"\u2208 in (comma list)"}];function g(e=!1){const t=[];return e&&t.push({value:"",label:"\u2014 select field \u2014"}),(f||[]).forEach(l=>{t.push({value:`data.${l.name}`,label:`${l.label||l.name} (${l.name})`})}),t.length===0||t.length===1&&e?(t.push({value:"__meta.createdAt",label:"Created At"}),t.push({value:"__meta.updatedAt",label:"Updated At"})):(t.push({value:"__meta.createdAt",label:"Created At (meta)"}),t.push({value:"__meta.updatedAt",label:"Updated At (meta)"})),t}function v(e,t){const l=document.createElement("select");return l.className="form-input form-input--sm",e.forEach(n=>{const a=document.createElement("option");a.value=n.value,a.textContent=n.label,String(n.value)===String(t)&&(a.selected=!0),l.appendChild(a)}),l}function N(e,t,l="text"){const n=document.createElement("input");return n.type=l,n.className="form-input form-input--sm",n.placeholder=e,n.value=t??"",n}function B(e){if(!f?.length)return null;const t=e?.startsWith("data.")?e.slice(5):null;return t&&f.find(l=>l.name===t)||null}function y(e,t,l=""){const n=B(e);if(n?.type==="select"&&(t==="eq"||t==="ne")&&n.options?.length){const i=document.createElement("select");i.className="form-input form-input--sm match-val",i.style.flex="1";const o=document.createElement("option");return o.value="",o.textContent="\u2014 select value \u2014",i.appendChild(o),(n.options||[]).forEach(c=>{const d=typeof c=="string"?c:c.value??"",u=typeof c=="string"?c:c.label||d;if(!d||d==="undefined")return;const m=document.createElement("option");m.value=d,m.textContent=u,d===l&&(m.selected=!0),i.appendChild(m)}),i}const s=t==="in"?"val1, val2, val3":t==="contains"?"search pattern (regex)":t==="gt"||t==="lt"||t==="gte"||t==="lte"?"0":"value",r=document.createElement("input");return r.type="text",r.className="form-input form-input--sm match-val",r.style.flex="1",r.placeholder=s,r.value=l,r}function k(e){const t=[],l={$eq:"eq",$ne:"ne",$gt:"gt",$lt:"lt",$gte:"gte",$lte:"lte",$in:"in"};return Object.entries(e||{}).forEach(([n,a])=>{if(typeof a=="object"&&a!==null&&!Array.isArray(a)){if("$regex"in a){t.push({field:n,op:"contains",val:String(a.$regex??"")});return}Object.entries(a).forEach(([s,r])=>{const i=l[s];i&&t.push({field:n,op:i,val:Array.isArray(r)?r.join(", "):String(r)})})}else t.push({field:n,op:"eq",val:String(a??"")})}),t}function C(e,t,l="",n="eq",a=""){const s=document.createElement("div");s.className="match-condition-row",s.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;flex-wrap:wrap;";const r=v([{value:"",label:"\u2014 field \u2014"},...g()],l);r.className+=" match-field",r.style.minWidth="140px";const i=v(_,n);i.className+=" match-op",i.style.minWidth="160px";const o=document.createElement("div");o.className="match-val-wrap",o.style.cssText="flex:1;min-width:120px;display:flex;",o.appendChild(y(l,n,a));const c=()=>{const m=o.querySelector(".match-val")?.value??"";for(;o.firstChild;)o.removeChild(o.firstChild);o.appendChild(y(r.value,i.value,m))};r.addEventListener("change",c),i.addEventListener("change",c);const d=document.createElement("button");d.type="button",d.className="btn btn-sm btn-ghost",d.title="Remove";const u=document.createElement("span");u.setAttribute("data-icon","x"),d.appendChild(u),d.addEventListener("click",()=>s.remove()),s.appendChild(r),s.appendChild(i),s.appendChild(o),s.appendChild(d),e.insertBefore(s,t),Domma.icons.scan(s)}function W(e,t={}){for(;e.firstChild;)e.removeChild(e.firstChild);const l=t.$match||t||{},n="$or"in l;let a=[];n?(l.$or||[]).forEach(d=>k(d).forEach(u=>a.push(u))):a=k(l);const s=document.createElement("div");s.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.6rem;";const r=document.createElement("label");r.className="form-label",r.style.marginBottom="0",r.textContent="Match:";const i=document.createElement("select");i.className="form-input form-input--sm match-logic",i.style.width="auto",[{value:"and",label:"ALL conditions (AND)"},{value:"or",label:"ANY condition (OR)"}].forEach(d=>{const u=document.createElement("option");u.value=d.value,u.textContent=d.label,(n?"or":"and")===d.value&&(u.selected=!0),i.appendChild(u)}),s.appendChild(r),s.appendChild(i),e.appendChild(s);const o=document.createElement("button");o.type="button",o.className="btn btn-ghost btn-sm match-add-btn";const c=document.createElement("span");c.setAttribute("data-icon","plus"),o.appendChild(c),o.appendChild(document.createTextNode(" Add Condition")),o.addEventListener("click",()=>{C(e,o),Domma.icons.scan(e)}),e.appendChild(o),a.forEach(d=>C(e,o,d.field,d.op,d.val)),a.length===0&&C(e,o),Domma.icons.scan(e)}function M(e){e.querySelectorAll(".match-condition-row").forEach(t=>{const l=t.querySelector(".match-field");if(l){const r=l.value,i=v([{value:"",label:"\u2014 field \u2014"},...g()],r);i.className=l.className,i.style.cssText=l.style.cssText;const o=t.querySelector(".match-val-wrap"),c=t.querySelector(".match-op");o&&c&&i.addEventListener("change",()=>{const d=o.querySelector(".match-val")?.value??"";for(;o.firstChild;)o.removeChild(o.firstChild);o.appendChild(y(i.value,c.value,d))}),l.parentNode.replaceChild(i,l)}const n=t.querySelector(".match-val-wrap"),a=t.querySelector(".match-field")?.value,s=t.querySelector(".match-op")?.value;if(n&&a&&s){const r=n.querySelector(".match-val")?.value??"";for(;n.firstChild;)n.removeChild(n.firstChild);n.appendChild(y(a,s,r))}})}function U(e){const t=e.querySelector(".match-logic")?.value||"and",l=a=>{const s=a.querySelector(".match-field")?.value?.trim(),r=a.querySelector(".match-op")?.value,i=a.querySelector(".match-val")?.value?.trim()??"";if(!s)return null;const o=s.startsWith("__")?s.slice(2):s,c={};switch(r){case"eq":c[o]=i;break;case"ne":c[o]={$ne:i};break;case"gt":c[o]={$gt:isNaN(i)?i:Number(i)};break;case"lt":c[o]={$lt:isNaN(i)?i:Number(i)};break;case"gte":c[o]={$gte:isNaN(i)?i:Number(i)};break;case"lte":c[o]={$lte:isNaN(i)?i:Number(i)};break;case"contains":c[o]={$regex:i,$options:"i"};break;case"in":c[o]={$in:i.split(",").map(d=>d.trim()).filter(Boolean)};break}return Object.keys(c).length?c:null},n=[];return e.querySelectorAll(".match-condition-row").forEach(a=>{const s=l(a);s&&n.push(s)}),n.length===0?{}:t==="or"?{$or:n}:Object.assign({},...n)}function V(e,t={}){for(;e.firstChild;)e.removeChild(e.firstChild);const l=t.$sort||t||{},n=Object.entries(l),a=document.createElement("button");a.type="button",a.className="btn btn-ghost btn-sm";const s=document.createElement("span");s.setAttribute("data-icon","plus"),a.appendChild(s),a.appendChild(document.createTextNode(" Add Sort")),e.appendChild(a);const r=(i="",o=1)=>{const c=document.createElement("div");c.className="sort-row",c.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;";const d=v([{value:"",label:"\u2014 field \u2014"},...g()],i);d.className+=" sort-field";const u=v([{value:"1",label:"\u2191 Ascending"},{value:"-1",label:"\u2193 Descending"}],String(o));u.className+=" sort-dir";const m=document.createElement("button");m.type="button",m.className="btn btn-sm btn-ghost",m.title="Remove";const p=document.createElement("span");p.setAttribute("data-icon","x"),m.appendChild(p),m.addEventListener("click",()=>{c.remove()}),c.appendChild(d),c.appendChild(u),c.appendChild(m),e.insertBefore(c,a),Domma.icons.scan(c)};a.addEventListener("click",()=>{r()}),n.forEach(([i,o])=>r(i,o)),n.length===0&&r(),Domma.icons.scan(e)}function z(e){e.querySelectorAll(".sort-row").forEach(t=>{const l=t.querySelector(".sort-field"),n=l?.value,a=v([{value:"",label:"\u2014 field \u2014"},...g()],n);a.className=l.className,l.parentNode.replaceChild(a,l)})}function J(e){const t={};return e.querySelectorAll(".sort-row").forEach(l=>{const n=l.querySelector(".sort-field")?.value?.trim(),a=parseInt(l.querySelector(".sort-dir")?.value,10)||1;if(!n)return;const s=n.startsWith("__")?n.slice(2):n;t[s]=a}),t}function q(e,t){const l=e.find("#pipeline-stages-list").get(0);if(!l)return;const n=l.querySelector(".stage-empty-placeholder");n&&n.remove();const a=["$match","$sort"].includes(t.type),s=document.createElement("div");s.className="card mb-2 stage-card",s.dataset.stageType=t.type,a&&(s.dataset.guided="true");const r=document.createElement("div");r.className="card-header",r.style.cssText="display:flex;align-items:center;gap:.5rem;";const i=document.createElement("code");i.textContent=t.type,i.style.cssText="flex:1;font-size:.85rem;";const o=document.createElement("button");o.type="button",o.className="btn btn-sm btn-danger";const c=document.createElement("span");c.setAttribute("data-icon","trash-2"),o.appendChild(c),o.addEventListener("click",()=>{s.remove(),l.querySelector(".stage-card")||l.appendChild(G())}),r.appendChild(i),r.appendChild(o);const d=document.createElement("div");if(d.className="card-body",a){const u=document.createElement("div");u.className="stage-guided",d.appendChild(u),t.type==="$match"&&W(u,t.config),t.type==="$sort"&&V(u,t.config)}else{const u=document.createElement("label");u.className="form-label",u.textContent="Stage Config (JSON)";const m=document.createElement("small");m.className="text-muted",m.style.cssText="display:block;margin-bottom:.4rem;",m.textContent=`Enter the inner config for ${t.type} \u2014 e.g. for $lookup: { from, localField, foreignField, as }. Do not wrap in { "${t.type}": ... }.`;const p=document.createElement("textarea");p.className="form-input stage-config",p.rows=5,p.style.cssText="font-family:monospace;font-size:.8rem;resize:vertical;",p.placeholder="{}",p.value=Object.keys(t.config||{}).length?JSON.stringify(t.config,null,2):"",d.appendChild(u),d.appendChild(m),d.appendChild(p)}s.appendChild(r),s.appendChild(d),l.appendChild(s),Domma.icons.scan(s)}function G(){const e=document.createElement("p");return e.className="text-muted stage-empty-placeholder",e.textContent="No stages yet. Add a stage to filter, join, or transform your data.",e.style.cssText="text-align:center;padding:2rem 0;",e}function K(e){const t=[];return e.find(".stage-card").each(function(){const l=this.dataset.stageType,n=this.querySelector(".stage-guided");let a;if(n)l==="$match"&&(a=U(n)),l==="$sort"&&(a=J(n));else{const s=this.querySelector(".stage-config")?.value?.trim()||"{}";try{a=JSON.parse(s)}catch{throw new Error(`Invalid JSON in ${l} stage config`)}}t.push({type:l,config:a})}),t}function A(e,t){const l=e.find("#view-columns-builder").get(0);if(!l)return;for(;l.firstChild;)l.removeChild(l.firstChild);const n=t??(f||[]).map(i=>({key:`data.${i.name}`,label:i.label||i.name})),a=(i="",o="")=>{const c=document.createElement("div");c.className="col-row",c.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;";const d=f?.length?v([{value:"",label:"\u2014 field \u2014"},...g()],i):N("data.fieldName",i);d.className+=" col-key";const u=N("Display label",o);u.className+=" col-label",u.style.flex="1",f?.length&&d.tagName==="SELECT"&&d.addEventListener("change",()=>{if(!u.value){const w=(f||[]).find(L=>`data.${L.name}`===d.value);w&&(u.value=w.label||w.name)}});const m=document.createElement("button");m.type="button",m.className="btn btn-sm btn-ghost",m.title="Remove column";const p=document.createElement("span");p.setAttribute("data-icon","x"),m.appendChild(p),m.addEventListener("click",()=>{c.remove(),Domma.icons.scan(l)}),c.appendChild(d),c.appendChild(u),c.appendChild(m),l.insertBefore(c,l.lastChild),Domma.icons.scan(c)};n.forEach(i=>a(i.key,i.label));const s=document.createElement("button");s.type="button",s.className="btn btn-ghost btn-sm";const r=document.createElement("span");r.setAttribute("data-icon","plus"),s.appendChild(r),s.appendChild(document.createTextNode(" Add Column")),s.addEventListener("click",()=>{a(),Domma.icons.scan(l)}),l.appendChild(s),Domma.icons.scan(l)}function P(e){const t=[];return e.find(".col-row").each(function(){const l=this.querySelector(".col-key")?.value?.trim(),n=this.querySelector(".col-label")?.value?.trim();l&&t.push({key:l,label:n||l})}),t}function Y(e){const t=e.find("#view-display-mode").get(0);t&&(t.addEventListener("change",()=>S(e)),S(e))}function S(e){const t=e.find("#view-display-mode").val()||"table",l=e.find("#view-columns-section").get(0),n=e.find("#view-block-section").get(0);l&&(l.style.display=t==="table"?"":"none"),n&&(n.style.display=t==="block"?"":"none")}function H(e){const t=e.find("#view-rowlevel-enabled").get(0),l=e.find("#view-rowlevel-config").get(0),n=e.find("#view-rowlevel-mode").get(0),a=e.find("#view-rowlevel-field-group").get(0);t&&(t.addEventListener("change",()=>{l&&(l.style.display=t.checked?"flex":"none")}),n&&n.addEventListener("change",()=>{a&&(a.style.display=n.value==="field"?"":"none")}))}async function Q(e){const t=e.find("#view-title").val().trim();if(!t){E.toast("Title is required.",{type:"warning"});return}const l=e.find("#view-source").val();if(!l){E.toast("Source collection is required (Source tab).",{type:"warning"});return}let n;try{n=K(e)}catch(d){E.toast(d.message,{type:"error"});return}const a=P(e),s=[];e.find(".view-role-cb:checked").each(function(){s.push(this.value)});const r=e.find("#view-rowlevel-enabled").is(":checked");let i=null;if(r){const d=e.find("#view-rowlevel-mode").val()||"owner",u=e.find("#view-rowlevel-userkey").val()||"id";if(i={mode:d,userKey:u},d==="field"){const m=e.find("#view-rowlevel-field").val().trim();if(!m){E.toast("Field name is required for Field Match mode.",{type:"warning"});return}i.field=m}}const o={title:t,slug:e.find("#view-slug").val().trim()||void 0,description:e.find("#view-description").val().trim(),connection:e.find("#view-connection").val()||"default",pipeline:{source:l,stages:n},display:{mode:e.find("#view-display-mode").val()||"table",columns:a,pageSize:parseInt(e.find("#view-page-size").val(),10)||25,block:e.find("#view-block-name").val()||""},access:{roles:s,public:e.find("#view-public").is(":checked"),rowLevel:i}},c=e.find("#save-view-btn").get(0);c&&(c.disabled=!0);try{if(b)await h.views.update(b,o),E.toast("View updated.",{type:"success"});else{const d=await h.views.create(o);E.toast("View created.",{type:"success"}),R.navigate(`/views/edit/${d.slug}`)}}catch(d){E.toast(d.message||"Failed to save view.",{type:"error"})}finally{c&&(c.disabled=!1)}}
@@ -0,0 +1 @@
1
+ import{api as m}from"../api.js";let l=null,r=1,c=0;export const viewPreviewView={templateUrl:"/admin/js/templates/view-preview.html",async onMount(e){l=null,r=1,c=0;const i=window.location.hash.match(/\/views\/([^/?#]+)\/preview/),s=i?i[1]:null;if(!s){E.toast("No view selected.",{type:"error"}),R.navigate("/views");return}try{if(l=await m.views.get(s),!l){E.toast("View not found.",{type:"error"}),R.navigate("/views");return}}catch(t){E.toast(t.message||"Failed to load view.",{type:"error"});return}e.find("#preview-title").text(l.title),e.find("#preview-edit-btn").attr("href",`#/views/edit/${l.slug}`),e.find("#preview-source").text(l.pipeline?.source||"\u2014"),e.find("#preview-connection").text(l.connection||"default"),e.find("#preview-mode").text(l.display?.mode||"table"),e.find("#preview-meta-card").show(),e.find("#preview-run-btn").off("click").on("click",async()=>{r=1,await v(e)}),e.find("#preview-prev").off("click").on("click",async()=>{r>1&&(r--,await v(e))}),e.find("#preview-next").off("click").on("click",async()=>{const t=l.display?.pageSize||25;r*t<c&&(r++,await v(e))}),Domma.icons.scan()}};async function v(e){const n=l.display?.pageSize||25,i=e.find("#preview-run-btn").get(0);i&&(i.disabled=!0,i.textContent="Running\u2026"),e.find("#preview-placeholder").hide(),e.find("#preview-table").hide(),e.find("#preview-list").hide(),e.find("#preview-empty").hide();try{const s=await m.views.execute(l.slug,{page:r,limit:n});c=s.total||0;const t=Math.max(1,Math.ceil(c/n));if(e.find("#preview-count").text(`${c} result${c!==1?"s":""}`),e.find("#preview-page-label").text(`Page ${r} of ${t}`),e.find("#preview-prev").prop("disabled",r<=1),e.find("#preview-next").prop("disabled",r>=t),e.find("#preview-pagination").show(),!s.results||s.results.length===0){e.find("#preview-empty").show();return}(l.display?.mode||"table")==="list"?g(s.results,e):y(s.results,e)}catch(s){E.toast(s.message||"Execution failed.",{type:"error"})}finally{i&&(i.disabled=!1,i.textContent="Run")}}function y(e,n){const i=l.display?.columns||[],s=i.length?i:Object.keys(u(e[0])).slice(0,6).map(t=>({key:t,label:t}));T.create("#preview-table",{data:e,columns:s.map(t=>({key:t.key,title:t.label||t.key,render:(d,f)=>{const w=t.key.split(".");let a=f;for(const h of w)a=a?.[h];const o=a!=null?String(a):"",p=document.createElement("span");return p.title=o,p.textContent=o.length>80?o.slice(0,80)+"\u2026":o,p.outerHTML}})),emptyMessage:"No results."}),n.find("#preview-table").show(),Domma.icons.scan()}function g(e,n){const i=n.find("#preview-list").get(0);if(i){for(;i.firstChild;)i.removeChild(i.firstChild);e.forEach(s=>{const t=document.createElement("div");t.style.cssText="padding:.75rem;border-bottom:1px solid var(--dm-border,#333);";const d=u(s);Object.entries(d).slice(0,6).forEach(([f,w])=>{const a=document.createElement("div");a.style.cssText="display:flex;gap:.5rem;font-size:.875rem;";const o=document.createElement("strong");o.textContent=f+": ",o.style.cssText="color:var(--dm-text-muted,#888);min-width:120px;flex-shrink:0;";const p=document.createElement("span");p.textContent=String(w??""),a.appendChild(o),a.appendChild(p),t.appendChild(a)}),i.appendChild(t)}),n.find("#preview-list").show()}}function u(e,n="",i={}){for(const[s,t]of Object.entries(e||{})){const d=n?`${n}.${s}`:s;t&&typeof t=="object"&&!Array.isArray(t)&&Object.keys(t).length<8?u(t,d,i):i[d]=t}return i}
@@ -0,0 +1 @@
1
+ import{api as r}from"../api.js";function i(s){return String(s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}export const viewsListView={templateUrl:"/admin/js/templates/views-list.html",async onMount(s){await l(s),s.find("#create-view-btn").off("click").on("click",()=>{R.navigate("/views/new")}),Domma.icons.scan()}};async function l(s){let c=[];try{c=await r.views.list(),s.find("#views-pro-notice").hide()}catch(e){e.message?.includes("MongoDB")||e.message?.includes("pro mode")||e.message?.includes("connection")?s.find("#views-pro-notice").show():E.toast("Could not load views.",{type:"error"})}T.create("#views-table",{data:c,columns:[{key:"title",title:"Title",render:(e,t)=>{const n=document.createElement("a");return n.href=`#/views/${i(t.slug)}/preview`,n.textContent=e,n.style.fontWeight="600",n.outerHTML}},{key:"slug",title:"Slug",render:e=>`<code>${i(e)}</code>`},{key:"pipeline",title:"Source",render:e=>`<code>${i(e?.source||"\u2014")}</code>`},{key:"display",title:"Mode",render:e=>i(e?.mode||"table")},{key:"access",title:"Roles",render:e=>(e?.roles||[]).map(t=>`<span class="badge badge-secondary">${i(t)}</span>`).join(" ")},{key:"slug",title:"Actions",render:e=>{const t=document.createElement("div");t.style.cssText="display:flex;gap:.4rem;justify-content:flex-end;";const n=document.createElement("a");n.href=`#/views/${i(e)}/preview`,n.className="btn btn-sm btn-ghost",n.textContent="Preview";const a=document.createElement("a");a.href=`#/views/edit/${i(e)}`,a.className="btn btn-sm btn-primary",a.textContent="Edit";const o=document.createElement("button");return o.className="btn btn-sm btn-danger js-delete-view",o.dataset.slug=e,o.textContent="Delete",t.appendChild(n),t.appendChild(a),t.appendChild(o),t.outerHTML}}],emptyMessage:'No views yet. Click "New View" to create your first view.'}),document.querySelectorAll(".js-delete-view").forEach(e=>{e.addEventListener("click",async()=>{const t=e.dataset.slug;if(await E.confirm(`Delete view "${t}"? This cannot be undone.`))try{await r.views.delete(t),E.toast("View deleted.",{type:"success"}),await l(s)}catch{E.toast("Failed to delete view.",{type:"error"})}})}),Domma.icons.scan()}
package/bin/cli.js CHANGED
@@ -366,5 +366,5 @@ if (noInstall) console.log(` npm install`);
366
366
  if (noSetup && !noInstall) console.log(` npm run setup`);
367
367
  console.log(` npm run dev`);
368
368
  console.log('');
369
- console.log(` Then open: http://localhost:3050/admin`);
369
+ console.log(` Then open: http://localhost:4096/admin`);
370
370
  console.log('');
package/config/auth.json CHANGED
@@ -1,21 +1,6 @@
1
1
  {
2
2
  "accessTokenExpiry": "15m",
3
3
  "refreshTokenExpiry": "7d",
4
- "bcryptRounds": 10,
5
- "roles": {
6
- "admin": { "label": "Admin", "level": 0 },
7
- "manager": { "label": "Manager", "level": 1 },
8
- "editor": { "label": "Editor", "level": 2 },
9
- "subscriber": { "label": "Subscriber", "level": 3 }
10
- },
11
- "permissions": {
12
- "pages": ["admin", "manager", "editor"],
13
- "settings": ["admin", "manager"],
14
- "navigation": ["admin", "manager"],
15
- "layouts": ["admin", "manager"],
16
- "media": ["admin", "manager", "editor"],
17
- "users": ["admin", "manager"],
18
- "plugins": ["admin"],
19
- "collections": ["admin", "manager"]
20
- }
4
+ "resetTokenExpiry": "1h",
5
+ "bcryptRounds": 10
21
6
  }
@@ -0,0 +1,9 @@
1
+ {
2
+ "default": {
3
+ "type": "mongodb",
4
+ "uri": "mongodb://localhost:27017",
5
+ "database": "domma_cms",
6
+ "options": {}
7
+ },
8
+ "_comment": "Copy this file to connections.json and update with your MongoDB connection details. Each key is a named connection. Collections reference connections by name in their schema.json storage config. The 'default' connection is used when no connection name is specified."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "default": {
3
+ "type": "mongodb",
4
+ "uri": "mongodb://localhost:27017",
5
+ "database": "domma_cms",
6
+ "options": {}
7
+ },
8
+ "_comment": "Copy this file to connections.json and update with your MongoDB connection details. Each key is a named connection. Collections reference connections by name in their schema.json storage config. The 'default' connection is used when no connection name is specified."
9
+ }
@@ -25,6 +25,11 @@
25
25
  "url": "/contact",
26
26
  "icon": "mail"
27
27
  },
28
+ {
29
+ "text": "Feedback",
30
+ "url": "/feedback",
31
+ "icon": "message-circle"
32
+ },
28
33
  {
29
34
  "text": "Resources",
30
35
  "url": "/resources",
@@ -64,6 +69,16 @@
64
69
  "text": "Interactive",
65
70
  "url": "/resources/interactive",
66
71
  "icon": "mouse-pointer"
72
+ },
73
+ {
74
+ "text": "Components",
75
+ "url": "/resources/components",
76
+ "icon": "layers"
77
+ },
78
+ {
79
+ "text": "Dependencies",
80
+ "url": "/resources/dependencies",
81
+ "icon": "package"
67
82
  }
68
83
  ]
69
84
  }
@@ -1,29 +1,19 @@
1
- {
2
- "example-analytics": {
3
- "enabled": true,
4
- "settings": {}
5
- },
6
- "form-builder": {
7
- "enabled": true,
8
- "settings": {
9
- "smtp": {
10
- "host": "localhost",
11
- "port": 1025,
12
- "secure": false,
13
- "user": "",
14
- "pass": ""
15
- },
16
- "fromAddress": "noreply@example.com",
17
- "fromName": "Website Forms"
18
- }
19
- },
20
- "domma-effects": {
21
- "enabled": true,
22
- "settings": {
23
- "respectMotion": false,
24
- "defaultDuration": 600,
25
- "defaultAnimation": "fade",
26
- "defaultThreshold": 0.1
27
- }
28
- }
29
- }
1
+ {
2
+ "example-analytics": {
3
+ "enabled": true,
4
+ "settings": {}
5
+ },
6
+ "theme-roller": {
7
+ "enabled": true,
8
+ "settings": {}
9
+ },
10
+ "domma-effects": {
11
+ "enabled": true,
12
+ "settings": {
13
+ "respectMotion": false,
14
+ "defaultDuration": 600,
15
+ "defaultAnimation": "fade",
16
+ "defaultThreshold": 0.1
17
+ }
18
+ }
19
+ }
@@ -1,6 +1,6 @@
1
- {
2
- "port": 80,
3
- "host": "0.0.0.0",
4
- "cors": { "origin": true },
5
- "uploads": { "maxFileSize": 10485760 }
6
- }
1
+ {
2
+ "port": 4096,
3
+ "host": "0.0.0.0",
4
+ "cors": { "origin": false },
5
+ "uploads": { "maxFileSize": 10485760 }
6
+ }
package/config/site.json CHANGED
@@ -3,8 +3,11 @@
3
3
  "tagline": "My Dead Good Site",
4
4
  "fontFamily": "Roboto",
5
5
  "fontSize": 16,
6
- "theme": "charcoal-dark",
6
+ "theme": "silver-dark",
7
7
  "adminTheme": "charcoal-dark",
8
+ "layoutOptions": {
9
+ "spacerSize": 40
10
+ },
8
11
  "seo": {
9
12
  "defaultTitle": "My Boss Site",
10
13
  "titleSeparator": " | ",
@@ -41,16 +44,17 @@
41
44
  "user": "",
42
45
  "pass": "",
43
46
  "secure": false,
44
- "fromAddress": "",
45
- "fromName": ""
47
+ "fromAddress": "noreply@dwit.uk",
48
+ "fromName": "My Big Boss Website"
46
49
  },
47
50
  "backToTop": {
48
51
  "enabled": true,
49
- "scrollThreshold": 150,
52
+ "scrollThreshold": 180,
50
53
  "position": "bottom-right",
54
+ "offset": 16,
55
+ "bottomOffset": 16,
51
56
  "label": "",
52
- "smooth": true,
53
- "offset": 48
57
+ "smooth": true
54
58
  },
55
59
  "cookieConsent": {
56
60
  "enabled": true,
@@ -70,5 +74,12 @@
70
74
  "showAnalytics": true,
71
75
  "showMarketing": true,
72
76
  "consentVersion": "1.0"
77
+ },
78
+ "breadcrumbs": {
79
+ "enabled": true,
80
+ "homeLabel": "Home",
81
+ "position": "TL",
82
+ "offsetX": 24,
83
+ "offsetY": 64
73
84
  }
74
85
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.2.1",
3
+ "version": "0.5.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",
@@ -10,25 +10,34 @@
10
10
  "files": [
11
11
  "bin/",
12
12
  "server/",
13
- "admin/",
14
- "public/",
13
+ "admin/js/",
14
+ "admin/css/",
15
+ "admin/index.html",
16
+ "admin/dist/domma/domma-tools.css",
17
+ "admin/dist/domma/domma-tools.min.js",
18
+ "public/js/",
19
+ "public/css/",
15
20
  "config/",
21
+ "!config/connections.json",
16
22
  "plugins/",
17
- "scripts/"
23
+ "scripts/",
24
+ "docs/",
25
+ "CHANGELOG.md"
18
26
  ],
19
27
  "scripts": {
20
28
  "build": "node scripts/build.js",
21
- "start": "pm2 start server/server.js -i max --name domma-cms",
22
- "dev": "PORT=3050 node --watch server/server.js",
29
+ "start": "node server/server.js",
30
+ "start:cluster": "pm2 start server/server.js -i max --name domma-cms",
31
+ "dev": "PORT=4096 node --watch server/server.js",
23
32
  "prod": "node server/server.js",
24
33
  "setup": "node scripts/setup.js",
25
34
  "reset": "node scripts/reset.js",
26
35
  "seed": "node scripts/seed.js",
27
36
  "fresh": "node scripts/fresh.js",
28
- "copy-domma": "node scripts/copy-domma.js"
37
+ "pro": "node scripts/pro.js"
29
38
  },
30
39
  "engines": {
31
- "node": ">=18.0.0"
40
+ "node": ">=18.17.0"
32
41
  },
33
42
  "keywords": [
34
43
  "cms",
@@ -51,13 +60,15 @@
51
60
  },
52
61
  "dependencies": {
53
62
  "@fastify/cors": "^11.2.0",
63
+ "@fastify/helmet": "^13.0.2",
54
64
  "@fastify/jwt": "^10.0.0",
55
65
  "@fastify/multipart": "^9.3.0",
66
+ "@fastify/rate-limit": "^10.3.0",
56
67
  "@fastify/static": "^8.1.0",
57
68
  "bcryptjs": "^3.0.3",
58
- "domma-js": "^0.19.1",
69
+ "domma-js": "^0.19.4",
59
70
  "dotenv": "^17.2.3",
60
- "fastify": "^5.7.3",
71
+ "fastify": "5.8.1",
61
72
  "gray-matter": "^4.0.3",
62
73
  "marked": "^15.0.0",
63
74
  "nodemailer": "^8.0.1",
@@ -65,6 +76,9 @@
65
76
  "sharp": "^0.34.5",
66
77
  "uuid": "^13.0.0"
67
78
  },
79
+ "optionalDependencies": {
80
+ "mongodb": "^6.21.0"
81
+ },
68
82
  "devDependencies": {
69
83
  "esbuild": "^0.27.3"
70
84
  }
@@ -1,30 +1,4 @@
1
- /**
2
- * Canvas Management Utilities
3
- * Handles canvas creation, resizing, and context management
4
- */
5
-
6
- export class CanvasManager {
7
- constructor(options = {}) {
8
- this.canvasId = options.canvasId || 'celebrations-canvas';
9
- this.zIndex = options.zIndex || 999;
10
- this.canvas = null;
11
- this.ctx = null;
12
- this.resizeTimeout = null;
13
- this.resizeCallback = options.onResize || null;
14
-
15
- // Bind methods
16
- this._handleResize = this._handleResize.bind(this);
17
- }
18
-
19
- /**
20
- * Create and append canvas to DOM
21
- */
22
- create() {
23
- if (this.canvas) return; // Already created
24
-
25
- this.canvas = document.createElement('canvas');
26
- this.canvas.id = this.canvasId;
27
- this.canvas.style.cssText = `
1
+ export class CanvasManager{constructor(e={}){this.canvasId=e.canvasId||"celebrations-canvas",this.zIndex=e.zIndex||999,this.canvas=null,this.ctx=null,this.resizeTimeout=null,this.resizeCallback=e.onResize||null,this._handleResize=this._handleResize.bind(this)}create(){this.canvas||(this.canvas=document.createElement("canvas"),this.canvas.id=this.canvasId,this.canvas.style.cssText=`
28
2
  position: fixed;
29
3
  top: 0;
30
4
  left: 0;
@@ -32,80 +6,4 @@ export class CanvasManager {
32
6
  height: 100vh;
33
7
  pointer-events: none;
34
8
  z-index: ${this.zIndex};
35
- `;
36
-
37
- this.ctx = this.canvas.getContext('2d', { alpha: true });
38
- this.resize();
39
- document.body.appendChild(this.canvas);
40
-
41
- // Bind resize listener
42
- window.addEventListener('resize', this._handleResize);
43
- }
44
-
45
- /**
46
- * Resize canvas to match window dimensions
47
- */
48
- resize() {
49
- if (!this.canvas) return;
50
-
51
- this.canvas.width = window.innerWidth;
52
- this.canvas.height = window.innerHeight;
53
- }
54
-
55
- /**
56
- * Clear entire canvas
57
- */
58
- clear() {
59
- if (!this.ctx) return;
60
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
61
- }
62
-
63
- /**
64
- * Get canvas dimensions
65
- */
66
- getDimensions() {
67
- return {
68
- width: this.canvas?.width || 0,
69
- height: this.canvas?.height || 0
70
- };
71
- }
72
-
73
- /**
74
- * Check if mobile device
75
- */
76
- isMobile() {
77
- return window.innerWidth < 768;
78
- }
79
-
80
- /**
81
- * Remove canvas from DOM and cleanup
82
- */
83
- destroy() {
84
- window.removeEventListener('resize', this._handleResize);
85
-
86
- if (this.canvas && this.canvas.parentNode) {
87
- this.canvas.parentNode.removeChild(this.canvas);
88
- }
89
-
90
- this.canvas = null;
91
- this.ctx = null;
92
- }
93
-
94
- /**
95
- * Handle window resize with debouncing
96
- * @private
97
- */
98
- _handleResize() {
99
- if (this.resizeTimeout) {
100
- clearTimeout(this.resizeTimeout);
101
- }
102
-
103
- this.resizeTimeout = setTimeout(() => {
104
- this.resize();
105
-
106
- if (this.resizeCallback) {
107
- this.resizeCallback();
108
- }
109
- }, 250);
110
- }
111
- }
9
+ `,this.ctx=this.canvas.getContext("2d",{alpha:!0}),this.resize(),document.body.appendChild(this.canvas),window.addEventListener("resize",this._handleResize))}resize(){this.canvas&&(this.canvas.width=window.innerWidth,this.canvas.height=window.innerHeight)}clear(){this.ctx&&this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)}getDimensions(){return{width:this.canvas?.width||0,height:this.canvas?.height||0}}isMobile(){return window.innerWidth<768}destroy(){window.removeEventListener("resize",this._handleResize),this.canvas&&this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas),this.canvas=null,this.ctx=null}_handleResize(){this.resizeTimeout&&clearTimeout(this.resizeTimeout),this.resizeTimeout=setTimeout(()=>{this.resize(),this.resizeCallback&&this.resizeCallback()},250)}}