domma-cms 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +2 -3
  2. package/admin/css/admin.css +78 -1
  3. package/admin/js/api.js +32 -0
  4. package/admin/js/app.js +24 -7
  5. package/admin/js/config/sidebar-config.js +8 -0
  6. package/admin/js/templates/collection-editor.html +80 -0
  7. package/admin/js/templates/collection-entries.html +36 -0
  8. package/admin/js/templates/collections.html +12 -0
  9. package/admin/js/templates/documentation.html +136 -0
  10. package/admin/js/templates/navigation.html +26 -4
  11. package/admin/js/templates/page-editor.html +91 -85
  12. package/admin/js/templates/settings.html +433 -172
  13. package/admin/js/views/collection-editor.js +487 -0
  14. package/admin/js/views/collection-entries.js +484 -0
  15. package/admin/js/views/collections.js +153 -0
  16. package/admin/js/views/dashboard.js +14 -6
  17. package/admin/js/views/index.js +9 -3
  18. package/admin/js/views/login.js +3 -2
  19. package/admin/js/views/navigation.js +77 -11
  20. package/admin/js/views/page-editor.js +207 -25
  21. package/admin/js/views/pages.js +14 -6
  22. package/admin/js/views/settings.js +137 -2
  23. package/admin/js/views/users.js +10 -7
  24. package/bin/cli.js +53 -17
  25. package/config/auth.json +2 -1
  26. package/config/content.json +1 -0
  27. package/config/navigation.json +14 -4
  28. package/config/plugins.json +0 -18
  29. package/config/presets.json +4 -8
  30. package/config/site.json +44 -3
  31. package/package.json +6 -2
  32. package/plugins/domma-effects/admin/templates/domma-effects.html +92 -3
  33. package/plugins/domma-effects/plugin.js +125 -0
  34. package/plugins/domma-effects/public/inject-body.html +19 -0
  35. package/plugins/example-analytics/admin/views/analytics.js +2 -2
  36. package/plugins/example-analytics/plugin.json +8 -0
  37. package/plugins/example-analytics/stats.json +15 -1
  38. package/plugins/form-builder/admin/templates/form-editor.html +19 -6
  39. package/plugins/form-builder/admin/views/form-editor.js +634 -9
  40. package/plugins/form-builder/admin/views/form-submissions.js +4 -4
  41. package/plugins/form-builder/admin/views/forms-list.js +5 -5
  42. package/plugins/form-builder/data/forms/consent.json +104 -0
  43. package/plugins/form-builder/data/forms/contacts.json +66 -0
  44. package/plugins/form-builder/data/submissions/consent.json +13 -0
  45. package/plugins/form-builder/data/submissions/contacts.json +26 -0
  46. package/plugins/form-builder/plugin.js +62 -11
  47. package/plugins/form-builder/plugin.json +12 -16
  48. package/plugins/form-builder/public/form-logic-engine.js +568 -0
  49. package/plugins/form-builder/public/inject-body.html +88 -6
  50. package/plugins/form-builder/public/inject-head.html +16 -0
  51. package/plugins/form-builder/public/package.json +1 -0
  52. package/public/css/site.css +113 -0
  53. package/public/js/btt.js +90 -0
  54. package/public/js/cookie-consent.js +61 -0
  55. package/public/js/site.js +129 -34
  56. package/scripts/build.js +129 -0
  57. package/scripts/seed.js +517 -7
  58. package/scripts/setup.js +12 -9
  59. package/server/routes/api/collections.js +301 -0
  60. package/server/routes/api/settings.js +66 -2
  61. package/server/server.js +19 -15
  62. package/server/services/collections.js +430 -0
  63. package/server/services/content.js +11 -2
  64. package/server/services/hooks.js +109 -0
  65. package/server/services/markdown.js +500 -149
  66. package/server/services/plugins.js +6 -1
  67. package/server/services/renderer.js +73 -7
  68. package/server/templates/page.html +38 -3
  69. package/plugins/back-to-top/admin/templates/back-to-top-settings.html +0 -55
  70. package/plugins/back-to-top/admin/views/back-to-top-settings.js +0 -44
  71. package/plugins/back-to-top/config.js +0 -10
  72. package/plugins/back-to-top/plugin.js +0 -24
  73. package/plugins/back-to-top/plugin.json +0 -36
  74. package/plugins/back-to-top/public/inject-body.html +0 -105
  75. package/plugins/cookie-consent/admin/templates/cookie-consent-settings.html +0 -113
  76. package/plugins/cookie-consent/admin/views/cookie-consent-settings.js +0 -73
  77. package/plugins/cookie-consent/config.js +0 -30
  78. package/plugins/cookie-consent/plugin.js +0 -24
  79. package/plugins/cookie-consent/plugin.json +0 -36
  80. package/plugins/cookie-consent/public/inject-body.html +0 -69
  81. package/plugins/custom-css/admin/templates/custom-css.html +0 -17
  82. package/plugins/custom-css/admin/views/custom-css.js +0 -35
  83. package/plugins/custom-css/config.js +0 -1
  84. package/plugins/custom-css/data/custom.css +0 -0
  85. package/plugins/custom-css/plugin.js +0 -63
  86. package/plugins/custom-css/plugin.json +0 -32
  87. package/plugins/custom-css/public/inject-head.html +0 -1
  88. package/plugins/form-builder/data/forms/contact.json +0 -52
  89. package/plugins/form-builder/data/submissions/contact.json +0 -14
