domma-cms 0.1.0 → 0.2.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.
- package/admin/css/admin.css +78 -1
- package/admin/js/api.js +32 -0
- package/admin/js/app.js +24 -7
- package/admin/js/config/sidebar-config.js +8 -0
- package/admin/js/templates/collection-editor.html +80 -0
- package/admin/js/templates/collection-entries.html +36 -0
- package/admin/js/templates/collections.html +12 -0
- package/admin/js/templates/documentation.html +136 -0
- package/admin/js/templates/navigation.html +26 -4
- package/admin/js/templates/page-editor.html +91 -85
- package/admin/js/templates/settings.html +433 -172
- package/admin/js/views/collection-editor.js +487 -0
- package/admin/js/views/collection-entries.js +484 -0
- package/admin/js/views/collections.js +153 -0
- package/admin/js/views/dashboard.js +14 -6
- package/admin/js/views/index.js +9 -3
- package/admin/js/views/login.js +3 -2
- package/admin/js/views/navigation.js +77 -11
- package/admin/js/views/page-editor.js +207 -25
- package/admin/js/views/pages.js +14 -6
- package/admin/js/views/settings.js +137 -2
- package/admin/js/views/users.js +10 -7
- package/bin/cli.js +37 -10
- package/config/auth.json +2 -1
- package/config/content.json +1 -0
- package/config/navigation.json +14 -4
- package/config/plugins.json +0 -18
- package/config/presets.json +4 -8
- package/config/site.json +44 -3
- package/package.json +6 -2
- package/plugins/domma-effects/admin/templates/domma-effects.html +92 -3
- package/plugins/domma-effects/plugin.js +125 -0
- package/plugins/domma-effects/public/inject-body.html +19 -0
- package/plugins/example-analytics/admin/views/analytics.js +2 -2
- package/plugins/example-analytics/plugin.json +8 -0
- package/plugins/example-analytics/stats.json +15 -1
- package/plugins/form-builder/admin/templates/form-editor.html +19 -6
- package/plugins/form-builder/admin/views/form-editor.js +634 -9
- package/plugins/form-builder/admin/views/form-submissions.js +4 -4
- package/plugins/form-builder/admin/views/forms-list.js +5 -5
- package/plugins/form-builder/data/forms/consent.json +104 -0
- package/plugins/form-builder/data/forms/contacts.json +66 -0
- package/plugins/form-builder/data/submissions/consent.json +13 -0
- package/plugins/form-builder/data/submissions/contacts.json +26 -0
- package/plugins/form-builder/plugin.js +62 -11
- package/plugins/form-builder/plugin.json +12 -16
- package/plugins/form-builder/public/form-logic-engine.js +568 -0
- package/plugins/form-builder/public/inject-body.html +88 -6
- package/plugins/form-builder/public/inject-head.html +16 -0
- package/plugins/form-builder/public/package.json +1 -0
- package/public/css/site.css +113 -0
- package/public/js/btt.js +90 -0
- package/public/js/cookie-consent.js +61 -0
- package/public/js/site.js +129 -34
- package/scripts/build.js +129 -0
- package/scripts/seed.js +517 -7
- package/server/routes/api/collections.js +301 -0
- package/server/routes/api/settings.js +66 -2
- package/server/server.js +19 -15
- package/server/services/collections.js +430 -0
- package/server/services/content.js +11 -2
- package/server/services/hooks.js +109 -0
- package/server/services/markdown.js +500 -149
- package/server/services/plugins.js +6 -1
- package/server/services/renderer.js +73 -7
- package/server/templates/page.html +38 -3
- package/plugins/back-to-top/admin/templates/back-to-top-settings.html +0 -55
- package/plugins/back-to-top/admin/views/back-to-top-settings.js +0 -44
- package/plugins/back-to-top/config.js +0 -10
- package/plugins/back-to-top/plugin.js +0 -24
- package/plugins/back-to-top/plugin.json +0 -36
- package/plugins/back-to-top/public/inject-body.html +0 -105
- package/plugins/cookie-consent/admin/templates/cookie-consent-settings.html +0 -113
- package/plugins/cookie-consent/admin/views/cookie-consent-settings.js +0 -73
- package/plugins/cookie-consent/config.js +0 -30
- package/plugins/cookie-consent/plugin.js +0 -24
- package/plugins/cookie-consent/plugin.json +0 -36
- package/plugins/cookie-consent/public/inject-body.html +0 -69
- package/plugins/custom-css/admin/templates/custom-css.html +0 -17
- package/plugins/custom-css/admin/views/custom-css.js +0 -35
- package/plugins/custom-css/config.js +0 -1
- package/plugins/custom-css/data/custom.css +0 -0
- package/plugins/custom-css/plugin.js +0 -63
- package/plugins/custom-css/plugin.json +0 -32
- package/plugins/custom-css/public/inject-head.html +0 -1
- package/plugins/form-builder/data/forms/contact.json +0 -52
- package/plugins/form-builder/data/submissions/contact.json +0 -14
package/scripts/build.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build script — creates a publishable staging copy in _publish/
|
|
3
|
+
* Browser-facing JS/CSS is minified; server-side code is copied as-is.
|
|
4
|
+
* Run: node scripts/build.js
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync} from 'fs';
|
|
8
|
+
import {dirname, join} from 'path';
|
|
9
|
+
import {glob} from 'fs/promises';
|
|
10
|
+
import * as esbuild from 'esbuild';
|
|
11
|
+
|
|
12
|
+
const ROOT = new URL('..', import.meta.url).pathname.replace(/\/$/, '');
|
|
13
|
+
const OUT = join(ROOT, '_publish');
|
|
14
|
+
|
|
15
|
+
// Files/dirs copied verbatim (server-side, config, docs)
|
|
16
|
+
const COPY_AS_IS = [
|
|
17
|
+
'server',
|
|
18
|
+
'bin',
|
|
19
|
+
'config',
|
|
20
|
+
'scripts',
|
|
21
|
+
'package.json',
|
|
22
|
+
'README.md',
|
|
23
|
+
'LICENSE',
|
|
24
|
+
'.npmignore',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
// Glob patterns for browser-facing assets to minify
|
|
28
|
+
const MINIFY_PATTERNS = [
|
|
29
|
+
'admin/js/**/*.js',
|
|
30
|
+
'admin/css/**/*.css',
|
|
31
|
+
'public/js/**/*.js',
|
|
32
|
+
'public/css/**/*.css',
|
|
33
|
+
'plugins/*/public/**/*.js',
|
|
34
|
+
'plugins/*/public/**/*.css',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// Files within plugins that are copied as-is (server-side plugin code)
|
|
38
|
+
const PLUGIN_AS_IS_PATTERNS = [
|
|
39
|
+
'plugins/**/plugin.js',
|
|
40
|
+
'plugins/**/config.js',
|
|
41
|
+
'plugins/**/routes/**/*.js',
|
|
42
|
+
'plugins/**/services/**/*.js',
|
|
43
|
+
'plugins/**/views/**/*.js',
|
|
44
|
+
'plugins/**/*.json',
|
|
45
|
+
'plugins/**/*.html',
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// HTML templates in admin (copied as-is)
|
|
49
|
+
const ADMIN_HTML_PATTERNS = [
|
|
50
|
+
'admin/**/*.html',
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
async function collectFiles(pattern) {
|
|
54
|
+
const files = [];
|
|
55
|
+
for await (const file of glob(pattern, {cwd: ROOT})) {
|
|
56
|
+
files.push(file);
|
|
57
|
+
}
|
|
58
|
+
return files;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function minifyFile(relPath, loader) {
|
|
62
|
+
const src = readFileSync(join(ROOT, relPath), 'utf8');
|
|
63
|
+
const result = await esbuild.transform(src, {
|
|
64
|
+
loader,
|
|
65
|
+
minify: true,
|
|
66
|
+
target: 'es2020',
|
|
67
|
+
});
|
|
68
|
+
const outPath = join(OUT, relPath);
|
|
69
|
+
mkdirSync(dirname(outPath), {recursive: true});
|
|
70
|
+
writeFileSync(outPath, result.code);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function build() {
|
|
74
|
+
// Clean and recreate _publish/
|
|
75
|
+
if (existsSync(OUT)) rmSync(OUT, {recursive: true, force: true});
|
|
76
|
+
mkdirSync(OUT, {recursive: true});
|
|
77
|
+
|
|
78
|
+
// Copy server-side dirs/files verbatim
|
|
79
|
+
for (const item of COPY_AS_IS) {
|
|
80
|
+
const src = join(ROOT, item);
|
|
81
|
+
if (existsSync(src)) {
|
|
82
|
+
cpSync(src, join(OUT, item), {recursive: true});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Copy plugin server-side code verbatim
|
|
87
|
+
const pluginAsIs = await collectFiles('{' + PLUGIN_AS_IS_PATTERNS.join(',') + '}');
|
|
88
|
+
for (const rel of pluginAsIs) {
|
|
89
|
+
const outPath = join(OUT, rel);
|
|
90
|
+
mkdirSync(dirname(outPath), {recursive: true});
|
|
91
|
+
cpSync(join(ROOT, rel), outPath);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Copy admin HTML templates verbatim
|
|
95
|
+
const adminHtml = await collectFiles(ADMIN_HTML_PATTERNS[0]);
|
|
96
|
+
for (const rel of adminHtml) {
|
|
97
|
+
const outPath = join(OUT, rel);
|
|
98
|
+
mkdirSync(dirname(outPath), {recursive: true});
|
|
99
|
+
cpSync(join(ROOT, rel), outPath);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Minify browser-facing JS and CSS
|
|
103
|
+
let jsCount = 0;
|
|
104
|
+
let cssCount = 0;
|
|
105
|
+
|
|
106
|
+
// Collect all minify targets, deduplicate against plugin as-is
|
|
107
|
+
const pluginAsIsSet = new Set(pluginAsIs);
|
|
108
|
+
|
|
109
|
+
for (const pattern of MINIFY_PATTERNS) {
|
|
110
|
+
const files = await collectFiles(pattern);
|
|
111
|
+
for (const rel of files) {
|
|
112
|
+
if (pluginAsIsSet.has(rel)) continue; // skip plugin server-side JS
|
|
113
|
+
const loader = rel.endsWith('.css') ? 'css' : 'js';
|
|
114
|
+
await minifyFile(rel, loader);
|
|
115
|
+
if (loader === 'js') jsCount++;
|
|
116
|
+
else cssCount++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const totalCopied = COPY_AS_IS.filter(i => existsSync(join(ROOT, i))).length
|
|
121
|
+
+ pluginAsIs.length + adminHtml.length;
|
|
122
|
+
|
|
123
|
+
console.log(`Built into _publish/ — JS: ${jsCount} minified, CSS: ${cssCount} minified, ${totalCopied} entries copied`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
build().catch(err => {
|
|
127
|
+
console.error('Build failed:', err.message);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
});
|
package/scripts/seed.js
CHANGED
|
@@ -13,7 +13,10 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
13
13
|
const ROOT = path.resolve(__dirname, '..');
|
|
14
14
|
|
|
15
15
|
const PAGES_DIR = path.join(ROOT, 'content', 'pages');
|
|
16
|
-
const
|
|
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');
|
|
17
20
|
|
|
18
21
|
const now = new Date().toISOString();
|
|
19
22
|
|
|
@@ -35,7 +38,9 @@ const SEED_NAV = {
|
|
|
35
38
|
{text: 'Typography', url: '/resources/typography', icon: 'type'},
|
|
36
39
|
{text: 'Grid System', url: '/resources/grid', icon: 'layout'},
|
|
37
40
|
{text: 'Cards', url: '/resources/cards', icon: 'credit-card'},
|
|
38
|
-
{text: 'Shortcode Reference', url: '/resources/shortcodes', icon: 'code'}
|
|
41
|
+
{text: 'Shortcode Reference', url: '/resources/shortcodes', icon: 'code'},
|
|
42
|
+
{text: 'Effects', url: '/resources/effects', icon: 'zap'},
|
|
43
|
+
{text: 'Interactive', url: '/resources/interactive', icon: 'mouse-pointer'}
|
|
39
44
|
]
|
|
40
45
|
}
|
|
41
46
|
],
|
|
@@ -109,13 +114,41 @@ Add your services, mission statement, or team information here.
|
|
|
109
114
|
},
|
|
110
115
|
body: `# Contact
|
|
111
116
|
|
|
112
|
-
|
|
117
|
+
Have a question, a project in mind, or just want to say hello? Fill in the form and we'll get back to you as soon as possible.
|
|
113
118
|
|
|
114
|
-
|
|
119
|
+
[grid cols="2" gap="6"]
|
|
120
|
+
[col]
|
|
121
|
+
|
|
122
|
+
## Send a message
|
|
123
|
+
|
|
124
|
+
<div data-form="contact"></div>
|
|
125
|
+
|
|
126
|
+
[/col]
|
|
127
|
+
[col]
|
|
128
|
+
|
|
129
|
+
## Get in touch
|
|
130
|
+
|
|
131
|
+
[card title="Address"]
|
|
132
|
+
123 Example Street
|
|
133
|
+
London, EC1A 1BB
|
|
134
|
+
United Kingdom
|
|
135
|
+
[/card]
|
|
115
136
|
|
|
116
|
-
|
|
137
|
+
[card title="Email"]
|
|
138
|
+
hello@example.com
|
|
139
|
+
[/card]
|
|
117
140
|
|
|
118
|
-
|
|
141
|
+
[card title="Phone"]
|
|
142
|
+
+44 20 0000 0000
|
|
143
|
+
[/card]
|
|
144
|
+
|
|
145
|
+
[card title="Office hours"]
|
|
146
|
+
Monday – Friday: 9am – 5pm
|
|
147
|
+
Saturday – Sunday: Closed
|
|
148
|
+
[/card]
|
|
149
|
+
|
|
150
|
+
[/col]
|
|
151
|
+
[/grid]
|
|
119
152
|
`
|
|
120
153
|
},
|
|
121
154
|
{
|
|
@@ -217,6 +250,22 @@ Full reference for every shortcode supported in the editor, including all attrib
|
|
|
217
250
|
[Read more →](/resources/shortcodes)
|
|
218
251
|
[/card]
|
|
219
252
|
[/col]
|
|
253
|
+
[col]
|
|
254
|
+
[card title="Effects"]
|
|
255
|
+
Live demonstrations of every effect shortcode — scroll reveals, animated counters, typewriter text, ambient backgrounds,
|
|
256
|
+
and more.
|
|
257
|
+
|
|
258
|
+
[Read more →](/resources/effects)
|
|
259
|
+
[/card]
|
|
260
|
+
[/col]
|
|
261
|
+
[col]
|
|
262
|
+
[card title="Interactive"]
|
|
263
|
+
Live demos of \`[slideover]\` panels and \`[dconfig]\` declarative behaviour — toggle classes, show panels, and wire up
|
|
264
|
+
click handlers without writing JavaScript.
|
|
265
|
+
|
|
266
|
+
[Read more →](/resources/interactive)
|
|
267
|
+
[/card]
|
|
268
|
+
[/col]
|
|
220
269
|
[/grid]
|
|
221
270
|
`
|
|
222
271
|
},
|
|
@@ -612,9 +661,444 @@ Replace \`your-form-slug\` with the slug from the Form Builder. Example:
|
|
|
612
661
|
|
|
613
662
|
---
|
|
614
663
|
|
|
664
|
+
## [\`slideover\`]
|
|
665
|
+
|
|
666
|
+
Renders a trigger button that opens a slide-in panel with Markdown content.
|
|
667
|
+
|
|
668
|
+
| Attribute | Required | Description |
|
|
669
|
+
|-----------|----------|-------------|
|
|
670
|
+
| \`title\` | No | Panel header text |
|
|
671
|
+
| \`trigger\` | No | Button label (default: \`"Open"\`) |
|
|
672
|
+
| \`size\` | No | \`sm\`, \`md\` (default), \`lg\` |
|
|
673
|
+
| \`position\` | No | \`right\` (default) or \`left\` |
|
|
674
|
+
|
|
675
|
+
\`\`\`
|
|
676
|
+
[slideover title="More Info" trigger="Read more" size="md"]
|
|
677
|
+
Markdown here — cards, grids, and other shortcodes work inside.
|
|
678
|
+
[/slideover]
|
|
679
|
+
\`\`\`
|
|
680
|
+
|
|
681
|
+
---
|
|
682
|
+
|
|
683
|
+
## [\`dconfig\`]
|
|
684
|
+
|
|
685
|
+
Declarative behaviour — wires up click handlers and class toggles from JSON, no JavaScript needed.
|
|
686
|
+
|
|
687
|
+
\`\`\`
|
|
688
|
+
[dconfig]
|
|
689
|
+
{
|
|
690
|
+
"#my-btn": {
|
|
691
|
+
"events": {
|
|
692
|
+
"click": { "target": "#panel", "toggleClass": "hidden" }
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
[/dconfig]
|
|
697
|
+
\`\`\`
|
|
698
|
+
|
|
699
|
+
You can also set DConfig from the **DConfig section** in the page editor. Inline shortcodes win on selector conflict.
|
|
700
|
+
See [Interactive demos →](/resources/interactive)
|
|
701
|
+
|
|
702
|
+
---
|
|
703
|
+
|
|
704
|
+
← [Back to Resources](/resources)
|
|
705
|
+
`
|
|
706
|
+
},
|
|
707
|
+
|
|
708
|
+
// ----- Effects -----
|
|
709
|
+
{
|
|
710
|
+
file: path.join('resources', 'effects.md'),
|
|
711
|
+
fm: {
|
|
712
|
+
title: 'Effects', slug: 'effects',
|
|
713
|
+
description: 'Live demonstrations of every Domma Effects shortcode — reveals, counters, typewriter text, ambient backgrounds, and more.',
|
|
714
|
+
layout: 'default', status: 'published',
|
|
715
|
+
sortOrder: 15, showInNav: false, sidebar: false,
|
|
716
|
+
seo: {title: 'Effects — Resources', description: 'Live demonstrations of Domma Effects shortcodes'},
|
|
717
|
+
createdAt: now, updatedAt: now
|
|
718
|
+
},
|
|
719
|
+
body: `# Effects
|
|
720
|
+
|
|
721
|
+
Effect shortcodes bring content to life. All examples below are live (requires the Domma Effects plugin).
|
|
722
|
+
|
|
723
|
+
---
|
|
724
|
+
|
|
725
|
+
## \`[reveal]\` — Scroll-triggered entrance
|
|
726
|
+
|
|
727
|
+
[reveal animation="fade"]
|
|
728
|
+
[card title="Fade in"]
|
|
729
|
+
This card faded in as you scrolled to it.
|
|
730
|
+
[/card]
|
|
731
|
+
[/reveal]
|
|
732
|
+
|
|
733
|
+
\`\`\`
|
|
734
|
+
[reveal animation="fade"]
|
|
735
|
+
Your content here.
|
|
736
|
+
[/reveal]
|
|
737
|
+
\`\`\`
|
|
738
|
+
|
|
739
|
+
**Attributes:** \`animation\` (fade, slide-up, zoom, flip) · \`duration\` (ms) · \`delay\` (ms) · \`once\` (true/false)
|
|
740
|
+
|
|
741
|
+
---
|
|
742
|
+
|
|
743
|
+
## \`[counter]\` — Animated number
|
|
744
|
+
|
|
745
|
+
[reveal animation="slide-up"]
|
|
746
|
+
[grid cols="3" gap="4"]
|
|
747
|
+
[col]<div style="text-align:center;padding:1rem 0">
|
|
748
|
+
|
|
749
|
+
## [counter to="247" separator="," /]
|
|
750
|
+
|
|
751
|
+
Pages published
|
|
752
|
+
</div>[/col]
|
|
753
|
+
[col]<div style="text-align:center;padding:1rem 0">
|
|
754
|
+
|
|
755
|
+
## [counter to="98" suffix="%" /]
|
|
756
|
+
|
|
757
|
+
Uptime
|
|
758
|
+
</div>[/col]
|
|
759
|
+
[col]<div style="text-align:center;padding:1rem 0">
|
|
760
|
+
|
|
761
|
+
## [counter to="12" /]
|
|
762
|
+
|
|
763
|
+
Plugins
|
|
764
|
+
</div>[/col]
|
|
765
|
+
[/grid]
|
|
766
|
+
[/reveal]
|
|
767
|
+
|
|
768
|
+
\`\`\`
|
|
769
|
+
[counter to="100" prefix="$" suffix="+" /]
|
|
770
|
+
\`\`\`
|
|
771
|
+
|
|
772
|
+
---
|
|
773
|
+
|
|
774
|
+
## \`[scribe]\` — Typewriter
|
|
775
|
+
|
|
776
|
+
[reveal animation="fade"]
|
|
777
|
+
[card]
|
|
778
|
+
[scribe speed="40" cursor="true"]
|
|
779
|
+
Domma CMS — fast, flexible, file-based.
|
|
780
|
+
[/scribe]
|
|
781
|
+
[/card]
|
|
782
|
+
[/reveal]
|
|
783
|
+
|
|
784
|
+
---
|
|
785
|
+
|
|
786
|
+
## \`[animate]\` — CSS-only animation
|
|
787
|
+
|
|
788
|
+
[grid cols="3" gap="4"]
|
|
789
|
+
[col]
|
|
790
|
+
[animate type="fade-in-up"]
|
|
791
|
+
[card title="fade-in-up"]
|
|
792
|
+
No plugin needed.
|
|
793
|
+
[/card]
|
|
794
|
+
[/animate]
|
|
795
|
+
[/col]
|
|
796
|
+
[col]
|
|
797
|
+
[animate type="zoom-in"]
|
|
798
|
+
[card title="zoom-in"]
|
|
799
|
+
Scales up from centre.
|
|
800
|
+
[/card]
|
|
801
|
+
[/animate]
|
|
802
|
+
[/col]
|
|
803
|
+
[col]
|
|
804
|
+
[animate type="fade-in-left"]
|
|
805
|
+
[card title="fade-in-left"]
|
|
806
|
+
Slides in from right.
|
|
807
|
+
[/card]
|
|
808
|
+
[/animate]
|
|
809
|
+
[/col]
|
|
810
|
+
[/grid]
|
|
811
|
+
|
|
812
|
+
---
|
|
813
|
+
|
|
814
|
+
← [Back to Resources](/resources)
|
|
815
|
+
`
|
|
816
|
+
},
|
|
817
|
+
|
|
818
|
+
// ----- Interactive -----
|
|
819
|
+
{
|
|
820
|
+
file: path.join('resources', 'interactive.md'),
|
|
821
|
+
fm: {
|
|
822
|
+
title: 'Interactive Shortcodes', slug: 'interactive',
|
|
823
|
+
description: 'Live demonstrations of the [slideover] and [dconfig] shortcodes — declarative interactivity without JavaScript.',
|
|
824
|
+
layout: 'default', status: 'published',
|
|
825
|
+
sortOrder: 16, showInNav: false, sidebar: false,
|
|
826
|
+
seo: {
|
|
827
|
+
title: 'Interactive Shortcodes — Resources',
|
|
828
|
+
description: 'Live demos of slideover and DConfig in Domma CMS'
|
|
829
|
+
},
|
|
830
|
+
createdAt: now, updatedAt: now
|
|
831
|
+
},
|
|
832
|
+
body: `# Interactive Shortcodes
|
|
833
|
+
|
|
834
|
+
Two shortcodes let you add interactivity to any page without writing JavaScript.
|
|
835
|
+
|
|
836
|
+
---
|
|
837
|
+
|
|
838
|
+
## Slideover
|
|
839
|
+
|
|
840
|
+
[slideover title="More Information" trigger="Read more →"]
|
|
841
|
+
## About Domma CMS
|
|
842
|
+
|
|
843
|
+
Domma CMS is a **file-based** content management system built on Fastify. Pages are stored as Markdown files with YAML frontmatter.
|
|
844
|
+
|
|
845
|
+
No database required — content lives in \\\`content/pages/\\\` and is served server-side.
|
|
846
|
+
[/slideover]
|
|
847
|
+
|
|
848
|
+
[slideover title="Package Details" trigger="View specifications" size="lg"]
|
|
849
|
+
[card title="Technical Requirements"]
|
|
850
|
+
- **Node.js** 18 or later
|
|
851
|
+
- Any Linux/macOS/Windows system
|
|
852
|
+
[/card]
|
|
853
|
+
|
|
854
|
+
[card title="Included Plugins" collapsible="true"]
|
|
855
|
+
- Back to Top
|
|
856
|
+
- Cookie Consent
|
|
857
|
+
- Custom CSS
|
|
858
|
+
- Form Builder
|
|
859
|
+
[/card]
|
|
860
|
+
[/slideover]
|
|
861
|
+
|
|
862
|
+
---
|
|
863
|
+
|
|
864
|
+
## DConfig — Toggle on Click
|
|
865
|
+
|
|
866
|
+
[dconfig]
|
|
867
|
+
{
|
|
868
|
+
"#toggle-demo-btn": {
|
|
869
|
+
"events": {
|
|
870
|
+
"click": { "target": "#toggle-demo-panel", "toggleClass": "hidden" }
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
[/dconfig]
|
|
875
|
+
|
|
876
|
+
<button id="toggle-demo-btn" class="btn btn-primary">Toggle panel</button>
|
|
877
|
+
|
|
878
|
+
<div id="toggle-demo-panel" class="card mt-3" style="max-width:400px">
|
|
879
|
+
<div class="card-body">
|
|
880
|
+
<p>This panel toggles when you click the button above.</p>
|
|
881
|
+
</div>
|
|
882
|
+
</div>
|
|
883
|
+
|
|
884
|
+
---
|
|
885
|
+
|
|
886
|
+
## Syntax Reference
|
|
887
|
+
|
|
888
|
+
**Slideover attributes:**
|
|
889
|
+
|
|
890
|
+
| Attribute | Default | Description |
|
|
891
|
+
|-----------|---------|-------------|
|
|
892
|
+
| \`title\` | — | Panel header text |
|
|
893
|
+
| \`trigger\` | \`"Open"\` | Button label |
|
|
894
|
+
| \`size\` | \`"md"\` | \`sm\`, \`md\`, \`lg\` |
|
|
895
|
+
| \`position\` | \`"right"\` | \`right\` or \`left\` |
|
|
896
|
+
|
|
897
|
+
**DConfig format:**
|
|
898
|
+
|
|
899
|
+
\`\`\`
|
|
900
|
+
[dconfig]
|
|
901
|
+
{
|
|
902
|
+
"#selector": {
|
|
903
|
+
"events": {
|
|
904
|
+
"click": { "target": "#other-selector", "toggleClass": "class-name" }
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
[/dconfig]
|
|
909
|
+
\`\`\`
|
|
910
|
+
|
|
911
|
+
See the [Shortcode Reference](/resources/shortcodes) for full documentation.
|
|
912
|
+
|
|
913
|
+
---
|
|
914
|
+
|
|
615
915
|
← [Back to Resources](/resources)
|
|
616
916
|
`
|
|
917
|
+
}
|
|
918
|
+
];
|
|
919
|
+
|
|
920
|
+
// ---------------------------------------------------------------------------
|
|
921
|
+
// Default collections
|
|
922
|
+
// ---------------------------------------------------------------------------
|
|
923
|
+
|
|
924
|
+
const COLLECTIONS = [
|
|
925
|
+
{
|
|
926
|
+
slug: 'contacts',
|
|
927
|
+
schema: {
|
|
928
|
+
slug: 'contacts',
|
|
929
|
+
title: 'Contacts',
|
|
930
|
+
description: 'Submissions from the contact form',
|
|
931
|
+
fields: [
|
|
932
|
+
{name: 'full_name', type: 'string', label: 'Full Name', required: false},
|
|
933
|
+
{name: 'email_address', type: 'string', label: 'Email Address', required: false},
|
|
934
|
+
{name: 'phone_number', type: 'tel', label: 'Phone Number', required: false},
|
|
935
|
+
{name: 'subject', type: 'string', label: 'Subject', required: false},
|
|
936
|
+
{name: 'message', type: 'textarea', label: 'Message', required: false}
|
|
937
|
+
],
|
|
938
|
+
api: {
|
|
939
|
+
create: {enabled: false, access: 'admin'},
|
|
940
|
+
read: {enabled: true, access: 'public'},
|
|
941
|
+
update: {enabled: false, access: 'admin'},
|
|
942
|
+
delete: {enabled: false, access: 'admin'}
|
|
943
|
+
},
|
|
944
|
+
createdAt: now,
|
|
945
|
+
updatedAt: now
|
|
617
946
|
}
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
slug: 'bookings',
|
|
950
|
+
schema: {
|
|
951
|
+
slug: 'bookings',
|
|
952
|
+
title: 'Bookings',
|
|
953
|
+
description: 'Booking requests — startedAt, endedAt, and createdAt (auto) captured per entry',
|
|
954
|
+
fields: [
|
|
955
|
+
{name: 'full_name', type: 'string', label: 'Full Name', required: true},
|
|
956
|
+
{name: 'email', type: 'string', label: 'Email', required: true},
|
|
957
|
+
{name: 'phone', type: 'tel', label: 'Phone Number', required: false},
|
|
958
|
+
{name: 'started_at', type: 'date', label: 'Start Date', required: true},
|
|
959
|
+
{name: 'ended_at', type: 'date', label: 'End Date', required: true},
|
|
960
|
+
{name: 'notes', type: 'textarea', label: 'Notes', required: false}
|
|
961
|
+
],
|
|
962
|
+
api: {
|
|
963
|
+
create: {enabled: false, access: 'admin'},
|
|
964
|
+
read: {enabled: true, access: 'public'},
|
|
965
|
+
update: {enabled: false, access: 'admin'},
|
|
966
|
+
delete: {enabled: false, access: 'admin'}
|
|
967
|
+
},
|
|
968
|
+
createdAt: now,
|
|
969
|
+
updatedAt: now
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
];
|
|
973
|
+
|
|
974
|
+
// ---------------------------------------------------------------------------
|
|
975
|
+
// Default forms (with collection actions)
|
|
976
|
+
// ---------------------------------------------------------------------------
|
|
977
|
+
|
|
978
|
+
const FORMS = [
|
|
979
|
+
{
|
|
980
|
+
slug: 'booking',
|
|
981
|
+
data: {
|
|
982
|
+
slug: 'booking',
|
|
983
|
+
title: 'Booking',
|
|
984
|
+
description: 'Request a booking — captures name, contact details, start date, end date, and notes. Entries stored in the Bookings collection with an automatic createdAt timestamp.',
|
|
985
|
+
fields: [
|
|
986
|
+
{
|
|
987
|
+
name: 'full_name',
|
|
988
|
+
type: 'string',
|
|
989
|
+
label: 'Full Name',
|
|
990
|
+
required: true,
|
|
991
|
+
placeholder: 'Your full name'
|
|
992
|
+
},
|
|
993
|
+
{
|
|
994
|
+
name: 'email',
|
|
995
|
+
type: 'string',
|
|
996
|
+
label: 'Email Address',
|
|
997
|
+
required: true,
|
|
998
|
+
placeholder: 'you@example.com'
|
|
999
|
+
},
|
|
1000
|
+
{
|
|
1001
|
+
name: 'phone',
|
|
1002
|
+
type: 'tel',
|
|
1003
|
+
label: 'Phone Number',
|
|
1004
|
+
required: false,
|
|
1005
|
+
placeholder: '+44 7700 000000'
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
name: 'started_at',
|
|
1009
|
+
type: 'date',
|
|
1010
|
+
label: 'Start Date',
|
|
1011
|
+
required: true
|
|
1012
|
+
},
|
|
1013
|
+
{
|
|
1014
|
+
name: 'ended_at',
|
|
1015
|
+
type: 'date',
|
|
1016
|
+
label: 'End Date',
|
|
1017
|
+
required: true
|
|
1018
|
+
},
|
|
1019
|
+
{
|
|
1020
|
+
name: 'notes',
|
|
1021
|
+
type: 'textarea',
|
|
1022
|
+
label: 'Notes',
|
|
1023
|
+
required: false,
|
|
1024
|
+
placeholder: 'Any additional details…'
|
|
1025
|
+
}
|
|
1026
|
+
],
|
|
1027
|
+
settings: {
|
|
1028
|
+
submitText: 'Request Booking',
|
|
1029
|
+
successMessage: 'Thank you — your booking request has been received. We\'ll be in touch to confirm.',
|
|
1030
|
+
layout: 'stacked',
|
|
1031
|
+
honeypot: true,
|
|
1032
|
+
rateLimitPerMinute: 3
|
|
1033
|
+
},
|
|
1034
|
+
actions: {
|
|
1035
|
+
email: {enabled: false, recipients: '', subjectPrefix: '[Booking]'},
|
|
1036
|
+
webhook: {enabled: false, url: '', method: 'POST'},
|
|
1037
|
+
collection: {enabled: true, slug: 'bookings'}
|
|
1038
|
+
},
|
|
1039
|
+
createdAt: now,
|
|
1040
|
+
updatedAt: now
|
|
1041
|
+
}
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
slug: 'contact',
|
|
1045
|
+
data: {
|
|
1046
|
+
slug: 'contact',
|
|
1047
|
+
title: 'Contact',
|
|
1048
|
+
description: 'Get in touch with us.',
|
|
1049
|
+
fields: [
|
|
1050
|
+
{
|
|
1051
|
+
name: 'full_name',
|
|
1052
|
+
type: 'string',
|
|
1053
|
+
label: 'Full Name',
|
|
1054
|
+
required: true,
|
|
1055
|
+
placeholder: 'Your full name'
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
name: 'email_address',
|
|
1059
|
+
type: 'string',
|
|
1060
|
+
label: 'Email Address',
|
|
1061
|
+
required: true,
|
|
1062
|
+
placeholder: 'you@example.com'
|
|
1063
|
+
},
|
|
1064
|
+
{
|
|
1065
|
+
name: 'phone_number',
|
|
1066
|
+
type: 'tel',
|
|
1067
|
+
label: 'Phone Number',
|
|
1068
|
+
required: false,
|
|
1069
|
+
placeholder: '+44 7700 000000'
|
|
1070
|
+
},
|
|
1071
|
+
{
|
|
1072
|
+
name: 'subject',
|
|
1073
|
+
type: 'string',
|
|
1074
|
+
label: 'Subject',
|
|
1075
|
+
required: false,
|
|
1076
|
+
placeholder: 'What is this regarding?'
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
name: 'message',
|
|
1080
|
+
type: 'textarea',
|
|
1081
|
+
label: 'Message',
|
|
1082
|
+
required: true,
|
|
1083
|
+
placeholder: 'Your message…'
|
|
1084
|
+
}
|
|
1085
|
+
],
|
|
1086
|
+
settings: {
|
|
1087
|
+
submitText: 'Send Message',
|
|
1088
|
+
successMessage: 'Thank you for your message. We\'ll be in touch shortly.',
|
|
1089
|
+
layout: 'stacked',
|
|
1090
|
+
honeypot: true,
|
|
1091
|
+
rateLimitPerMinute: 3
|
|
1092
|
+
},
|
|
1093
|
+
actions: {
|
|
1094
|
+
email: {enabled: false, recipients: '', subjectPrefix: '[Contact]'},
|
|
1095
|
+
webhook: {enabled: false, url: '', method: 'POST'},
|
|
1096
|
+
collection: {enabled: true, slug: 'contacts'}
|
|
1097
|
+
},
|
|
1098
|
+
createdAt: now,
|
|
1099
|
+
updatedAt: now
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
618
1102
|
];
|
|
619
1103
|
|
|
620
1104
|
// ---------------------------------------------------------------------------
|
|
@@ -659,8 +1143,34 @@ process.stdout.write(' Updating navigation…');
|
|
|
659
1143
|
await writeFile(NAV_CFG, JSON.stringify(SEED_NAV, null, 4) + '\n', 'utf8');
|
|
660
1144
|
console.log(' done.');
|
|
661
1145
|
|
|
1146
|
+
for (const col of COLLECTIONS) {
|
|
1147
|
+
const dir = path.join(COLLECTIONS_DIR, col.slug);
|
|
1148
|
+
await mkdir(dir, {recursive: true});
|
|
1149
|
+
await writeFile(path.join(dir, 'schema.json'), JSON.stringify(col.schema, null, 2) + '\n', 'utf8');
|
|
1150
|
+
// Only initialise data.json if it doesn't already exist (preserve entries)
|
|
1151
|
+
const dataFile = path.join(dir, 'data.json');
|
|
1152
|
+
try {
|
|
1153
|
+
await writeFile(dataFile, '[]\n', {encoding: 'utf8', flag: 'wx'});
|
|
1154
|
+
} catch { /* already exists */
|
|
1155
|
+
}
|
|
1156
|
+
console.log(` ✓ collection: ${col.slug}`);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
await mkdir(FORMS_DIR, {recursive: true});
|
|
1160
|
+
await mkdir(SUBS_DIR, {recursive: true});
|
|
1161
|
+
for (const form of FORMS) {
|
|
1162
|
+
await writeFile(path.join(FORMS_DIR, `${form.slug}.json`), JSON.stringify(form.data, null, 4) + '\n', 'utf8');
|
|
1163
|
+
// Only initialise submissions file if it doesn't already exist
|
|
1164
|
+
const subsFile = path.join(SUBS_DIR, `${form.slug}.json`);
|
|
1165
|
+
try {
|
|
1166
|
+
await writeFile(subsFile, '[]\n', {encoding: 'utf8', flag: 'wx'});
|
|
1167
|
+
} catch { /* already exists */
|
|
1168
|
+
}
|
|
1169
|
+
console.log(` ✓ form: ${form.slug} → collection: ${form.data.actions.collection.slug}`);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
662
1172
|
console.log('');
|
|
663
|
-
console.log(` ✓ Seed complete — ${PAGES.length} pages
|
|
1173
|
+
console.log(` ✓ Seed complete — ${PAGES.length} pages, ${COLLECTIONS.length} collection(s), ${FORMS.length} form(s).`);
|
|
664
1174
|
console.log(' Visit your site at http://localhost:3050/');
|
|
665
1175
|
console.log(' Resources section available at http://localhost:3050/resources');
|
|
666
1176
|
console.log('');
|