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
package/scripts/seed.js DELETED
@@ -1,1996 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Domma CMS — Seed Demo Content
4
- * Creates sample pages and updates navigation.
5
- * Safe to run multiple times — overwrites seed paths only.
6
- * Run: npm run seed
7
- */
8
- import {mkdir, writeFile} from 'node:fs/promises';
9
- import path from 'node:path';
10
- import {fileURLToPath} from 'node:url';
11
-
12
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
- const ROOT = path.resolve(__dirname, '..');
14
-
15
- const PAGES_DIR = path.join(ROOT, 'content', 'pages');
16
- const COLLECTIONS_DIR = path.join(ROOT, 'content', 'collections');
17
- const FORMS_DIR = path.join(ROOT, 'plugins', 'form-builder', 'data', 'forms');
18
- const SUBS_DIR = path.join(ROOT, 'plugins', 'form-builder', 'data', 'submissions');
19
- const NAV_CFG = path.join(ROOT, 'config', 'navigation.json');
20
-
21
- const now = new Date().toISOString();
22
-
23
- // ---------------------------------------------------------------------------
24
- // Seed navigation
25
- // ---------------------------------------------------------------------------
26
-
27
- const SEED_NAV = {
28
- brand: { text: 'My Site', logo: null, url: '/' },
29
- items: [
30
- { text: 'Home', url: '/', icon: 'home' },
31
- { text: 'About', url: '/about', icon: 'info' },
32
- { text: 'Blog', url: '/blog', icon: 'book-open' },
33
- {text: 'Contact', url: '/contact', icon: 'mail'},
34
- {text: 'Feedback', url: '/feedback', icon: 'message-square'},
35
- {
36
- text: 'Resources', url: '/resources', icon: 'book-open',
37
- items: [
38
- {text: 'Overview', url: '/resources', icon: 'grid'},
39
- {text: 'Typography', url: '/resources/typography', icon: 'type'},
40
- {text: 'Grid System', url: '/resources/grid', icon: 'layout'},
41
- {text: 'Cards', url: '/resources/cards', icon: 'credit-card'},
42
- {text: 'Shortcode Reference', url: '/resources/shortcodes', icon: 'code'},
43
- {text: 'Effects', url: '/resources/effects', icon: 'zap'},
44
- {text: 'Interactive', url: '/resources/interactive', icon: 'mouse-pointer'}
45
- ]
46
- }
47
- ],
48
- variant: 'dark',
49
- position: 'sticky'
50
- };
51
-
52
- // ---------------------------------------------------------------------------
53
- // Page definitions
54
- // ---------------------------------------------------------------------------
55
-
56
- const PAGES = [
57
- {
58
- file: 'index.md',
59
- fm: {
60
- title: 'Welcome', slug: 'index',
61
- description: 'Your new website, powered by Domma CMS',
62
- layout: 'landing', status: 'published',
63
- sortOrder: 1, showInNav: false, sidebar: false,
64
- seo: { title: 'Welcome', description: 'Your new website, powered by Domma CMS' },
65
- createdAt: now, updatedAt: now
66
- },
67
- body: `[hero fullwidth="true" variant="dark" size="lg" align="left" twinkle twinkle-count="30" blobs]
68
-
69
- <span class="hero-label">File-Based CMS</span>
70
-
71
- # [scribe speed="60" cursor="true"]The CMS that gets out of your way.[/scribe]
72
-
73
- No databases. No bloat. Just Markdown, shortcodes, and a clean admin panel — content management the way it should be.
74
-
75
- <div class="hero-cta"><a href="/admin/">Sign in to Admin →</a> <a href="/resources/">Browse Resources →</a></div>
76
-
77
- [/hero]
78
-
79
- [reveal animation="fade" delay="200"]
80
-
81
- [grid cols="4" gap="4"]
82
-
83
- [col]
84
- [card]
85
- [counter to="25" suffix="+" /]
86
- **Shortcodes**
87
- Powerful content blocks built right in.
88
- [/card]
89
- [/col]
90
-
91
- [col]
92
- [card]
93
- [counter to="22" suffix="" /]
94
- **Themes**
95
- Light and dark colour schemes, zero configuration.
96
- [/card]
97
- [/col]
98
-
99
- [col]
100
- [card]
101
- [counter to="3" suffix="" /]
102
- **Plugins**
103
- Extend with effects, analytics, and form handling.
104
- [/card]
105
- [/col]
106
-
107
- [col]
108
- [card]
109
- [counter to="100" suffix="%" /]
110
- **File-based**
111
- Your content lives as plain files — always portable.
112
- [/card]
113
- [/col]
114
-
115
- [/grid]
116
-
117
- [/reveal]
118
-
119
- ## Features
120
-
121
- [reveal animation="slide-up" delay="200"]
122
- [grid cols="3" gap="4"]
123
-
124
- [col]
125
- [card title="Markdown Pages" icon="file-text" icon-layout="stacked" hover]
126
- Write content in plain Markdown with YAML frontmatter. Add shortcodes anywhere for rich layouts — no HTML required.
127
- [/card]
128
- [/col]
129
-
130
- [col]
131
- [card title="Shortcode System" icon="code" icon-layout="stacked" hover]
132
- From heroes to carousels, counters to accordions — drop expressive shortcodes directly into your Markdown.
133
- [/card]
134
- [/col]
135
-
136
- [col]
137
- [card title="Plugin Architecture" icon="code-block" icon-layout="stacked" hover]
138
- Add effects, analytics, and forms through a clean plugin API. Each plugin ships its own routes, config, and admin UI.
139
- [/card]
140
- [/col]
141
-
142
- [/grid]
143
- [/reveal]
144
-
145
- [spacer size="24" /]
146
-
147
- [reveal animation="slide-up" delay="200"]
148
- [hero variant="gradient-purple" size="md" fullwidth="true"]
149
-
150
- ## Ready to build?
151
-
152
- [grid cols="2" gap="3"]
153
-
154
- [col]
155
- [ambient color="#a855f7"]
156
- **[Sign in to the admin panel →](/admin/)**
157
- Create pages, manage navigation, and configure your site.
158
- [/ambient]
159
- [/col]
160
-
161
- [col]
162
- [ambient color="#6366f1"]
163
- **[Browse the resources →](/resources/)**
164
- Guides, shortcode references, and plugin documentation.
165
- [/ambient]
166
- [/col]
167
-
168
- [/grid]
169
-
170
- [/hero]
171
- [/reveal]
172
- `
173
- },
174
- {
175
- file: 'about.md',
176
- fm: {
177
- title: 'About', slug: 'about',
178
- description: 'Learn more about us',
179
- layout: 'default', status: 'published',
180
- sortOrder: 2, showInNav: true, sidebar: false,
181
- seo: { title: 'About', description: 'Learn more about us' },
182
- createdAt: now, updatedAt: now
183
- },
184
- body: `[hero variant="dark" size="lg" fullwidth="true" twinkle twinkle-count="30" blobs]
185
-
186
- # Build the web your way.
187
-
188
- [scribe speed="35" cursor="true"]No databases. No lock-in. Just files, Markdown, and a CMS that stays out of your way.[/scribe]
189
-
190
- [/hero]
191
-
192
- [spacer /]
193
-
194
- ## What Domma CMS gives you
195
-
196
- [reveal animation="slide-up"]
197
-
198
- [grid cols="3" gap="4"]
199
- [col]
200
- [card title="25 Shortcodes"]
201
- Layout blocks, scroll reveals, counters, heroes, accordions, typewriters — all available directly inside your Markdown. No HTML required.
202
- [/card]
203
- [/col]
204
- [col]
205
- [card title="22 Themes"]
206
- Dark and light variants across 11 carefully crafted palettes. Switch instantly from the admin panel — no rebuild, no redeployment.
207
- [/card]
208
- [/col]
209
- [col]
210
- [card title="3 Plugins"]
211
- Analytics, smart forms, and visual effects — ready to enable with a toggle. Extend your site without touching a line of code.
212
- [/card]
213
- [/col]
214
- [/grid]
215
-
216
- [/reveal]
217
-
218
- [spacer /]
219
-
220
- ## By the numbers
221
-
222
- [reveal animation="zoom"]
223
-
224
- [grid cols="4" gap="4"]
225
- [col]
226
- <div style="text-align:center;padding:1.5rem 0">
227
-
228
- ## [counter to="25" suffix="+" /]
229
-
230
- Shortcodes
231
-
232
- </div>
233
- [/col]
234
- [col]
235
- <div style="text-align:center;padding:1.5rem 0">
236
-
237
- ## [counter to="22" /]
238
-
239
- Themes
240
-
241
- </div>
242
- [/col]
243
- [col]
244
- <div style="text-align:center;padding:1.5rem 0">
245
-
246
- ## [counter to="3" /]
247
-
248
- Plugins
249
-
250
- </div>
251
- [/col]
252
- [col]
253
- <div style="text-align:center;padding:1.5rem 0">
254
-
255
- ## [counter to="100" suffix="%" /]
256
-
257
- Markdown
258
-
259
- </div>
260
- [/col]
261
- [/grid]
262
-
263
- [/reveal]
264
-
265
- [spacer /]
266
-
267
- [breathe]
268
- ## Our philosophy
269
- [/breathe]
270
-
271
- [reveal animation="twinkle"]
272
-
273
- Content belongs in files — not databases. Domma CMS is built on the conviction that a great publishing tool should be transparent, portable, and free from framework lock-in. Every page is a Markdown file. Every setting is plain JSON. Your content was yours before you installed Domma, and it will still be yours long after.
274
-
275
- [/reveal]
276
-
277
- [spacer size="32" /]
278
-
279
- [breathe]
280
- [card title="Explore the shortcode reference"]
281
- Domma ships with 25 shortcodes covering layout, animation, interactivity, and more — all usable straight from the Markdown editor.
282
-
283
- [Browse the full reference &rarr;](/resources/)
284
- [/card]
285
- [/breathe]
286
- `
287
- },
288
- {
289
- file: 'contact.md',
290
- fm: {
291
- title: 'Contact', slug: 'contact',
292
- description: 'Get in touch',
293
- layout: 'default', status: 'published',
294
- sortOrder: 4, showInNav: true, sidebar: false,
295
- seo: { title: 'Contact Us', description: 'Get in touch with us — we\'d love to hear from you' },
296
- createdAt: now, updatedAt: now
297
- },
298
- body: `[hero variant="dark" size="lg" fullwidth="true" twinkle twinkle-count="50" blobs]
299
-
300
- # Get in Touch
301
-
302
- [scribe]We'd love to hear from you — drop us a message and we'll get back to you shortly.[/scribe]
303
-
304
- [/hero]
305
-
306
- [spacer /]
307
-
308
- [grid cols="2" gap="4"]
309
-
310
- [col]
311
-
312
- [reveal animation="fade-up"]
313
- [card title="Contact Information"]
314
-
315
- <p><span data-icon="mail"></span> &nbsp;<strong>Email</strong><br>hello@example.com</p>
316
-
317
- <p><span data-icon="phone"></span> &nbsp;<strong>Phone</strong><br>+44 20 0000 0000</p>
318
-
319
- <p><span data-icon="clock"></span> &nbsp;<strong>Office Hours</strong><br>Monday – Friday, 9am – 5pm</p>
320
-
321
- [/card]
322
- [/reveal]
323
-
324
- [spacer /]
325
-
326
- [/col]
327
-
328
- [col]
329
-
330
- [reveal animation="fade-up"]
331
-
332
- [card title="Send Us a Message"]
333
-
334
- <div data-form="enquiries"></div>
335
-
336
- [/card]
337
-
338
- [/reveal]
339
-
340
- [/col]
341
-
342
- [/grid]
343
- `
344
- },
345
- {
346
- file: 'feedback.md',
347
- fm: {
348
- title: 'Feedback', slug: 'feedback',
349
- description: 'Share your feedback with us',
350
- layout: 'default', status: 'published',
351
- sortOrder: 5, showInNav: true, sidebar: false,
352
- seo: { title: 'Share Your Feedback', description: 'Help us improve by sharing your thoughts and experiences' },
353
- createdAt: now, updatedAt: now
354
- },
355
- body: `[hero variant="dark" size="lg" fullwidth="true" twinkle twinkle-count="50" blobs]
356
-
357
- # Share Your Feedback
358
-
359
- [scribe]Your thoughts matter — help us improve by telling us about your experience.[/scribe]
360
-
361
- [/hero]
362
-
363
- [reveal animation="slide-up"]
364
-
365
- [card title="Feedback Form"]
366
-
367
- <div data-form="feedback"></div>
368
-
369
- [/card]
370
-
371
- [/reveal]
372
- `
373
- },
374
- {
375
- file: path.join('blog', 'index.md'),
376
- fm: {
377
- title: 'Blog', slug: 'blog',
378
- description: 'News, updates and articles',
379
- layout: 'landing', status: 'published',
380
- sortOrder: 3, showInNav: true, sidebar: false,
381
- seo: { title: 'Blog', description: 'News, updates and articles' },
382
- createdAt: now, updatedAt: now
383
- },
384
- body: `[hero fullwidth="true" size="lg" variant="dark" align="left" twinkle twinkle-count="30" blobs]
385
-
386
- ## Blog & Articles
387
-
388
- [scribe speed="45" cursor="true"]Ideas, guides, and updates.[/scribe]
389
-
390
- [/hero]
391
-
392
- [spacer size="40" /]
393
-
394
- [reveal animation="slide-up" delay="0"]
395
- [grid cols="3" gap="4"]
396
-
397
- [col]
398
- [card title="Getting Started"]
399
- Your complete guide to creating pages, using shortcodes, and customising your site from scratch.
400
-
401
- [Read more →](/blog/hello-world)
402
- [/card]
403
- [/col]
404
-
405
- [col]
406
- [card title="Write Your First Post"]
407
- Everything you need to know about Markdown, frontmatter, and publishing content in Domma CMS.
408
-
409
- [Read more →](/blog/hello-world)
410
- [/card]
411
- [/col]
412
-
413
- [col]
414
- [card title="Shortcode Primer"]
415
- A quick tour of the most useful shortcodes — heroes, grids, cards, counters, and reveal animations.
416
-
417
- [Read more →](/blog/hello-world)
418
- [/card]
419
- [/col]
420
-
421
- [/grid]
422
- [/reveal]
423
- `
424
- },
425
- {
426
- file: path.join('blog', 'hello-world.md'),
427
- fm: {
428
- title: 'Getting Started with Domma CMS', slug: 'hello-world',
429
- description: 'A quick guide to creating pages, using shortcodes, and publishing content',
430
- layout: 'default', status: 'published',
431
- sortOrder: 1, showInNav: false, sidebar: false,
432
- seo: { title: 'Getting Started', description: 'A quick guide to creating pages, using shortcodes, and publishing content' },
433
- createdAt: now, updatedAt: now
434
- },
435
- body: `[hero variant="dark" size="lg" fullwidth="true" twinkle twinkle-count="30" blobs]
436
-
437
- # Getting Started
438
-
439
- [scribe speed="45" cursor="true"]Everything you need to start creating content with Domma CMS.[/scribe]
440
-
441
- [/hero]
442
-
443
- [spacer /]
444
-
445
- [reveal animation="slide-up"]
446
- [grid cols="3" gap="4"]
447
- [col]
448
- [card title="1. Create a page"]
449
- Head to the admin panel, click **Pages → New Page**, give it a title and slug, then start writing in the Markdown editor.
450
- [/card]
451
- [/col]
452
- [col]
453
- [card title="2. Add shortcodes"]
454
- Drop shortcodes anywhere in your content — \`[card]\`, \`[grid]\`, \`[hero]\`, \`[reveal]\` and more. The reference is at [/resources/shortcodes](/resources/shortcodes).
455
- [/card]
456
- [/col]
457
- [col]
458
- [card title="3. Publish"]
459
- Set the page status to **Published** and save. It's live instantly — no build step, no deployment.
460
- [/card]
461
- [/col]
462
- [/grid]
463
- [/reveal]
464
-
465
- [spacer /]
466
-
467
- ## Writing in Markdown
468
-
469
- Domma CMS uses **Markdown** for all page content. The basics:
470
-
471
- - **Bold** with \`**double asterisks**\`
472
- - *Italic* with \`*single asterisks*\`
473
- - \`Inline code\` with backticks
474
- - [Links](https://example.com) with \`[text](url)\`
475
-
476
- > Blockquotes with \`>\`
477
-
478
- \`\`\`
479
- Code blocks with triple backticks
480
- \`\`\`
481
-
482
- [spacer /]
483
-
484
- [card title="Where to go next"]
485
- - [Shortcode Reference →](/resources/shortcodes) — every shortcode with examples
486
- - [Effects →](/resources/effects) — scroll reveals, counters, typewriter text
487
- - [Admin panel →](/admin/) — manage pages, media, navigation, and settings
488
- [/card]
489
- `
490
- },
491
-
492
- // ----- Resources -----
493
-
494
- {
495
- file: path.join('resources', 'index.md'),
496
- fm: {
497
- title: 'Resources', slug: 'resources',
498
- description: 'Reference pages showing the Domma CMS kit — typography, grid, cards, and shortcodes.',
499
- layout: 'default', status: 'published',
500
- sortOrder: 10, showInNav: false, sidebar: false,
501
- seo: {title: 'Resources', description: 'Domma CMS component and shortcode reference'},
502
- createdAt: now, updatedAt: now
503
- },
504
- body: `# Resources
505
-
506
- Reference pages for content authors and developers working with Domma CMS.
507
-
508
- [grid cols="2" gap="4"]
509
- [col]
510
- [card title="Typography"]
511
- Headings, paragraphs, lists, blockquotes, inline code, and all standard Markdown elements.
512
-
513
- [Read more →](/resources/typography)
514
- [/card]
515
- [/col]
516
- [col]
517
- [card title="Grid System"]
518
- Equal columns, column spanning, gap sizes, and flexbox rows using the \`[grid]\`, \`[row]\`, and \`[col]\` shortcodes.
519
-
520
- [Read more →](/resources/grid)
521
- [/card]
522
- [/col]
523
- [col]
524
- [card title="Cards"]
525
- Card shortcode variants — basic, titled, collapsible, and cards inside grid layouts.
526
-
527
- [Read more →](/resources/cards)
528
- [/card]
529
- [/col]
530
- [col]
531
- [card title="Shortcode Reference"]
532
- Full reference for every shortcode supported in the editor, including all attributes and examples.
533
-
534
- [Read more →](/resources/shortcodes)
535
- [/card]
536
- [/col]
537
- [col]
538
- [card title="Effects"]
539
- Live demonstrations of every effect shortcode — scroll reveals, animated counters, typewriter text, ambient backgrounds,
540
- and more.
541
-
542
- [Read more →](/resources/effects)
543
- [/card]
544
- [/col]
545
- [col]
546
- [card title="Interactive"]
547
- Live demos of \`[slideover]\` panels and \`[dconfig]\` declarative behaviour — toggle classes, show panels, and wire up
548
- click handlers without writing JavaScript.
549
-
550
- [Read more →](/resources/interactive)
551
- [/card]
552
- [/col]
553
- [/grid]
554
- `
555
- },
556
- {
557
- file: path.join('resources', 'typography.md'),
558
- fm: {
559
- title: 'Typography', slug: 'typography',
560
- description: 'Headings, paragraphs, lists, blockquotes, code, and all standard text elements.',
561
- layout: 'default', status: 'published',
562
- sortOrder: 11, showInNav: false, sidebar: false,
563
- seo: {title: 'Typography — Resources', description: 'Typography reference for Domma CMS'},
564
- createdAt: now, updatedAt: now
565
- },
566
- body: `# Typography
567
-
568
- Everything you can express with standard Markdown in the Domma CMS editor.
569
-
570
- ---
571
-
572
- ## Headings
573
-
574
- # Heading 1
575
- ## Heading 2
576
- ### Heading 3
577
- #### Heading 4
578
- ##### Heading 5
579
- ###### Heading 6
580
-
581
- ---
582
-
583
- ## Paragraphs & Inline Styles
584
-
585
- This is a regular paragraph. You can make text **bold**, _italic_, or ~~strikethrough~~. Combine them: **_bold and italic_**.
586
-
587
- You can also write \`inline code\` using backticks.
588
-
589
- ---
590
-
591
- ## Blockquotes
592
-
593
- > A blockquote stands out from body text and is useful for callouts, pull quotes, or attributed statements.
594
-
595
- > Blockquotes can contain **inline formatting** and span multiple lines.
596
-
597
- ---
598
-
599
- ## Lists
600
-
601
- ### Unordered
602
-
603
- - First item
604
- - Second item
605
- - Nested item
606
- - Another nested item
607
- - Third item
608
-
609
- ### Ordered
610
-
611
- 1. Step one
612
- 2. Step two
613
- 3. Step three
614
- 1. Sub-step
615
- 2. Sub-step
616
-
617
- ---
618
-
619
- ## Code
620
-
621
- Inline code uses single backticks: \`npm run dev\`
622
-
623
- Fenced code blocks use triple backticks:
624
-
625
- \`\`\`js
626
- function greet(name) {
627
- return \\\`Hello, \\\${name}!\\\`;
628
- }
629
- \`\`\`
630
-
631
- ---
632
-
633
- ## Tables
634
-
635
- | Column A | Column B | Column C |
636
- |----------|----------|----------|
637
- | Cell 1 | Cell 2 | Cell 3 |
638
- | Cell 4 | Cell 5 | Cell 6 |
639
-
640
- ---
641
-
642
- ← [Back to Resources](/resources)
643
- `
644
- },
645
- {
646
- file: path.join('resources', 'grid.md'),
647
- fm: {
648
- title: 'Grid System', slug: 'grid',
649
- description: 'Equal columns, column spanning, gap sizes, and flexbox rows using grid shortcodes.',
650
- layout: 'default', status: 'published',
651
- sortOrder: 12, showInNav: false, sidebar: false,
652
- seo: {title: 'Grid System — Resources', description: 'Domma CMS grid shortcode reference'},
653
- createdAt: now, updatedAt: now
654
- },
655
- body: `# Grid System
656
-
657
- Domma uses native CSS Grid via shortcodes. Use \`[grid]\`, \`[row]\`, and \`[col]\` in the editor — no HTML needed.
658
-
659
- ---
660
-
661
- ## Equal Columns
662
-
663
- ### 2 Columns
664
-
665
- [grid cols="2" gap="4"]
666
- [col]
667
- **Left column**
668
- Each \`[col]\` fills one track of the grid automatically.
669
- [/col]
670
- [col]
671
- **Right column**
672
- No extra attributes needed for equal widths.
673
- [/col]
674
- [/grid]
675
-
676
- ### 3 Columns
677
-
678
- [grid cols="3" gap="4"]
679
- [col]**One** — first of three.[/col]
680
- [col]**Two** — second of three.[/col]
681
- [col]**Three** — third of three.[/col]
682
- [/grid]
683
-
684
- ### 4 Columns
685
-
686
- [grid cols="4" gap="3"]
687
- [col]Alpha[/col]
688
- [col]Beta[/col]
689
- [col]Gamma[/col]
690
- [col]Delta[/col]
691
- [/grid]
692
-
693
- ---
694
-
695
- ## Column Spanning
696
-
697
- Use \`span="N"\` on a \`[col]\` to make it span more than one track. Values must add up to \`cols\`.
698
-
699
- ### 8 / 4 split
700
-
701
- [grid cols="12" gap="4"]
702
- [col span="8"]**span="8"** — main content area (two-thirds).[/col]
703
- [col span="4"]**span="4"** — sidebar (one-third).[/col]
704
- [/grid]
705
-
706
- ### 6 / 6 (halves)
707
-
708
- [grid cols="12" gap="4"]
709
- [col span="6"]**span="6"** — left half.[/col]
710
- [col span="6"]**span="6"** — right half.[/col]
711
- [/grid]
712
-
713
- ### 9 / 3
714
-
715
- [grid cols="12" gap="4"]
716
- [col span="9"]**span="9"** — wide column.[/col]
717
- [col span="3"]**span="3"** — narrow aside.[/col]
718
- [/grid]
719
-
720
- ---
721
-
722
- ## Gap Sizes
723
-
724
- **gap="1"**
725
- [grid cols="4" gap="1"]
726
- [col]One[/col][col]Two[/col][col]Three[/col][col]Four[/col]
727
- [/grid]
728
-
729
- **gap="3"**
730
- [grid cols="4" gap="3"]
731
- [col]One[/col][col]Two[/col][col]Three[/col][col]Four[/col]
732
- [/grid]
733
-
734
- **gap="6"**
735
- [grid cols="4" gap="6"]
736
- [col]One[/col][col]Two[/col][col]Three[/col][col]Four[/col]
737
- [/grid]
738
-
739
- ---
740
-
741
- ## Row (Flexbox)
742
-
743
- \`[row]\` uses flexbox — columns share available space equally without specifying a count.
744
-
745
- [row gap="4"]
746
- [col]**Flex col 1**[/col]
747
- [col]**Flex col 2**[/col]
748
- [col]**Flex col 3**[/col]
749
- [/row]
750
-
751
- ---
752
-
753
- ← [Back to Resources](/resources)
754
- `
755
- },
756
- {
757
- file: path.join('resources', 'cards.md'),
758
- fm: {
759
- title: 'Cards', slug: 'cards',
760
- description: 'Card shortcode variants — basic, titled, collapsible, and cards in grid layouts.',
761
- layout: 'default', status: 'published',
762
- sortOrder: 13, showInNav: false, sidebar: false,
763
- seo: {title: 'Cards — Resources', description: 'Domma CMS card shortcode reference'},
764
- createdAt: now, updatedAt: now
765
- },
766
- body: `# Cards
767
-
768
- The \`[card]\` shortcode wraps content in a styled card. Cards support full Markdown inside them, including other shortcodes.
769
-
770
- ---
771
-
772
- ## Basic Card (no header)
773
-
774
- [card]
775
- This is a basic card with no title. Useful for callout blocks or highlighted sections.
776
-
777
- It supports **bold**, _italic_, and \`inline code\`.
778
- [/card]
779
-
780
- ---
781
-
782
- ## Card with Title
783
-
784
- [card title="Card Title"]
785
- This card has a header. The \`title\` attribute sets the text in the card's top bar.
786
- [/card]
787
-
788
- ---
789
-
790
- ## Collapsible Card
791
-
792
- Add \`collapsible="true"\` to make the body toggle on header click. Starts collapsed.
793
-
794
- [card title="Click to expand" collapsible="true"]
795
- This content is hidden by default and revealed when the header is clicked.
796
- [/card]
797
-
798
- ---
799
-
800
- ## Cards in a Grid
801
-
802
- [grid cols="3" gap="4"]
803
- [col]
804
- [card title="Feature One"]
805
- Describe the first feature here. One or two sentences works well in a three-column layout.
806
- [/card]
807
- [/col]
808
- [col]
809
- [card title="Feature Two"]
810
- Cards in a grid automatically share the same height within a row.
811
- [/card]
812
- [/col]
813
- [col]
814
- [card title="Feature Three"]
815
- Add a link if this feature has its own dedicated page.
816
- [/card]
817
- [/col]
818
- [/grid]
819
-
820
- ---
821
-
822
- ## Grid Inside a Card
823
-
824
- [card title="Two-column layout inside a card"]
825
- [grid cols="2" gap="4"]
826
- [col]
827
- Use the left column for a description or introductory copy.
828
- [/col]
829
- [col]
830
- Use the right column for a list or supplementary detail.
831
-
832
- - Point A
833
- - Point B
834
- - Point C
835
- [/col]
836
- [/grid]
837
- [/card]
838
-
839
- ---
840
-
841
- ← [Back to Resources](/resources)
842
- `
843
- },
844
- {
845
- file: path.join('resources', 'shortcodes.md'),
846
- fm: {
847
- title: 'Shortcode Reference', slug: 'shortcodes',
848
- description: 'Full reference for every shortcode supported in the Domma CMS editor.',
849
- layout: 'default', status: 'published',
850
- sortOrder: 14, showInNav: false, sidebar: false,
851
- seo: {title: 'Shortcode Reference — Resources', description: 'Complete shortcode reference for Domma CMS'},
852
- createdAt: now, updatedAt: now
853
- },
854
- body: `# Shortcode Reference
855
-
856
- Shortcodes extend Markdown with layout and component capabilities. Mix them freely with regular Markdown.
857
-
858
- **Syntax:** \`[shortcode attribute="value"]…[/shortcode]\`
859
-
860
- ---
861
-
862
- ## [\`grid\`]
863
-
864
- Creates a CSS Grid container.
865
-
866
- | Attribute | Required | Description |
867
- |-----------|----------|-------------|
868
- | \`cols\` | Yes | Number of columns (1–12) |
869
- | \`gap\` | No | Gap between cells (1–6) |
870
- | \`class\` | No | Extra CSS classes |
871
-
872
- [grid cols="3" gap="4"]
873
- [col]**cols="3"**[/col]
874
- [col]**gap="4"**[/col]
875
- [col]**Equal widths**[/col]
876
- [/grid]
877
-
878
- ---
879
-
880
- ## [\`row\`]
881
-
882
- Flexbox row — columns share available width equally, no \`cols\` needed.
883
-
884
- | Attribute | Required | Description |
885
- |-----------|----------|-------------|
886
- | \`gap\` | No | Gap between columns (1–6) |
887
- | \`class\` | No | Extra CSS classes |
888
-
889
- [row gap="4"]
890
- [col]**Left**[/col]
891
- [col]**Right**[/col]
892
- [/row]
893
-
894
- ---
895
-
896
- ## [\`col\`]
897
-
898
- A column inside \`[grid]\` or \`[row]\`. Content is rendered as Markdown.
899
-
900
- | Attribute | Required | Description |
901
- |-----------|----------|-------------|
902
- | \`span\` | No | Columns to span (values must sum to grid \`cols\`) |
903
- | \`class\` | No | Extra CSS classes |
904
-
905
- [grid cols="12" gap="4"]
906
- [col span="8"]**span="8"** — main content[/col]
907
- [col span="4"]**span="4"** — sidebar[/col]
908
- [/grid]
909
-
910
- ---
911
-
912
- ## [\`card\`]
913
-
914
- Wraps content in a styled card.
915
-
916
- | Attribute | Required | Description |
917
- |-----------|----------|-------------|
918
- | \`title\` | No | Card header text |
919
- | \`collapsible\` | No | \`"true"\` to toggle body on header click |
920
- | \`class\` | No | Extra CSS classes |
921
-
922
- [card title="Example card"]
923
- Markdown **works** inside cards, including _italic_, \`code\`, and shortcodes.
924
- [/card]
925
-
926
- [card title="Collapsible" collapsible="true"]
927
- This body starts collapsed.
928
- [/card]
929
-
930
- ---
931
-
932
- ## Embedding a Form
933
-
934
- Paste this HTML anywhere in your Markdown to embed a form:
935
-
936
- \`\`\`html
937
- <div data-form="your-form-slug"></div>
938
- \`\`\`
939
-
940
- Replace \`your-form-slug\` with the slug from the Form Builder. Example:
941
-
942
- \`\`\`html
943
- <div data-form="enquiries"></div>
944
- \`\`\`
945
-
946
- ---
947
-
948
- ## [\`slideover\`]
949
-
950
- Renders a trigger button that opens a slide-in panel with Markdown content.
951
-
952
- | Attribute | Required | Description |
953
- |-----------|----------|-------------|
954
- | \`title\` | No | Panel header text |
955
- | \`trigger\` | No | Button label (default: \`"Open"\`) |
956
- | \`size\` | No | \`sm\`, \`md\` (default), \`lg\` |
957
- | \`position\` | No | \`right\` (default) or \`left\` |
958
-
959
- \`\`\`
960
- [slideover title="More Info" trigger="Read more" size="md"]
961
- Markdown here — cards, grids, and other shortcodes work inside.
962
- [/slideover]
963
- \`\`\`
964
-
965
- ---
966
-
967
- ## [\`dconfig\`]
968
-
969
- Declarative behaviour — wires up click handlers and class toggles from JSON, no JavaScript needed.
970
-
971
- \`\`\`
972
- [dconfig]
973
- {
974
- "#my-btn": {
975
- "events": {
976
- "click": { "target": "#panel", "toggleClass": "hidden" }
977
- }
978
- }
979
- }
980
- [/dconfig]
981
- \`\`\`
982
-
983
- You can also set DConfig from the **DConfig section** in the page editor. Inline shortcodes win on selector conflict.
984
- See [Interactive demos →](/resources/interactive)
985
-
986
- ---
987
-
988
- ← [Back to Resources](/resources)
989
- `
990
- },
991
-
992
- // ----- Effects -----
993
- {
994
- file: path.join('resources', 'effects.md'),
995
- fm: {
996
- title: 'Effects', slug: 'effects',
997
- description: 'Live demonstrations of every Domma Effects shortcode — reveals, counters, typewriter text, ambient backgrounds, and more.',
998
- layout: 'default', status: 'published',
999
- sortOrder: 15, showInNav: false, sidebar: false,
1000
- seo: {title: 'Effects — Resources', description: 'Live demonstrations of Domma Effects shortcodes'},
1001
- createdAt: now, updatedAt: now
1002
- },
1003
- body: `# Effects
1004
-
1005
- Effect shortcodes bring content to life. All examples below are live (requires the Domma Effects plugin).
1006
-
1007
- ---
1008
-
1009
- ## \`[reveal]\` — Scroll-triggered entrance
1010
-
1011
- [reveal animation="fade"]
1012
- [card title="Fade in"]
1013
- This card faded in as you scrolled to it.
1014
- [/card]
1015
- [/reveal]
1016
-
1017
- \`\`\`
1018
- [reveal animation="fade"]
1019
- Your content here.
1020
- [/reveal]
1021
- \`\`\`
1022
-
1023
- **Attributes:** \`animation\` (fade, slide-up, zoom, flip) · \`duration\` (ms) · \`delay\` (ms) · \`once\` (true/false)
1024
-
1025
- ---
1026
-
1027
- ## \`[counter]\` — Animated number
1028
-
1029
- [reveal animation="slide-up"]
1030
- [grid cols="3" gap="4"]
1031
- [col]<div style="text-align:center;padding:1rem 0">
1032
-
1033
- ## [counter to="247" separator="," /]
1034
-
1035
- Pages published
1036
- </div>[/col]
1037
- [col]<div style="text-align:center;padding:1rem 0">
1038
-
1039
- ## [counter to="98" suffix="%" /]
1040
-
1041
- Uptime
1042
- </div>[/col]
1043
- [col]<div style="text-align:center;padding:1rem 0">
1044
-
1045
- ## [counter to="12" /]
1046
-
1047
- Plugins
1048
- </div>[/col]
1049
- [/grid]
1050
- [/reveal]
1051
-
1052
- \`\`\`
1053
- [counter to="100" prefix="$" suffix="+" /]
1054
- \`\`\`
1055
-
1056
- ---
1057
-
1058
- ## \`[scribe]\` — Typewriter
1059
-
1060
- [reveal animation="fade"]
1061
- [card]
1062
- [scribe speed="40" cursor="true"]
1063
- Domma CMS — fast, flexible, file-based.
1064
- [/scribe]
1065
- [/card]
1066
- [/reveal]
1067
-
1068
- ---
1069
-
1070
- ## \`[animate]\` — CSS-only animation
1071
-
1072
- [grid cols="3" gap="4"]
1073
- [col]
1074
- [animate type="fade-in-up"]
1075
- [card title="fade-in-up"]
1076
- No plugin needed.
1077
- [/card]
1078
- [/animate]
1079
- [/col]
1080
- [col]
1081
- [animate type="zoom-in"]
1082
- [card title="zoom-in"]
1083
- Scales up from centre.
1084
- [/card]
1085
- [/animate]
1086
- [/col]
1087
- [col]
1088
- [animate type="fade-in-left"]
1089
- [card title="fade-in-left"]
1090
- Slides in from right.
1091
- [/card]
1092
- [/animate]
1093
- [/col]
1094
- [/grid]
1095
-
1096
- ---
1097
-
1098
- ← [Back to Resources](/resources)
1099
- `
1100
- },
1101
-
1102
- // ----- Interactive -----
1103
- {
1104
- file: path.join('resources', 'interactive.md'),
1105
- fm: {
1106
- title: 'Interactive Shortcodes', slug: 'interactive',
1107
- description: 'Live demonstrations of the [slideover] and [dconfig] shortcodes — declarative interactivity without JavaScript.',
1108
- layout: 'default', status: 'published',
1109
- sortOrder: 16, showInNav: false, sidebar: false,
1110
- seo: {
1111
- title: 'Interactive Shortcodes — Resources',
1112
- description: 'Live demos of slideover and DConfig in Domma CMS'
1113
- },
1114
- createdAt: now, updatedAt: now
1115
- },
1116
- body: `# Interactive Shortcodes
1117
-
1118
- Two shortcodes let you add interactivity to any page without writing JavaScript.
1119
-
1120
- ---
1121
-
1122
- ## Slideover
1123
-
1124
- [slideover title="More Information" trigger="Read more →"]
1125
- ## About Domma CMS
1126
-
1127
- Domma CMS is a **file-based** content management system built on Fastify. Pages are stored as Markdown files with YAML frontmatter.
1128
-
1129
- No database required — content lives in \\\`content/pages/\\\` and is served server-side.
1130
- [/slideover]
1131
-
1132
- [slideover title="Package Details" trigger="View specifications" size="lg"]
1133
- [card title="Technical Requirements"]
1134
- - **Node.js** 18 or later
1135
- - Any Linux/macOS/Windows system
1136
- [/card]
1137
-
1138
- [card title="Included Plugins" collapsible="true"]
1139
- - Back to Top
1140
- - Cookie Consent
1141
- - Custom CSS
1142
- - Form Builder
1143
- [/card]
1144
- [/slideover]
1145
-
1146
- ---
1147
-
1148
- ## DConfig — Toggle on Click
1149
-
1150
- [dconfig]
1151
- {
1152
- "#toggle-demo-btn": {
1153
- "events": {
1154
- "click": { "target": "#toggle-demo-panel", "toggleClass": "hidden" }
1155
- }
1156
- }
1157
- }
1158
- [/dconfig]
1159
-
1160
- <button id="toggle-demo-btn" class="btn btn-primary">Toggle panel</button>
1161
-
1162
- <div id="toggle-demo-panel" class="card mt-3" style="max-width:400px">
1163
- <div class="card-body">
1164
- <p>This panel toggles when you click the button above.</p>
1165
- </div>
1166
- </div>
1167
-
1168
- ---
1169
-
1170
- ## Syntax Reference
1171
-
1172
- **Slideover attributes:**
1173
-
1174
- | Attribute | Default | Description |
1175
- |-----------|---------|-------------|
1176
- | \`title\` | — | Panel header text |
1177
- | \`trigger\` | \`"Open"\` | Button label |
1178
- | \`size\` | \`"md"\` | \`sm\`, \`md\`, \`lg\` |
1179
- | \`position\` | \`"right"\` | \`right\` or \`left\` |
1180
-
1181
- **DConfig format:**
1182
-
1183
- \`\`\`
1184
- [dconfig]
1185
- {
1186
- "#selector": {
1187
- "events": {
1188
- "click": { "target": "#other-selector", "toggleClass": "class-name" }
1189
- }
1190
- }
1191
- }
1192
- [/dconfig]
1193
- \`\`\`
1194
-
1195
- See the [Shortcode Reference](/resources/shortcodes) for full documentation.
1196
-
1197
- ---
1198
-
1199
- ← [Back to Resources](/resources)
1200
- `
1201
- },
1202
- {
1203
- file: 'privacy.md',
1204
- fm: {
1205
- title: 'Privacy Policy', slug: 'privacy',
1206
- description: 'How we collect, use, and protect your personal data',
1207
- layout: 'default', status: 'published',
1208
- sortOrder: 90, showInNav: false, sidebar: false,
1209
- seo: {title: 'Privacy Policy', description: 'How we collect, use, and protect your personal data'},
1210
- createdAt: now, updatedAt: now, visibility: 'public'
1211
- },
1212
- body: `[hero variant="dark" size="sm" fullwidth="true" twinkle twinkle-count="20" blobs]
1213
-
1214
- # Privacy Policy
1215
-
1216
- How [Your Organisation] collects, uses, and protects your personal data.
1217
-
1218
- [/hero]
1219
-
1220
- **Last updated: ${new Date().toLocaleDateString('en-GB', {day: 'numeric', month: 'long', year: 'numeric'})}**
1221
-
1222
- [Your Organisation] ("we", "us", "our") is committed to protecting and respecting your privacy. This policy explains
1223
- what personal data we collect, how we use it, and your rights under applicable data protection law, including the UK
1224
- GDPR.
1225
-
1226
- ---
1227
-
1228
- ## 1. Who We Are
1229
-
1230
- **Data Controller:** [Your Organisation]
1231
-
1232
- If you have any questions about this policy or how we handle your data, please [contact us](/contact).
1233
-
1234
- ---
1235
-
1236
- ## 2. What Data We Collect
1237
-
1238
- We may collect and process the following categories of personal data:
1239
-
1240
- **Information you provide directly:**
1241
-
1242
- - Name and contact details (e.g. email address) when you submit a contact or feedback form
1243
- - Any other information you voluntarily provide through the site
1244
-
1245
- **Information collected automatically:**
1246
-
1247
- - Server access logs (IP address, browser type, pages visited, timestamps) for security and performance monitoring
1248
- - Session data stored in your browser (e.g. theme preferences)
1249
-
1250
- We do not use third-party analytics services or advertising trackers on this site.
1251
-
1252
- ---
1253
-
1254
- ## 3. How We Use Your Data
1255
-
1256
- We use the personal data we collect for the following purposes:
1257
-
1258
- | Purpose | Legal Basis |
1259
- |----------------------------------------------------------|----------------------|
1260
- | Responding to enquiries submitted via our contact form | Legitimate interests |
1261
- | Monitoring site security and diagnosing technical issues | Legitimate interests |
1262
- | Complying with legal obligations | Legal obligation |
1263
-
1264
- We will never sell, rent, or share your personal data with third parties for marketing purposes.
1265
-
1266
- ---
1267
-
1268
- ## 4. How Long We Keep Your Data
1269
-
1270
- We retain personal data only for as long as necessary for the purposes described above:
1271
-
1272
- - **Contact form submissions:** Up to 12 months from the date of receipt, unless an ongoing relationship requires longer
1273
- retention
1274
- - **Server access logs:** Up to 30 days, then automatically purged
1275
-
1276
- ---
1277
-
1278
- ## 5. Your Rights
1279
-
1280
- Under the UK GDPR, you have the following rights regarding your personal data:
1281
-
1282
- - **Right of access** — request a copy of the data we hold about you
1283
- - **Right to rectification** — ask us to correct inaccurate data
1284
- - **Right to erasure** — ask us to delete your data where we have no lawful basis to retain it
1285
- - **Right to restrict processing** — ask us to pause processing while a dispute is resolved
1286
- - **Right to data portability** — receive your data in a structured, machine-readable format
1287
- - **Right to object** — object to processing based on legitimate interests
1288
-
1289
- To exercise any of these rights, please [contact us](/contact). We will respond within one calendar month.
1290
-
1291
- If you are not satisfied with our response, you have the right to lodge a complaint with the **Information
1292
- Commissioner's Office (ICO)**: [ico.org.uk](https://ico.org.uk) | 0303 123 1113.
1293
-
1294
- ---
1295
-
1296
- ## 6. Cookies
1297
-
1298
- This site uses only essential cookies necessary for its operation (e.g. storing your preferred theme). No tracking or
1299
- advertising cookies are used. See our [Cookie Policy](/cookies) for full details.
1300
-
1301
- ---
1302
-
1303
- ## 7. Security
1304
-
1305
- We take appropriate technical and organisational measures to protect your personal data against unauthorised access,
1306
- disclosure, alteration, or destruction.
1307
-
1308
- ---
1309
-
1310
- ## 8. Changes to This Policy
1311
-
1312
- We may update this policy from time to time. The "Last updated" date at the top of this page will always reflect the
1313
- most recent revision.
1314
-
1315
- ---
1316
-
1317
- ## 9. Contact Us
1318
-
1319
- For any privacy-related enquiries, please use our [contact page](/contact).
1320
- `
1321
- },
1322
- {
1323
- file: 'cookies.md',
1324
- fm: {
1325
- title: 'Cookie Policy', slug: 'cookies',
1326
- description: 'Our approach to cookies, tracking, and your rights under UK GDPR',
1327
- layout: 'default', status: 'published',
1328
- sortOrder: 91, showInNav: false, sidebar: false,
1329
- seo: {
1330
- title: 'Cookie Policy',
1331
- description: 'Our approach to cookies, tracking, and your rights under UK GDPR'
1332
- },
1333
- createdAt: now, updatedAt: now, visibility: 'public'
1334
- },
1335
- body: `[hero variant="dark" size="sm" fullwidth="true" twinkle twinkle-count="20" blobs]
1336
-
1337
- # Cookie Policy
1338
-
1339
- Your rights and our obligations under UK GDPR & PECR.
1340
-
1341
- [/hero]
1342
-
1343
- **Last updated: ${new Date().toLocaleDateString('en-GB', {day: 'numeric', month: 'long', year: 'numeric'})}**
1344
-
1345
- This page explains how [Your Organisation] handles cookies and your rights under the UK General Data Protection
1346
- Regulation (UK GDPR) and the Privacy and Electronic Communications Regulations (PECR).
1347
-
1348
- ---
1349
-
1350
- ## 1. What Are Cookies?
1351
-
1352
- Cookies are small text files placed on your device by a website. They allow the site to remember your preferences and
1353
- settings between visits.
1354
-
1355
- ---
1356
-
1357
- ## 2. Cookies We Use
1358
-
1359
- This site uses a minimal, privacy-first approach to cookies. We do **not** use advertising, tracking, or profiling
1360
- cookies of any kind.
1361
-
1362
- | Cookie | Type | Purpose | Duration |
1363
- |-------------|------------|---------------------------------------|------------------------|
1364
- | \`dm_theme\` | Functional | Stores your preferred colour theme | Session / localStorage |
1365
- | \`dm_motion\` | Functional | Stores your reduced-motion preference | Session / localStorage |
1366
-
1367
- **Note:** Theme and motion preferences are stored in your browser's \`localStorage\`, not as HTTP cookies. They never
1368
- leave your device and are not transmitted to our servers.
1369
-
1370
- We do not embed third-party content (e.g. social media widgets, YouTube iframes) that would set third-party cookies
1371
- without your consent.
1372
-
1373
- ---
1374
-
1375
- ## 3. Your Cookie Choices
1376
-
1377
- Because we only use functional, non-tracking storage, we do not require a cookie consent banner under PECR.
1378
-
1379
- You can clear locally stored preferences at any time through your browser settings:
1380
-
1381
- - **Chrome / Edge:** Settings → Privacy and security → Clear browsing data → Cookies and other site data
1382
- - **Firefox:** Settings → Privacy & Security → Cookies and Site Data → Clear Data
1383
- - **Safari:** Settings → Safari → Clear History and Website Data
1384
-
1385
- Clearing these will reset your theme and motion preferences to the site defaults.
1386
-
1387
- ---
1388
-
1389
- ## 4. Your Rights Under UK GDPR
1390
-
1391
- As a UK resident (or EU resident accessing our site), you have rights under UK GDPR:
1392
-
1393
- ### Right to Be Informed
1394
-
1395
- We tell you how we use your data through this policy and our [Privacy Policy](/privacy).
1396
-
1397
- ### Right of Access
1398
-
1399
- You can request a copy of any personal data we hold about you by [contacting us](/contact).
1400
-
1401
- ### Right to Erasure ("Right to be Forgotten")
1402
-
1403
- Where we hold personal data about you and have no overriding legal reason to retain it, you can ask us to delete it.
1404
-
1405
- ### Right to Restrict Processing
1406
-
1407
- If you dispute the accuracy of your data or our right to process it, you can ask us to pause processing while we
1408
- investigate.
1409
-
1410
- ### Right to Object
1411
-
1412
- You can object to processing of your personal data where we rely on legitimate interests as the legal basis.
1413
-
1414
- ### Right to Data Portability
1415
-
1416
- Where processing is based on your consent or a contract, you can receive your data in a portable format.
1417
-
1418
- ### How to Exercise Your Rights
1419
-
1420
- Submit a request via our [contact page](/contact). We will acknowledge your request within 72 hours and respond fully
1421
- within one calendar month.
1422
-
1423
- ---
1424
-
1425
- ## 5. Lawful Bases for Processing
1426
-
1427
- We only process personal data where we have a valid lawful basis. The primary bases we rely on are:
1428
-
1429
- - **Legitimate interests** — operating a secure, functional website and responding to enquiries
1430
- - **Legal obligation** — where required by UK law
1431
-
1432
- We do not rely on consent for any processing on this site, meaning you do not need to opt in or out for the site to
1433
- function normally.
1434
-
1435
- ---
1436
-
1437
- ## 6. International Transfers
1438
-
1439
- All data processed by this site is stored and processed within the UK and/or EEA. We do not transfer personal data to
1440
- countries outside these territories.
1441
-
1442
- ---
1443
-
1444
- ## 7. Data Retention
1445
-
1446
- We retain personal data only as long as necessary. See our [Privacy Policy](/privacy) for specific retention periods.
1447
-
1448
- ---
1449
-
1450
- ## 8. The ICO
1451
-
1452
- The UK supervisory authority for data protection is the **Information Commissioner's Office (ICO)**:
1453
-
1454
- - Website: [ico.org.uk](https://ico.org.uk)
1455
- - Phone: 0303 123 1113
1456
-
1457
- You have the right to lodge a complaint with the ICO at any time if you believe we have not handled your data lawfully.
1458
-
1459
- ---
1460
-
1461
- ## 9. Changes to This Policy
1462
-
1463
- We will update this page whenever our practices change. The "Last updated" date at the top reflects the current
1464
- revision.
1465
-
1466
- ---
1467
-
1468
- ## 10. Contact
1469
-
1470
- Privacy queries: use our [contact page](/contact).
1471
- `
1472
- }
1473
- ];
1474
-
1475
- // ---------------------------------------------------------------------------
1476
- // Default collections
1477
- // ---------------------------------------------------------------------------
1478
-
1479
- const COLLECTIONS = [
1480
- {
1481
- slug: 'contacts',
1482
- schema: {
1483
- slug: 'contacts',
1484
- title: 'Contacts',
1485
- description: 'People and their contact information.',
1486
- fields: [
1487
- {name: 'full_name', label: 'Full Name', type: 'text', required: true},
1488
- {name: 'email_address', label: 'Email', type: 'text', required: true},
1489
- {name: 'phone_number', label: 'Phone', type: 'text'}
1490
- ],
1491
- api: {
1492
- create: {enabled: true, access: 'public'},
1493
- read: {enabled: true, access: 'admin'},
1494
- update: {enabled: false, access: 'admin'},
1495
- delete: {enabled: false, access: 'admin'}
1496
- }
1497
- }
1498
- },
1499
- {
1500
- slug: 'enquiries',
1501
- schema: {
1502
- slug: 'enquiries',
1503
- title: 'Enquiries',
1504
- description: 'Messages and enquiries from website visitors.',
1505
- fields: [
1506
- {name: 'full_name', label: 'Full Name', type: 'text', required: true},
1507
- {name: 'email', label: 'Email', type: 'text', required: true},
1508
- {name: 'phone', label: 'Phone', type: 'text'},
1509
- {
1510
- name: 'subject',
1511
- label: 'Subject',
1512
- type: 'select',
1513
- options: ['General Enquiry', 'Support', 'Sales', 'Partnership', 'Other'],
1514
- required: true
1515
- },
1516
- {name: 'message', label: 'Message', type: 'text', required: true}
1517
- ],
1518
- api: {
1519
- create: {enabled: true, access: 'public'},
1520
- read: {enabled: true, access: 'admin'},
1521
- update: {enabled: false, access: 'admin'},
1522
- delete: {enabled: false, access: 'admin'}
1523
- }
1524
- }
1525
- },
1526
- {
1527
- slug: 'feedback',
1528
- schema: {
1529
- slug: 'feedback',
1530
- title: 'Feedback',
1531
- description: 'User feedback and ratings.',
1532
- fields: [
1533
- {name: 'name', label: 'Name', type: 'text', required: true},
1534
- {name: 'email', label: 'Email', type: 'text', required: true},
1535
- {
1536
- name: 'rating',
1537
- label: 'Rating',
1538
- type: 'select',
1539
- options: ['Excellent', 'Good', 'Average', 'Poor'],
1540
- required: true
1541
- },
1542
- {
1543
- name: 'category',
1544
- label: 'Category',
1545
- type: 'select',
1546
- options: ['General', 'Bug Report', 'Feature Request', 'Praise']
1547
- },
1548
- {name: 'subject', label: 'Subject', type: 'text'},
1549
- {name: 'message', label: 'Message', type: 'text', required: true}
1550
- ],
1551
- api: {
1552
- create: {enabled: true, access: 'public'},
1553
- read: {enabled: true, access: 'admin'},
1554
- update: {enabled: false, access: 'admin'},
1555
- delete: {enabled: false, access: 'admin'}
1556
- }
1557
- }
1558
- },
1559
- {
1560
- slug: 'to-do',
1561
- schema: {
1562
- slug: 'to-do',
1563
- title: 'To-Do',
1564
- description: 'Task tracking with priorities and due dates.',
1565
- fields: [
1566
- {name: 'title', label: 'Title', type: 'text', required: true},
1567
- {name: 'description', label: 'Description', type: 'text'},
1568
- {name: 'status', label: 'Status', type: 'select', options: ['Pending', 'In Progress', 'Done'], required: true},
1569
- {name: 'priority', label: 'Priority', type: 'select', options: ['Low', 'Medium', 'High']},
1570
- {name: 'due_date', label: 'Due Date', type: 'text'},
1571
- {name: 'assigned_to', label: 'Assigned To', type: 'text'}
1572
- ],
1573
- api: {
1574
- create: {enabled: false, access: 'admin'},
1575
- read: {enabled: false, access: 'admin'},
1576
- update: {enabled: false, access: 'admin'},
1577
- delete: {enabled: false, access: 'admin'}
1578
- }
1579
- }
1580
- },
1581
- {
1582
- slug: 'notes',
1583
- schema: {
1584
- slug: 'notes',
1585
- title: 'Notes',
1586
- description: 'Free-form notes with categories and tags.',
1587
- fields: [
1588
- {name: 'title', label: 'Title', type: 'text', required: true},
1589
- {name: 'content', label: 'Content', type: 'text', required: true},
1590
- {name: 'category', label: 'Category', type: 'select', options: ['General', 'Idea', 'Reminder', 'Reference']},
1591
- {name: 'tags', label: 'Tags', type: 'text'}
1592
- ],
1593
- api: {
1594
- create: {enabled: false, access: 'admin'},
1595
- read: {enabled: false, access: 'admin'},
1596
- update: {enabled: false, access: 'admin'},
1597
- delete: {enabled: false, access: 'admin'}
1598
- }
1599
- }
1600
- }
1601
- ];
1602
-
1603
- // ---------------------------------------------------------------------------
1604
- // Default forms (with collection actions)
1605
- // ---------------------------------------------------------------------------
1606
-
1607
- const FORMS = [
1608
- {
1609
- slug: 'contacts',
1610
- data: {
1611
- slug: 'contacts',
1612
- title: 'Contacts',
1613
- description: 'Contact Information',
1614
- fields: [
1615
- {
1616
- name: 'full_name',
1617
- type: 'string',
1618
- label: 'Full Name',
1619
- required: false,
1620
- placeholder: 'Full Name',
1621
- helper: 'Full Name',
1622
- minLength: 8,
1623
- maxLength: 255
1624
- },
1625
- {type: 'spacer'},
1626
- {
1627
- name: 'phone_number',
1628
- type: 'tel',
1629
- label: 'Phone Number',
1630
- required: false,
1631
- placeholder: 'Phone Number',
1632
- helper: 'Primary Phone Number'
1633
- },
1634
- {type: 'spacer'},
1635
- {
1636
- name: 'email_address',
1637
- type: 'string',
1638
- label: 'Email Address',
1639
- required: false,
1640
- placeholder: 'Email Address',
1641
- helper: 'Email Address',
1642
- minLength: 8,
1643
- maxLength: 255
1644
- }
1645
- ],
1646
- settings: {
1647
- submitText: 'Submit',
1648
- successMessage: 'Thank you for your submission.',
1649
- layout: 'stacked',
1650
- honeypot: true,
1651
- rateLimitPerMinute: 3
1652
- },
1653
- actions: {
1654
- email: {enabled: false, recipients: '', subjectPrefix: '[Contacts]'},
1655
- webhook: {enabled: false, url: '', method: 'POST'},
1656
- collection: {enabled: true, slug: 'contacts'}
1657
- },
1658
- createdAt: now,
1659
- updatedAt: now
1660
- }
1661
- },
1662
- {
1663
- slug: 'enquiries',
1664
- data: {
1665
- slug: 'enquiries',
1666
- title: 'Enquiries',
1667
- description: 'Get in touch with us',
1668
- fields: [
1669
- {
1670
- name: 'full_name',
1671
- type: 'string',
1672
- label: 'Full Name',
1673
- required: true,
1674
- placeholder: 'Your full name',
1675
- helper: '',
1676
- validation: {min: 2, max: 100}
1677
- },
1678
- {
1679
- name: 'email',
1680
- type: 'email',
1681
- label: 'Email Address',
1682
- required: true,
1683
- placeholder: 'your@email.com',
1684
- helper: ''
1685
- },
1686
- {
1687
- name: 'phone',
1688
- type: 'tel',
1689
- label: 'Phone Number',
1690
- required: false,
1691
- placeholder: '+44 7700 000000',
1692
- helper: 'Optional'
1693
- },
1694
- {
1695
- name: 'subject',
1696
- type: 'select',
1697
- label: 'Subject',
1698
- required: true,
1699
- placeholder: 'Please select a subject',
1700
- helper: '',
1701
- options: [
1702
- {value: 'general', label: 'General Enquiry'},
1703
- {value: 'support', label: 'Support'},
1704
- {value: 'sales', label: 'Sales'},
1705
- {value: 'partnership', label: 'Partnership'},
1706
- {value: 'other', label: 'Other'}
1707
- ]
1708
- },
1709
- {
1710
- name: 'message',
1711
- type: 'textarea',
1712
- label: 'Message',
1713
- required: true,
1714
- placeholder: 'How can we help you?',
1715
- helper: '',
1716
- rows: 4,
1717
- validation: {min: 10, max: 2000}
1718
- }
1719
- ],
1720
- settings: {
1721
- submitText: 'Send Message',
1722
- successMessage: 'Thanks for reaching out! We\'ll get back to you shortly.',
1723
- layout: 'stacked',
1724
- honeypot: true,
1725
- rateLimitPerMinute: 3
1726
- },
1727
- actions: {
1728
- email: {enabled: false, recipients: '', subjectPrefix: '[enquiries]'},
1729
- webhook: {enabled: false, url: '', method: 'POST'},
1730
- collection: {enabled: true, slug: 'enquiries'}
1731
- },
1732
- createdAt: now,
1733
- updatedAt: now
1734
- }
1735
- },
1736
- {
1737
- slug: 'feedback',
1738
- data: {
1739
- slug: 'feedback',
1740
- title: 'Feedback',
1741
- description: 'Share your feedback with us',
1742
- fields: [
1743
- {
1744
- name: 'name',
1745
- type: 'string',
1746
- label: 'Your Name',
1747
- required: true,
1748
- placeholder: 'Please enter your full name',
1749
- helper: 'Please enter your full name',
1750
- validation: {min: 2, max: 100}
1751
- },
1752
- {
1753
- name: 'email',
1754
- type: 'email',
1755
- label: 'Email Address',
1756
- required: true,
1757
- placeholder: 'your@email.com',
1758
- helper: 'Please enter your email address'
1759
- },
1760
- {
1761
- name: 'rating', type: 'select', label: 'Overall Rating', required: true, helper: 'Tell us how we are doing!',
1762
- options: [
1763
- {value: 'none', label: 'Please Choose'},
1764
- {value: 'excellent', label: 'Excellent'},
1765
- {value: 'good', label: 'Good'},
1766
- {value: 'average', label: 'Average'},
1767
- {value: 'poor', label: 'Poor'}
1768
- ]
1769
- },
1770
- {
1771
- name: 'category',
1772
- type: 'select',
1773
- label: 'Category',
1774
- required: true,
1775
- placeholder: 'Please select a category',
1776
- helper: '',
1777
- options: [
1778
- {value: 'general', label: 'General'},
1779
- {value: 'bug-report', label: 'Bug Report'},
1780
- {value: 'feature-request', label: 'Feature Request'},
1781
- {value: 'praise', label: 'Praise'}
1782
- ]
1783
- },
1784
- {
1785
- name: 'subject',
1786
- type: 'string',
1787
- label: 'Subject',
1788
- required: true,
1789
- placeholder: 'Brief summary of your feedback',
1790
- helper: '',
1791
- validation: {max: 200}
1792
- },
1793
- {
1794
- name: 'message',
1795
- type: 'textarea',
1796
- label: 'Your Feedback',
1797
- required: true,
1798
- placeholder: 'Please share your thoughts in detail…',
1799
- helper: 'Please share your thoughts in detail…',
1800
- rows: 4,
1801
- validation: {min: 10, max: 2000}
1802
- }
1803
- ],
1804
- settings: {
1805
- submitText: 'Submit Feedback',
1806
- successMessage: 'Thank you for your feedback! We appreciate you taking the time.',
1807
- layout: 'stacked',
1808
- honeypot: true,
1809
- rateLimitPerMinute: 3
1810
- },
1811
- actions: {
1812
- email: {enabled: true, recipients: '', subjectPrefix: '[feedback]'},
1813
- webhook: {enabled: false, url: '', method: 'POST'},
1814
- collection: {enabled: true, slug: 'feedback'}
1815
- },
1816
- createdAt: now,
1817
- updatedAt: now
1818
- }
1819
- },
1820
- {
1821
- slug: 'to-do',
1822
- data: {
1823
- slug: 'to-do',
1824
- title: 'To-Do',
1825
- description: 'Task tracking with status, priority, and due dates.',
1826
- fields: [
1827
- {name: 'title', type: 'string', label: 'Title', required: true, placeholder: 'Task title'},
1828
- {
1829
- name: 'description',
1830
- type: 'textarea',
1831
- label: 'Description',
1832
- required: false,
1833
- placeholder: 'Task details…',
1834
- rows: 3
1835
- },
1836
- {
1837
- name: 'status', type: 'select', label: 'Status', required: true,
1838
- options: [
1839
- {value: 'pending', label: 'Pending'},
1840
- {value: 'in-progress', label: 'In Progress'},
1841
- {value: 'done', label: 'Done'}
1842
- ]
1843
- },
1844
- {
1845
- name: 'priority', type: 'select', label: 'Priority', required: false,
1846
- options: [
1847
- {value: 'low', label: 'Low'},
1848
- {value: 'medium', label: 'Medium'},
1849
- {value: 'high', label: 'High'}
1850
- ]
1851
- },
1852
- {name: 'due_date', type: 'date', label: 'Due Date', required: false},
1853
- {name: 'assigned_to', type: 'string', label: 'Assigned To', required: false, placeholder: 'Name or email'}
1854
- ],
1855
- settings: {
1856
- submitText: 'Add Task',
1857
- successMessage: 'Task added successfully.',
1858
- layout: 'stacked',
1859
- honeypot: true,
1860
- rateLimitPerMinute: 3
1861
- },
1862
- actions: {
1863
- email: {enabled: false, recipients: '', subjectPrefix: '[to-do]'},
1864
- webhook: {enabled: false, url: '', method: 'POST'},
1865
- collection: {enabled: true, slug: 'to-do'}
1866
- },
1867
- createdAt: now,
1868
- updatedAt: now
1869
- }
1870
- },
1871
- {
1872
- slug: 'notes',
1873
- data: {
1874
- slug: 'notes',
1875
- title: 'Notes',
1876
- description: 'Free-form notes with categories and tags.',
1877
- fields: [
1878
- {name: 'title', type: 'string', label: 'Title', required: true, placeholder: 'Note title'},
1879
- {
1880
- name: 'content',
1881
- type: 'textarea',
1882
- label: 'Content',
1883
- required: true,
1884
- placeholder: 'Write your note here…',
1885
- rows: 5
1886
- },
1887
- {
1888
- name: 'category', type: 'select', label: 'Category', required: false,
1889
- options: [
1890
- {value: 'general', label: 'General'},
1891
- {value: 'idea', label: 'Idea'},
1892
- {value: 'reminder', label: 'Reminder'},
1893
- {value: 'reference', label: 'Reference'}
1894
- ]
1895
- },
1896
- {
1897
- name: 'tags',
1898
- type: 'string',
1899
- label: 'Tags',
1900
- required: false,
1901
- placeholder: 'Comma-separated tags',
1902
- helper: 'Separate tags with commas'
1903
- }
1904
- ],
1905
- settings: {
1906
- submitText: 'Save Note',
1907
- successMessage: 'Note saved successfully.',
1908
- layout: 'stacked',
1909
- honeypot: true,
1910
- rateLimitPerMinute: 3
1911
- },
1912
- actions: {
1913
- email: {enabled: false, recipients: '', subjectPrefix: '[notes]'},
1914
- webhook: {enabled: false, url: '', method: 'POST'},
1915
- collection: {enabled: true, slug: 'notes'}
1916
- },
1917
- createdAt: now,
1918
- updatedAt: now
1919
- }
1920
- }
1921
- ];
1922
-
1923
- // ---------------------------------------------------------------------------
1924
- // Serialise frontmatter + body to .md string
1925
- // Produces the same YAML-frontmatter format gray-matter reads.
1926
- // ---------------------------------------------------------------------------
1927
-
1928
- function toMarkdown(fm, body) {
1929
- const lines = [];
1930
- for (const [k, v] of Object.entries(fm)) {
1931
- if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
1932
- lines.push(`${k}:`);
1933
- for (const [nk, nv] of Object.entries(v)) {
1934
- lines.push(` ${nk}: ${JSON.stringify(nv)}`);
1935
- }
1936
- } else {
1937
- lines.push(`${k}: ${JSON.stringify(v)}`);
1938
- }
1939
- }
1940
- return `---\n${lines.join('\n')}\n---\n\n${body}`;
1941
- }
1942
-
1943
- // ---------------------------------------------------------------------------
1944
- // Main
1945
- // ---------------------------------------------------------------------------
1946
-
1947
- console.log('');
1948
- console.log(' ┌──────────────────────────────────┐');
1949
- console.log(' │ Domma CMS — Seed Demo Content │');
1950
- console.log(' └──────────────────────────────────┘');
1951
- console.log('');
1952
-
1953
- for (const page of PAGES) {
1954
- const filePath = path.join(PAGES_DIR, page.file);
1955
- await mkdir(path.dirname(filePath), { recursive: true });
1956
- await writeFile(filePath, toMarkdown(page.fm, page.body), 'utf8');
1957
- const urlLabel = '/' + page.file.replace(/\\/g, '/').replace(/index\.md$/, '').replace(/\.md$/, '');
1958
- console.log(` ✓ ${urlLabel || '/'}`);
1959
- }
1960
-
1961
- process.stdout.write(' Updating navigation…');
1962
- await writeFile(NAV_CFG, JSON.stringify(SEED_NAV, null, 4) + '\n', 'utf8');
1963
- console.log(' done.');
1964
-
1965
- for (const col of COLLECTIONS) {
1966
- const dir = path.join(COLLECTIONS_DIR, col.slug);
1967
- await mkdir(dir, {recursive: true});
1968
- await writeFile(path.join(dir, 'schema.json'), JSON.stringify(col.schema, null, 2) + '\n', 'utf8');
1969
- // Only initialise data.json if it doesn't already exist (preserve entries)
1970
- const dataFile = path.join(dir, 'data.json');
1971
- try {
1972
- await writeFile(dataFile, '[]\n', {encoding: 'utf8', flag: 'wx'});
1973
- } catch { /* already exists */
1974
- }
1975
- console.log(` ✓ collection: ${col.slug}`);
1976
- }
1977
-
1978
- await mkdir(FORMS_DIR, {recursive: true});
1979
- await mkdir(SUBS_DIR, {recursive: true});
1980
- for (const form of FORMS) {
1981
- await writeFile(path.join(FORMS_DIR, `${form.slug}.json`), JSON.stringify(form.data, null, 4) + '\n', 'utf8');
1982
- // Only initialise submissions file if it doesn't already exist
1983
- const subsFile = path.join(SUBS_DIR, `${form.slug}.json`);
1984
- try {
1985
- await writeFile(subsFile, '[]\n', {encoding: 'utf8', flag: 'wx'});
1986
- } catch { /* already exists */
1987
- }
1988
- const colSlug = form.data.actions?.collection?.slug ?? '—';
1989
- console.log(` ✓ form: ${form.slug} → collection: ${colSlug}`);
1990
- }
1991
-
1992
- console.log('');
1993
- console.log(` ✓ Seed complete — ${PAGES.length} pages, ${COLLECTIONS.length} collection(s), ${FORMS.length} form(s).`);
1994
- console.log(' Visit your site at http://localhost:4096/');
1995
- console.log(' Resources section available at http://localhost:4096/resources');
1996
- console.log('');