@@ -1,15 +1,19 @@
1
1
  /**
2
2
  * Settings View
3
3
  */
4
- import { api } from '../api.js';
4
+ import {api, apiRequest} from '../api.js';
5
5
 
6
6
  export const settingsView = {
7
7
  templateUrl: '/admin/js/templates/settings.html',
8
8
 
9
9
  async onMount($container) {
10
+ E.tabs($container.find('#settings-tabs').get(0));
11
+
10
12
  const s = await api.settings.get().catch(() => ({}));
11
13
  $container.find('#field-site-title').val(s.title || '');
12
14
  $container.find('#field-tagline').val(s.tagline || '');
15
+ $container.find('#field-font-family').val(s.fontFamily || 'Roboto');
16
+ $container.find('#field-font-size').val(s.fontSize || 16);
13
17
  $container.find('#field-theme').val(s.theme || 'charcoal-dark');
14
18
  $container.find('#field-admin-theme').val(s.adminTheme || 'charcoal-dark');
15
19
  $container.find('#field-seo-title').val(s.seo?.defaultTitle || '');
@@ -17,7 +21,14 @@ export const settingsView = {
17
21
  $container.find('#field-seo-desc').val(s.seo?.defaultDescription || '');
18
22
  $container.find('#field-footer-copy').val(s.footer?.copyright || '');
19
23
 
20
- // SMTP fields
24
+ // Social fields
25
+ $container.find('#field-social-twitter').val(s.social?.twitter || '');
26
+ $container.find('#field-social-facebook').val(s.social?.facebook || '');
27
+ $container.find('#field-social-instagram').val(s.social?.instagram || '');
28
+ $container.find('#field-social-linkedin').val(s.social?.linkedin || '');
29
+ $container.find('#field-social-github').val(s.social?.github || '');
30
+ $container.find('#field-social-youtube').val(s.social?.youtube || '');
31
+
21
32
  $container.find('#field-smtp-host').val(s.smtp?.host || '');
22
33
  $container.find('#field-smtp-port').val(s.smtp?.port || 587);
23
34
  $container.find('#field-smtp-user').val(s.smtp?.user || '');
@@ -26,11 +37,50 @@ export const settingsView = {
26
37
  $container.find('#field-smtp-from-address').val(s.smtp?.fromAddress || '');
27
38
  $container.find('#field-smtp-from-name').val(s.smtp?.fromName || '');
28
39
 
40
+ // Back to Top
41
+ const btt = s.backToTop || {};
42
+ $container.find('#field-btt-enabled').prop('checked', btt.enabled !== false);
43
+ $container.find('#field-btt-threshold').val(btt.scrollThreshold ?? 300);
44
+ $container.find('#field-btt-position').val(btt.position || 'bottom-right');
45
+ $container.find('#field-btt-offset').val(btt.offset ?? 32);
46
+ $container.find('#field-btt-label').val(btt.label || '');
47
+ $container.find('#field-btt-smooth').prop('checked', btt.smooth !== false);
48
+
49
+ // Cookie Consent
50
+ const cc = s.cookieConsent || {};
51
+ $container.find('#field-cc-enabled').prop('checked', cc.enabled !== false);
52
+ $container.find('#field-cc-message').val(cc.message || '');
53
+ $container.find('#field-cc-accept-all').val(cc.acceptAllText || 'Accept All');
54
+ $container.find('#field-cc-reject-all').val(cc.rejectAllText || 'Reject All');
55
+ $container.find('#field-cc-customize').val(cc.customizeText || 'Customize');
56
+ $container.find('#field-cc-save-prefs').val(cc.savePreferencesText || 'Save Preferences');
57
+ $container.find('#field-cc-privacy-text').val(cc.privacyPolicyText || 'Privacy Policy');
58
+ $container.find('#field-cc-privacy-url').val(cc.privacyPolicyUrl || '');
59
+ $container.find('#field-cc-cookie-text').val(cc.cookiePolicyText || 'Cookie Policy');
60
+ $container.find('#field-cc-cookie-url').val(cc.cookiePolicyUrl || '');
61
+ $container.find('#field-cc-position').val(cc.position || 'bottom');
62
+ $container.find('#field-cc-layout').val(cc.layout || 'bar');
63
+ $container.find('#field-cc-theme').val(cc.theme || 'dark');
64
+ $container.find('#field-cc-show-functional').prop('checked', cc.showFunctional !== false);
65
+ $container.find('#field-cc-show-analytics').prop('checked', cc.showAnalytics !== false);
66
+ $container.find('#field-cc-show-marketing').prop('checked', cc.showMarketing !== false);
67
+ $container.find('#field-cc-version').val(cc.consentVersion || '1.0');
68
+
69
+ // Custom CSS
70
+ try {
71
+ const { css } = await apiRequest('/settings/custom-css');
72
+ $container.find('#field-custom-css').val(css || '');
73
+ } catch {
74
+ // non-fatal — textarea stays empty
75
+ }
76
+
29
77
  $container.find('#save-settings-btn').on('click', async () => {
30
78
  const adminTheme = $container.find('#field-admin-theme').val();
31
79
  const data = {
32
80
  title: $container.find('#field-site-title').val().trim(),
33
81
  tagline: $container.find('#field-tagline').val().trim(),
82
+ fontFamily: $container.find('#field-font-family').val() || 'Roboto',
83
+ fontSize: parseInt($container.find('#field-font-size').val(), 10) || 16,
34
84
  theme: $container.find('#field-theme').val(),
35
85
  adminTheme,
36
86
  seo: {
@@ -42,6 +92,14 @@ export const settingsView = {
42
92
  copyright: $container.find('#field-footer-copy').val().trim(),
43
93
  links: s.footer?.links || []
44
94
  },
95
+ social: {
96
+ twitter: $container.find('#field-social-twitter').val().trim(),
97
+ facebook: $container.find('#field-social-facebook').val().trim(),
98
+ instagram: $container.find('#field-social-instagram').val().trim(),
99
+ linkedin: $container.find('#field-social-linkedin').val().trim(),
100
+ github: $container.find('#field-social-github').val().trim(),
101
+ youtube: $container.find('#field-social-youtube').val().trim()
102
+ },
45
103
  smtp: {
46
104
  host: $container.find('#field-smtp-host').val().trim(),
47
105
  port: parseInt($container.find('#field-smtp-port').val(), 10) || 587,
@@ -50,14 +108,91 @@ export const settingsView = {
50
108
  secure: $container.find('#field-smtp-secure').prop('checked'),
51
109
  fromAddress: $container.find('#field-smtp-from-address').val().trim(),
52
110
  fromName: $container.find('#field-smtp-from-name').val().trim()
111
+ },
112
+ backToTop: {
113
+ enabled: $container.find('#field-btt-enabled').prop('checked'),
114
+ scrollThreshold: parseInt($container.find('#field-btt-threshold').val(), 10) || 300,
115
+ position: $container.find('#field-btt-position').val() || 'bottom-right',
116
+ offset: parseInt($container.find('#field-btt-offset').val(), 10) || 32,
117
+ label: $container.find('#field-btt-label').val().trim(),
118
+ smooth: $container.find('#field-btt-smooth').prop('checked')
119
+ },
120
+ cookieConsent: {
121
+ enabled: $container.find('#field-cc-enabled').prop('checked'),
122
+ message: $container.find('#field-cc-message').val().trim(),
123
+ acceptAllText: $container.find('#field-cc-accept-all').val().trim(),
124
+ rejectAllText: $container.find('#field-cc-reject-all').val().trim(),
125
+ customizeText: $container.find('#field-cc-customize').val().trim(),
126
+ savePreferencesText: $container.find('#field-cc-save-prefs').val().trim(),
127
+ privacyPolicyText: $container.find('#field-cc-privacy-text').val().trim(),
128
+ privacyPolicyUrl: $container.find('#field-cc-privacy-url').val().trim(),
129
+ cookiePolicyText: $container.find('#field-cc-cookie-text').val().trim(),
130
+ cookiePolicyUrl: $container.find('#field-cc-cookie-url').val().trim(),
131
+ position: $container.find('#field-cc-position').val(),
132
+ layout: $container.find('#field-cc-layout').val(),
133
+ theme: $container.find('#field-cc-theme').val(),
134
+ showFunctional: $container.find('#field-cc-show-functional').prop('checked'),
135
+ showAnalytics: $container.find('#field-cc-show-analytics').prop('checked'),
136
+ showMarketing: $container.find('#field-cc-show-marketing').prop('checked'),
137
+ consentVersion: $container.find('#field-cc-version').val().trim() || '1.0'
53
138
  }
54
139
  };
140
+ const $btn = $container.find('#save-settings-btn');
141
+ $btn.prop('disabled', true);
55
142
  try {
56
143
  await api.settings.save(data);
57
144
  Domma.theme.set(adminTheme);
58
145
  E.toast('Settings saved.', { type: 'success' });
59
146
  } catch {
60
147
  E.toast('Failed to save settings.', { type: 'error' });
148
+ } finally {
149
+ $btn.prop('disabled', false);
150
+ }
151
+ });
152
+
153
+ // Send test email
154
+ $container.find('#send-test-email-btn').on('click', async () => {
155
+ const to = $container.find('#field-test-email-to').val().trim();
156
+ const resultEl = $container.find('#test-email-result').get(0);
157
+ const $btn = $container.find('#send-test-email-btn');
158
+
159
+ $btn.prop('disabled', true);
160
+ if (resultEl) { resultEl.textContent = 'Sending…'; resultEl.style.color = ''; }
161
+
162
+ try {
163
+ const res = await apiRequest('/settings/test-email', {
164
+ method: 'POST',
165
+ body: JSON.stringify({ to: to || undefined })
166
+ });
167
+ if (resultEl) {
168
+ resultEl.textContent = res.message || 'Test email sent.';
169
+ resultEl.style.color = 'var(--success,#4ade80)';
170
+ }
171
+ } catch (err) {
172
+ if (resultEl) {
173
+ resultEl.textContent = err.message || 'Failed to send test email.';
174
+ resultEl.style.color = 'var(--danger,#f87171)';
175
+ }
176
+ } finally {
177
+ $btn.prop('disabled', false);
178
+ }
179
+ });
180
+
181
+ // Save Custom CSS
182
+ $container.find('#save-css-btn').on('click', async () => {
183
+ const css = $container.find('#field-custom-css').val();
184
+ const $btn = $container.find('#save-css-btn');
185
+ $btn.prop('disabled', true);
186
+ try {
187
+ await apiRequest('/settings/custom-css', {
188
+ method: 'PUT',
189
+ body: JSON.stringify({ css })
190
+ });
191
+ E.toast('Custom CSS saved.', { type: 'success' });
192
+ } catch (err) {
193
+ E.toast(err.message || 'Failed to save CSS.', { type: 'error' });
194
+ } finally {
195
+ $btn.prop('disabled', false);
61
196
  }
62
197
  });
63
198
  }
@@ -2,7 +2,7 @@
2
2
  * Users List View
3
3
  * Shows all users in a Domma table with role badges.
4
4
  */
5
- import { api, getUser } from '../api.js';
5
+ import {api, getUser} from '../api.js';
6
6
 
7
7
  const ROLE_BADGE = {
8
8
  admin: 'badge-danger',
@@ -22,18 +22,21 @@ export const usersView = {
22
22
  T.create('#users-table', {
23
23
  data,
24
24
  columns: [
25
- { key: 'name', label: 'Name' },
26
- { key: 'email', label: 'Email' },
27
- { key: 'role', label: 'Role',
25
+ {key: 'name', title: 'Name'},
26
+ {key: 'email', title: 'Email'},
27
+ {
28
+ key: 'role', title: 'Role',
28
29
  render: (val) => `<span class="badge ${ROLE_BADGE[val] || 'badge-secondary'}">${val}</span>` },
29
- { key: 'isActive', label: 'Status',
30
+ {
31
+ key: 'isActive', title: 'Status',
30
32
  render: (val) => val
31
33
  ? '<span class="badge badge-success">Active</span>'
32
34
  : '<span class="badge badge-secondary">Inactive</span>' },
33
- { key: 'lastLogin', label: 'Last login',
35
+ {
36
+ key: 'lastLogin', title: 'Last login',
34
37
  render: (val) => val ? D(val).format('DD MMM YYYY HH:mm') : 'Never' },
35
38
  {
36
- key: 'actions', label: 'Actions',
39
+ key: 'actions', title: 'Actions',
37
40
  render: (_, row) => {
38
41
  const isSelf = row.id === currentUser?.id;
39
42
  return `
package/bin/cli.js CHANGED
@@ -42,7 +42,7 @@ if (flags.has('--help') || args.includes('-h')) {
42
42
  Options:
43
43
  --no-install Skip npm install
44
44
  --no-setup Skip the interactive setup wizard
45
- --seed Run npm run seed after setup
45
+ --no-seed Skip seeding default pages, forms, and collections
46
46
  --help Show this help message
47
47
 
48
48
  Example:
@@ -82,7 +82,7 @@ if (existsSync(target)) {
82
82
 
83
83
  const noInstall = flags.has('--no-install');
84
84
  const noSetup = flags.has('--no-setup') || noInstall;
85
- const withSeed = flags.has('--seed');
85
+ const noSeed = flags.has('--no-seed');
86
86
 
87
87
  // ---------------------------------------------------------------------------
88
88
  // Banner
@@ -152,8 +152,26 @@ done();
152
152
  // ---------------------------------------------------------------------------
153
153
 
154
154
  step('Resetting plugin data');
155
- writeFileSync(path.join(target, 'plugins/example-analytics/stats.json'), '{}\n', 'utf8');
156
- writeFileSync(path.join(target, 'plugins/form-builder/data/submissions/contact.json'), '[]\n', 'utf8');
155
+ // Read scaffold.reset entries from each plugin.json and apply them
156
+ const copiedPluginsDir = path.join(target, 'plugins');
157
+ try {
158
+ const pluginDirs = readdirSync(copiedPluginsDir, {withFileTypes: true})
159
+ .filter(e => e.isDirectory())
160
+ .map(e => e.name);
161
+ for (const pluginName of pluginDirs) {
162
+ const manifestPath = path.join(copiedPluginsDir, pluginName, 'plugin.json');
163
+ try {
164
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
165
+ for (const {path: relPath, content} of (manifest.scaffold?.reset || [])) {
166
+ const absPath = path.join(copiedPluginsDir, pluginName, relPath);
167
+ mkdirSync(path.dirname(absPath), {recursive: true});
168
+ writeFileSync(absPath, content + '\n', 'utf8');
169
+ }
170
+ } catch { /* missing or invalid plugin.json — skip */
171
+ }
172
+ }
173
+ } catch { /* plugins dir not found — skip */
174
+ }
157
175
  done();
158
176
 
159
177
  // ---------------------------------------------------------------------------
@@ -189,14 +207,29 @@ const DEFAULT_NAV = {
189
207
  position: 'sticky'
190
208
  };
191
209
 
192
- // All plugins present but disabled by default in a fresh project
193
- const DEFAULT_PLUGINS = {
194
- 'example-analytics': {enabled: false, settings: {}},
195
- 'contact-form': {enabled: false, settings: {}},
196
- 'form-builder': {enabled: false, settings: {}},
197
- 'back-to-top': {enabled: false, settings: {}},
198
- 'cookie-consent': {enabled: false, settings: {}}
199
- };
210
+ // Plugins enabled by default in a fresh project
211
+ const ENABLED_BY_DEFAULT = new Set([
212
+ 'domma-effects',
213
+ 'form-builder',
214
+ ]);
215
+
216
+ // Discover all plugins and set enabled state accordingly
217
+ const DEFAULT_PLUGINS = {};
218
+ try {
219
+ const pluginsRoot = path.join(target, 'plugins');
220
+ const pluginDirs = readdirSync(pluginsRoot, {withFileTypes: true})
221
+ .filter(e => e.isDirectory())
222
+ .map(e => e.name);
223
+ for (const name of pluginDirs) {
224
+ const manifestPath = path.join(pluginsRoot, name, 'plugin.json');
225
+ try {
226
+ JSON.parse(readFileSync(manifestPath, 'utf8')); // validate manifest exists
227
+ DEFAULT_PLUGINS[name] = {enabled: ENABLED_BY_DEFAULT.has(name), settings: {}};
228
+ } catch { /* no valid plugin.json — skip */
229
+ }
230
+ }
231
+ } catch { /* plugins dir not found — leave empty */
232
+ }
200
233
 
201
234
  step('Writing default config');
202
235
  writeFileSync(path.join(target, 'config/site.json'), JSON.stringify(DEFAULT_SITE, null, 4) + '\n', 'utf8');
@@ -210,6 +243,7 @@ done();
210
243
 
211
244
  step('Creating content directories');
212
245
  mkdirSync(path.join(target, 'content/pages'), {recursive: true});
246
+ mkdirSync(path.join(target, 'content/collections'), {recursive: true});
213
247
  mkdirSync(path.join(target, 'content/media'), {recursive: true});
214
248
  mkdirSync(path.join(target, 'content/users'), {recursive: true});
215
249
  done();
@@ -301,13 +335,15 @@ if (noSetup) {
301
335
  }
302
336
 
303
337
  // ---------------------------------------------------------------------------
304
- // Optional seed
338
+ // Seed default content (pages, forms, collections)
305
339
  // ---------------------------------------------------------------------------
306
340
 
307
- if (withSeed) {
308
- console.log(' Running seed (npm run seed)');
341
+ if (noSeed) {
342
+ console.log(' ⚠ Skipping seed (--no-seed).');
343
+ } else {
344
+ console.log(' Seeding default pages, forms and collections…');
309
345
  console.log('');
310
- const result = spawnSync('npm', ['run', 'seed'], {cwd: target, stdio: 'inherit'});
346
+ const result = spawnSync(process.execPath, ['scripts/seed.js'], {cwd: target, stdio: 'inherit'});
311
347
  if (result.status !== 0) {
312
348
  console.error('\n ✗ Seed failed.\n');
313
349
  process.exit(result.status ?? 1);
@@ -327,7 +363,7 @@ console.log('');
327
363
  console.log(` Next steps:`);
328
364
  console.log(` cd ${projectName}`);
329
365
  if (noInstall) console.log(` npm install`);
330
- if (noSetup) console.log(` npm run setup`);
366
+ if (noSetup && !noInstall) console.log(` npm run setup`);
331
367
  console.log(` npm run dev`);
332
368
  console.log('');
333
369
  console.log(` Then open: http://localhost:3050/admin`);
package/config/auth.json CHANGED
@@ -15,6 +15,7 @@
15
15
  "layouts": ["admin", "manager"],
16
16
  "media": ["admin", "manager", "editor"],
17
17
  "users": ["admin", "manager"],
18
- "plugins": ["admin"]
18
+ "plugins": ["admin"],
19
+ "collections": ["admin", "manager"]
19
20
  }
20
21
  }
@@ -2,6 +2,7 @@
2
2
  "contentDir": "./content",
3
3
  "mediaDir": "./content/media",
4
4
  "usersDir": "./content/users",
5
+ "collectionsDir": "./content/collections",
5
6
  "pageDefaults": {
6
7
  "layout": "default",
7
8
  "status": "draft",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "brand": {
3
- "text": "My Site",
4
- "logo": null,
5
- "url": "/"
3
+ "text": "My Dead Good Site",
4
+ "url": "/",
5
+ "icon": "home"
6
6
  },
7
7
  "items": [
8
8
  {
@@ -54,10 +54,20 @@
54
54
  "text": "Shortcode Reference",
55
55
  "url": "/resources/shortcodes",
56
56
  "icon": "code"
57
+ },
58
+ {
59
+ "text": "Effects",
60
+ "url": "/resources/effects",
61
+ "icon": "zap"
62
+ },
63
+ {
64
+ "text": "Interactive",
65
+ "url": "/resources/interactive",
66
+ "icon": "mouse-pointer"
57
67
  }
58
68
  ]
59
69
  }
60
70
  ],
61
- "variant": "dark",
71
+ "variant": "transparent",
62
72
  "position": "sticky"
63
73
  }
@@ -17,24 +17,6 @@
17
17
  "fromName": "Website Forms"
18
18
  }
19
19
  },
20
- "back-to-top": {
21
- "enabled": true,
22
- "settings": {
23
- "scrollThreshold": 150,
24
- "position": "bottom-right",
25
- "label": "",
26
- "smooth": true,
27
- "offset": 32
28
- }
29
- },
30
- "cookie-consent": {
31
- "enabled": true,
32
- "settings": {}
33
- },
34
- "custom-css": {
35
- "enabled": true,
36
- "settings": {}
37
- },
38
20
  "domma-effects": {
39
21
  "enabled": true,
40
22
  "settings": {
@@ -4,31 +4,27 @@
4
4
  "description": "Standard page with navbar and footer",
5
5
  "navbar": true,
6
6
  "footer": true,
7
- "sidebar": false,
8
- "features": ["back-to-top", "icon-scan"]
7
+ "sidebar": false
9
8
  },
10
9
  "with-sidebar": {
11
10
  "label": "With Sidebar",
12
11
  "description": "Page with navbar, sidebar, and footer",
13
12
  "navbar": true,
14
13
  "footer": true,
15
- "sidebar": true,
16
- "features": ["back-to-top", "icon-scan", "scroll-spy"]
14
+ "sidebar": true
17
15
  },
18
16
  "minimal": {
19
17
  "label": "Minimal",
20
18
  "description": "Clean page with no navbar or footer",
21
19
  "navbar": false,
22
20
  "footer": false,
23
- "sidebar": false,
24
- "features": []
21
+ "sidebar": false
25
22
  },
26
23
  "landing": {
27
24
  "label": "Landing Page",
28
25
  "description": "Full-width landing page layout",
29
26
  "navbar": true,
30
27
  "footer": true,
31
- "sidebar": false,
32
- "features": ["back-to-top", "icon-scan"]
28
+ "sidebar": false
33
29
  }
34
30
  }
package/config/site.json CHANGED
@@ -1,7 +1,9 @@
1
1
  {
2
2
  "title": "My Boss Site",
3
- "tagline": "My Dead Good Boss Site",
4
- "theme": "silver-dark",
3
+ "tagline": "My Dead Good Site",
4
+ "fontFamily": "Roboto",
5
+ "fontSize": 16,
6
+ "theme": "charcoal-dark",
5
7
  "adminTheme": "charcoal-dark",
6
8
  "seo": {
7
9
  "defaultTitle": "My Boss Site",
@@ -9,7 +11,7 @@
9
11
  "defaultDescription": "A site built with Domma CMS"
10
12
  },
11
13
  "footer": {
12
- "copyright": "© 2026 DCBW Consulting Ltd.",
14
+ "copyright": "© 2026 DCBW Consulting Ltd",
13
15
  "links": [
14
16
  {
15
17
  "text": "Privacy Policy",
@@ -18,9 +20,21 @@
18
20
  {
19
21
  "text": "Contact",
20
22
  "url": "/contact"
23
+ },
24
+ {
25
+ "text": "GDPR",
26
+ "url": "/gdpr"
21
27
  }
22
28
  ]
23
29
  },
30
+ "social": {
31
+ "twitter": "",
32
+ "facebook": "",
33
+ "instagram": "",
34
+ "linkedin": "",
35
+ "github": "https://github.com/pinpointzero73/",
36
+ "youtube": ""
37
+ },
24
38
  "smtp": {
25
39
  "host": "localhost",
26
40
  "port": 1025,
@@ -29,5 +43,32 @@
29
43
  "secure": false,
30
44
  "fromAddress": "",
31
45
  "fromName": ""
46
+ },
47
+ "backToTop": {
48
+ "enabled": true,
49
+ "scrollThreshold": 150,
50
+ "position": "bottom-right",
51
+ "label": "",
52
+ "smooth": true,
53
+ "offset": 48
54
+ },
55
+ "cookieConsent": {
56
+ "enabled": true,
57
+ "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.",
58
+ "acceptAllText": "Accept All",
59
+ "rejectAllText": "Reject All",
60
+ "customizeText": "Customize",
61
+ "savePreferencesText": "Save Preferences",
62
+ "privacyPolicyText": "Privacy Policy",
63
+ "privacyPolicyUrl": "/privacy-policy",
64
+ "cookiePolicyText": "Cookie Policy",
65
+ "cookiePolicyUrl": "",
66
+ "position": "bottom",
67
+ "layout": "bar",
68
+ "theme": "dark",
69
+ "showFunctional": true,
70
+ "showAnalytics": true,
71
+ "showMarketing": true,
72
+ "consentVersion": "1.0"
32
73
  }
33
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
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",
@@ -17,6 +17,7 @@
17
17
  "scripts/"
18
18
  ],
19
19
  "scripts": {
20
+ "build": "node scripts/build.js",
20
21
  "start": "pm2 start server/server.js -i max --name domma-cms",
21
22
  "dev": "PORT=3050 node --watch server/server.js",
22
23
  "prod": "node server/server.js",
@@ -54,7 +55,7 @@
54
55
  "@fastify/multipart": "^9.3.0",
55
56
  "@fastify/static": "^8.1.0",
56
57
  "bcryptjs": "^3.0.3",
57
- "domma-js": "^0.18.2",
58
+ "domma-js": "^0.19.1",
58
59
  "dotenv": "^17.2.3",
59
60
  "fastify": "^5.7.3",
60
61
  "gray-matter": "^4.0.3",
@@ -63,5 +64,8 @@
63
64
  "sanitize-html": "^2.17.0",
64
65
  "sharp": "^0.34.5",
65
66
  "uuid": "^13.0.0"
67
+ },
68
+ "devDependencies": {
69
+ "esbuild": "^0.27.3"
66
70
  }
67
71
  }