domma-cms 0.1.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/LICENSE +21 -0
- package/README.md +469 -0
- package/admin/css/admin.css +1123 -0
- package/admin/index.html +72 -0
- package/admin/js/api.js +210 -0
- package/admin/js/app.js +270 -0
- package/admin/js/config/sidebar-config.js +107 -0
- package/admin/js/lib/card.js +63 -0
- package/admin/js/lib/image-editor.js +869 -0
- package/admin/js/lib/markdown-toolbar.js +421 -0
- package/admin/js/templates/dashboard.html +50 -0
- package/admin/js/templates/documentation.html +237 -0
- package/admin/js/templates/layouts.html +11 -0
- package/admin/js/templates/login.html +58 -0
- package/admin/js/templates/media.html +16 -0
- package/admin/js/templates/navigation.html +50 -0
- package/admin/js/templates/page-editor.html +126 -0
- package/admin/js/templates/pages.html +18 -0
- package/admin/js/templates/plugins.html +12 -0
- package/admin/js/templates/settings.html +190 -0
- package/admin/js/templates/tutorials.html +233 -0
- package/admin/js/templates/user-editor.html +12 -0
- package/admin/js/templates/users.html +10 -0
- package/admin/js/views/dashboard.js +48 -0
- package/admin/js/views/documentation.js +12 -0
- package/admin/js/views/index.js +33 -0
- package/admin/js/views/layouts.js +49 -0
- package/admin/js/views/login.js +254 -0
- package/admin/js/views/media.js +240 -0
- package/admin/js/views/navigation.js +152 -0
- package/admin/js/views/page-editor.js +479 -0
- package/admin/js/views/pages.js +64 -0
- package/admin/js/views/plugins.js +100 -0
- package/admin/js/views/settings.js +64 -0
- package/admin/js/views/tutorials.js +12 -0
- package/admin/js/views/user-editor.js +88 -0
- package/admin/js/views/users.js +73 -0
- package/bin/cli.js +334 -0
- package/config/auth.json +20 -0
- package/config/content.json +10 -0
- package/config/navigation.json +63 -0
- package/config/plugins.json +47 -0
- package/config/presets.json +34 -0
- package/config/server.json +6 -0
- package/config/site.json +33 -0
- package/package.json +67 -0
- package/plugins/back-to-top/admin/templates/back-to-top-settings.html +55 -0
- package/plugins/back-to-top/admin/views/back-to-top-settings.js +44 -0
- package/plugins/back-to-top/config.js +10 -0
- package/plugins/back-to-top/plugin.js +24 -0
- package/plugins/back-to-top/plugin.json +36 -0
- package/plugins/back-to-top/public/inject-body.html +105 -0
- package/plugins/cookie-consent/admin/templates/cookie-consent-settings.html +113 -0
- package/plugins/cookie-consent/admin/views/cookie-consent-settings.js +73 -0
- package/plugins/cookie-consent/config.js +30 -0
- package/plugins/cookie-consent/plugin.js +24 -0
- package/plugins/cookie-consent/plugin.json +36 -0
- package/plugins/cookie-consent/public/inject-body.html +69 -0
- package/plugins/custom-css/admin/templates/custom-css.html +17 -0
- package/plugins/custom-css/admin/views/custom-css.js +35 -0
- package/plugins/custom-css/config.js +1 -0
- package/plugins/custom-css/data/custom.css +0 -0
- package/plugins/custom-css/plugin.js +63 -0
- package/plugins/custom-css/plugin.json +32 -0
- package/plugins/custom-css/public/inject-head.html +1 -0
- package/plugins/domma-effects/admin/templates/domma-effects.html +488 -0
- package/plugins/domma-effects/admin/views/domma-effects.js +56 -0
- package/plugins/domma-effects/config.js +9 -0
- package/plugins/domma-effects/plugin.js +22 -0
- package/plugins/domma-effects/plugin.json +36 -0
- package/plugins/domma-effects/public/celebrations/core/canvas.js +111 -0
- package/plugins/domma-effects/public/celebrations/core/particles.js +144 -0
- package/plugins/domma-effects/public/celebrations/core/physics.js +166 -0
- package/plugins/domma-effects/public/celebrations/index.js +535 -0
- package/plugins/domma-effects/public/celebrations/themes/christmas.js +1805 -0
- package/plugins/domma-effects/public/celebrations/themes/guy-fawkes.js +1477 -0
- package/plugins/domma-effects/public/celebrations/themes/halloween.js +1837 -0
- package/plugins/domma-effects/public/celebrations/themes/st-andrews.js +1175 -0
- package/plugins/domma-effects/public/celebrations/themes/st-davids.js +1258 -0
- package/plugins/domma-effects/public/celebrations/themes/st-georges.js +1754 -0
- package/plugins/domma-effects/public/celebrations/themes/st-patricks.js +1290 -0
- package/plugins/domma-effects/public/celebrations/themes/valentines.js +1361 -0
- package/plugins/domma-effects/public/inject-body.html +268 -0
- package/plugins/example-analytics/admin/templates/analytics.html +10 -0
- package/plugins/example-analytics/admin/views/analytics.js +51 -0
- package/plugins/example-analytics/config.js +6 -0
- package/plugins/example-analytics/plugin.js +58 -0
- package/plugins/example-analytics/plugin.json +27 -0
- package/plugins/example-analytics/public/inject-body.html +13 -0
- package/plugins/example-analytics/public/inject-head.html +1 -0
- package/plugins/example-analytics/stats.json +1 -0
- package/plugins/form-builder/admin/templates/form-editor.html +158 -0
- package/plugins/form-builder/admin/templates/form-settings.html +29 -0
- package/plugins/form-builder/admin/templates/form-submissions.html +30 -0
- package/plugins/form-builder/admin/templates/forms-list.html +17 -0
- package/plugins/form-builder/admin/views/form-editor.js +817 -0
- package/plugins/form-builder/admin/views/form-settings.js +38 -0
- package/plugins/form-builder/admin/views/form-submissions.js +295 -0
- package/plugins/form-builder/admin/views/forms-list.js +164 -0
- package/plugins/form-builder/config.js +9 -0
- package/plugins/form-builder/data/forms/contact-details.json +63 -0
- package/plugins/form-builder/data/forms/contact.json +52 -0
- package/plugins/form-builder/data/submissions/contact-details.json +1 -0
- package/plugins/form-builder/data/submissions/contact.json +14 -0
- package/plugins/form-builder/email.js +103 -0
- package/plugins/form-builder/plugin.js +454 -0
- package/plugins/form-builder/plugin.json +56 -0
- package/plugins/form-builder/public/inject-body.html +270 -0
- package/plugins/form-builder/public/inject-head.html +42 -0
- package/public/css/site.css +189 -0
- package/public/js/site.js +109 -0
- package/scripts/copy-domma.js +48 -0
- package/scripts/fresh.js +41 -0
- package/scripts/reset.js +124 -0
- package/scripts/seed.js +666 -0
- package/scripts/setup.js +263 -0
- package/server/config.js +56 -0
- package/server/middleware/auth.js +97 -0
- package/server/routes/api/auth.js +116 -0
- package/server/routes/api/layouts.js +25 -0
- package/server/routes/api/media.js +93 -0
- package/server/routes/api/navigation.js +37 -0
- package/server/routes/api/pages.js +118 -0
- package/server/routes/api/plugins.js +46 -0
- package/server/routes/api/settings.js +25 -0
- package/server/routes/api/users.js +110 -0
- package/server/routes/public.js +108 -0
- package/server/server.js +169 -0
- package/server/services/content.js +298 -0
- package/server/services/images.js +334 -0
- package/server/services/markdown.js +297 -0
- package/server/services/plugins.js +246 -0
- package/server/services/renderer.js +80 -0
- package/server/services/users.js +212 -0
- package/server/templates/page.html +78 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="settings"></span> Site Settings</h1>
|
|
3
|
+
<button id="save-settings-btn" class="btn btn-primary"><span data-icon="save"></span> Save</button>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="card card-collapsible mb-4">
|
|
7
|
+
<div class="card-header" role="button" tabindex="0">
|
|
8
|
+
<div class="card-header-content"><h2>General</h2></div>
|
|
9
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="card-body">
|
|
12
|
+
<div class="row mb-3">
|
|
13
|
+
<div class="col-6">
|
|
14
|
+
<label class="form-label">Site Title</label>
|
|
15
|
+
<input id="field-site-title" type="text" class="form-input" placeholder="My Site">
|
|
16
|
+
</div>
|
|
17
|
+
<div class="col-6">
|
|
18
|
+
<label class="form-label">Tagline</label>
|
|
19
|
+
<input id="field-tagline" type="text" class="form-input" placeholder="Built with Domma CMS">
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="row">
|
|
23
|
+
<div class="col-6">
|
|
24
|
+
<label class="form-label">Public Site Theme</label>
|
|
25
|
+
<select id="field-theme" class="form-select">
|
|
26
|
+
<optgroup label="Charcoal">
|
|
27
|
+
<option value="charcoal-dark">Charcoal Dark</option>
|
|
28
|
+
<option value="charcoal-light">Charcoal Light</option>
|
|
29
|
+
</optgroup>
|
|
30
|
+
<optgroup label="Ocean">
|
|
31
|
+
<option value="ocean-dark">Ocean Dark</option>
|
|
32
|
+
<option value="ocean-light">Ocean Light</option>
|
|
33
|
+
</optgroup>
|
|
34
|
+
<optgroup label="Forest">
|
|
35
|
+
<option value="forest-dark">Forest Dark</option>
|
|
36
|
+
<option value="forest-light">Forest Light</option>
|
|
37
|
+
</optgroup>
|
|
38
|
+
<optgroup label="Sunset">
|
|
39
|
+
<option value="sunset-dark">Sunset Dark</option>
|
|
40
|
+
<option value="sunset-light">Sunset Light</option>
|
|
41
|
+
</optgroup>
|
|
42
|
+
<optgroup label="Royal">
|
|
43
|
+
<option value="royal-dark">Royal Dark</option>
|
|
44
|
+
<option value="royal-light">Royal Light</option>
|
|
45
|
+
</optgroup>
|
|
46
|
+
<optgroup label="Lemon">
|
|
47
|
+
<option value="lemon-dark">Lemon Dark</option>
|
|
48
|
+
<option value="lemon-light">Lemon Light</option>
|
|
49
|
+
</optgroup>
|
|
50
|
+
<optgroup label="Silver">
|
|
51
|
+
<option value="silver-dark">Silver Dark</option>
|
|
52
|
+
<option value="silver-light">Silver Light</option>
|
|
53
|
+
</optgroup>
|
|
54
|
+
<optgroup label="Other">
|
|
55
|
+
<option value="grayve">Grayve</option>
|
|
56
|
+
<option value="christmas-dark">Christmas Dark</option>
|
|
57
|
+
<option value="christmas-light">Christmas Light</option>
|
|
58
|
+
</optgroup>
|
|
59
|
+
</select>
|
|
60
|
+
<p class="form-hint text-muted" style="margin-top:0.4rem;font-size:0.8rem;">Applied to the public-facing website.</p>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="col-6">
|
|
63
|
+
<label class="form-label">Admin Theme</label>
|
|
64
|
+
<select id="field-admin-theme" class="form-select">
|
|
65
|
+
<optgroup label="Charcoal">
|
|
66
|
+
<option value="charcoal-dark">Charcoal Dark</option>
|
|
67
|
+
<option value="charcoal-light">Charcoal Light</option>
|
|
68
|
+
</optgroup>
|
|
69
|
+
<optgroup label="Ocean">
|
|
70
|
+
<option value="ocean-dark">Ocean Dark</option>
|
|
71
|
+
<option value="ocean-light">Ocean Light</option>
|
|
72
|
+
</optgroup>
|
|
73
|
+
<optgroup label="Forest">
|
|
74
|
+
<option value="forest-dark">Forest Dark</option>
|
|
75
|
+
<option value="forest-light">Forest Light</option>
|
|
76
|
+
</optgroup>
|
|
77
|
+
<optgroup label="Sunset">
|
|
78
|
+
<option value="sunset-dark">Sunset Dark</option>
|
|
79
|
+
<option value="sunset-light">Sunset Light</option>
|
|
80
|
+
</optgroup>
|
|
81
|
+
<optgroup label="Royal">
|
|
82
|
+
<option value="royal-dark">Royal Dark</option>
|
|
83
|
+
<option value="royal-light">Royal Light</option>
|
|
84
|
+
</optgroup>
|
|
85
|
+
<optgroup label="Lemon">
|
|
86
|
+
<option value="lemon-dark">Lemon Dark</option>
|
|
87
|
+
<option value="lemon-light">Lemon Light</option>
|
|
88
|
+
</optgroup>
|
|
89
|
+
<optgroup label="Silver">
|
|
90
|
+
<option value="silver-dark">Silver Dark</option>
|
|
91
|
+
<option value="silver-light">Silver Light</option>
|
|
92
|
+
</optgroup>
|
|
93
|
+
<optgroup label="Other">
|
|
94
|
+
<option value="grayve">Grayve</option>
|
|
95
|
+
<option value="christmas-dark">Christmas Dark</option>
|
|
96
|
+
<option value="christmas-light">Christmas Light</option>
|
|
97
|
+
</optgroup>
|
|
98
|
+
</select>
|
|
99
|
+
<p class="form-hint text-muted" style="margin-top:0.4rem;font-size:0.8rem;">Applied to the admin panel.</p>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div class="card card-collapsible mb-4">
|
|
106
|
+
<div class="card-header" role="button" tabindex="0">
|
|
107
|
+
<div class="card-header-content"><h2>SEO Defaults</h2></div>
|
|
108
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="card-body">
|
|
111
|
+
<div class="row mb-3">
|
|
112
|
+
<div class="col-8">
|
|
113
|
+
<label class="form-label">Default Site Title</label>
|
|
114
|
+
<input id="field-seo-title" type="text" class="form-input" placeholder="My Site">
|
|
115
|
+
</div>
|
|
116
|
+
<div class="col-4">
|
|
117
|
+
<label class="form-label">Title Separator</label>
|
|
118
|
+
<input id="field-seo-separator" type="text" class="form-input" placeholder=" | ">
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="row">
|
|
122
|
+
<div class="col">
|
|
123
|
+
<label class="form-label">Default Meta Description</label>
|
|
124
|
+
<input id="field-seo-desc" type="text" class="form-input" placeholder="Brief description of your site">
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div class="card card-collapsible mb-4">
|
|
131
|
+
<div class="card-header" role="button" tabindex="0">
|
|
132
|
+
<div class="card-header-content"><h2>Footer</h2></div>
|
|
133
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="card-body">
|
|
136
|
+
<div class="row">
|
|
137
|
+
<div class="col">
|
|
138
|
+
<label class="form-label">Copyright Text</label>
|
|
139
|
+
<input id="field-footer-copy" type="text" class="form-input" placeholder="© 2026 My Site">
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div class="card card-collapsible mb-4">
|
|
146
|
+
<div class="card-header" role="button" tabindex="0">
|
|
147
|
+
<div class="card-header-content"><h2>Email / SMTP</h2></div>
|
|
148
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
149
|
+
</div>
|
|
150
|
+
<div class="card-body">
|
|
151
|
+
<div class="row mb-3">
|
|
152
|
+
<div class="col-8">
|
|
153
|
+
<label class="form-label">SMTP Host</label>
|
|
154
|
+
<input id="field-smtp-host" type="text" class="form-input" placeholder="smtp.example.com">
|
|
155
|
+
</div>
|
|
156
|
+
<div class="col-4">
|
|
157
|
+
<label class="form-label">Port</label>
|
|
158
|
+
<input id="field-smtp-port" type="number" class="form-input" placeholder="587" value="587">
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
<div class="row mb-3">
|
|
162
|
+
<div class="col-6">
|
|
163
|
+
<label class="form-label">Username</label>
|
|
164
|
+
<input id="field-smtp-user" type="text" class="form-input" placeholder="user@example.com" autocomplete="off">
|
|
165
|
+
</div>
|
|
166
|
+
<div class="col-6">
|
|
167
|
+
<label class="form-label">Password</label>
|
|
168
|
+
<input id="field-smtp-pass" type="password" class="form-input" placeholder="••••••••" autocomplete="new-password">
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
<div class="row mb-3">
|
|
172
|
+
<div class="col">
|
|
173
|
+
<label class="form-check-label" style="display:flex;align-items:center;gap:.5rem;cursor:pointer;">
|
|
174
|
+
<input id="field-smtp-secure" type="checkbox" class="form-check"> Use TLS (port 465)
|
|
175
|
+
</label>
|
|
176
|
+
<p class="form-hint text-muted" style="margin-top:.35rem;font-size:.8rem;">Enable for SMTPS. Leave off for STARTTLS (port 587).</p>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
<div class="row">
|
|
180
|
+
<div class="col-6">
|
|
181
|
+
<label class="form-label">From Address</label>
|
|
182
|
+
<input id="field-smtp-from-address" type="email" class="form-input" placeholder="noreply@example.com">
|
|
183
|
+
</div>
|
|
184
|
+
<div class="col-6">
|
|
185
|
+
<label class="form-label">From Name</label>
|
|
186
|
+
<input id="field-smtp-from-name" type="text" class="form-input" placeholder="My Website">
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="document"></span> Tutorials</h1>
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
<div class="row">
|
|
6
|
+
<div class="col-12">
|
|
7
|
+
|
|
8
|
+
<!-- Writing a Plugin -->
|
|
9
|
+
<div class="card card-collapsible mb-4">
|
|
10
|
+
<div class="card-header" role="button" tabindex="0">
|
|
11
|
+
<div class="card-header-content"><h2><span data-icon="package"></span> Writing a Plugin</h2></div>
|
|
12
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="card-body docs-body">
|
|
15
|
+
<p>Plugins live in the <code>plugins/</code> directory. Each plugin is a self-contained folder with three
|
|
16
|
+
required files and optional <code>admin/</code> and <code>public/</code> subdirectories.</p>
|
|
17
|
+
|
|
18
|
+
<h3>Directory structure</h3>
|
|
19
|
+
<pre class="code-block"><code>plugins/
|
|
20
|
+
my-plugin/
|
|
21
|
+
plugin.json ← manifest (required)
|
|
22
|
+
plugin.js ← Fastify plugin (required)
|
|
23
|
+
config.js ← settings defaults (required)
|
|
24
|
+
admin/
|
|
25
|
+
views/
|
|
26
|
+
my-view.js ← admin SPA view (optional)
|
|
27
|
+
templates/
|
|
28
|
+
my-view.html ← view template (optional)
|
|
29
|
+
public/
|
|
30
|
+
inject-head.html ← injected into <head> on every page (optional)
|
|
31
|
+
inject-body.html ← injected before </body> on every page (optional)
|
|
32
|
+
data/ ← plugin data store (optional, not publicly served)</code></pre>
|
|
33
|
+
|
|
34
|
+
<hr>
|
|
35
|
+
|
|
36
|
+
<h3>1. plugin.json — the manifest</h3>
|
|
37
|
+
<p>All fields below are <strong>required</strong>. Missing any will cause the plugin to be skipped on startup
|
|
38
|
+
with a warning in the server log.</p>
|
|
39
|
+
<pre class="code-block"><code>{
|
|
40
|
+
"name": "my-plugin",
|
|
41
|
+
"displayName": "My Plugin",
|
|
42
|
+
"version": "1.0.0",
|
|
43
|
+
"description": "A short description shown on the Plugins page.",
|
|
44
|
+
"author": "Your Name",
|
|
45
|
+
"date": "2026-03-01",
|
|
46
|
+
"icon": "star"
|
|
47
|
+
}</code></pre>
|
|
48
|
+
|
|
49
|
+
<p>Optional fields:</p>
|
|
50
|
+
<table class="table table-sm">
|
|
51
|
+
<thead>
|
|
52
|
+
<tr>
|
|
53
|
+
<th>Field</th>
|
|
54
|
+
<th>Type</th>
|
|
55
|
+
<th>Description</th>
|
|
56
|
+
</tr>
|
|
57
|
+
</thead>
|
|
58
|
+
<tbody>
|
|
59
|
+
<tr>
|
|
60
|
+
<td><code>inject.head</code></td>
|
|
61
|
+
<td>string</td>
|
|
62
|
+
<td>Path (relative to plugin root) to an HTML snippet injected into <code><head></code>.</td>
|
|
63
|
+
</tr>
|
|
64
|
+
<tr>
|
|
65
|
+
<td><code>inject.bodyEnd</code></td>
|
|
66
|
+
<td>string</td>
|
|
67
|
+
<td>Path to an HTML snippet injected before <code></body></code>.</td>
|
|
68
|
+
</tr>
|
|
69
|
+
<tr>
|
|
70
|
+
<td><code>admin.sidebar</code></td>
|
|
71
|
+
<td>array</td>
|
|
72
|
+
<td>Sidebar items to add to the admin panel.</td>
|
|
73
|
+
</tr>
|
|
74
|
+
<tr>
|
|
75
|
+
<td><code>admin.routes</code></td>
|
|
76
|
+
<td>array</td>
|
|
77
|
+
<td>SPA routes to register in the admin router.</td>
|
|
78
|
+
</tr>
|
|
79
|
+
<tr>
|
|
80
|
+
<td><code>admin.views</code></td>
|
|
81
|
+
<td>object</td>
|
|
82
|
+
<td>View modules to dynamically import into the admin SPA.</td>
|
|
83
|
+
</tr>
|
|
84
|
+
</tbody>
|
|
85
|
+
</table>
|
|
86
|
+
|
|
87
|
+
<hr>
|
|
88
|
+
|
|
89
|
+
<h3>2. plugin.js — the Fastify plugin</h3>
|
|
90
|
+
<p>This is the server-side entry point. It must export a default <strong>async function</strong> that Fastify
|
|
91
|
+
will call with <code>(fastify, options)</code>.</p>
|
|
92
|
+
<p>The CMS injects auth middleware through <code>options.auth</code> — always destructure from there rather than
|
|
93
|
+
importing directly.</p>
|
|
94
|
+
|
|
95
|
+
<pre class="code-block"><code>import { getPluginSettings, savePluginState } from '../../server/services/plugins.js';
|
|
96
|
+
|
|
97
|
+
export default async function myPlugin(fastify, options) {
|
|
98
|
+
const { authenticate, requireAdmin } = options.auth;
|
|
99
|
+
|
|
100
|
+
// Public endpoint — no auth needed
|
|
101
|
+
fastify.get('/hello', async () => {
|
|
102
|
+
return { message: 'Hello from my plugin!' };
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Admin-only endpoint
|
|
106
|
+
fastify.get('/settings', { preHandler: [authenticate, requireAdmin] }, async () => {
|
|
107
|
+
return getPluginSettings('my-plugin');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
fastify.put('/settings', { preHandler: [authenticate, requireAdmin] }, async (request) => {
|
|
111
|
+
savePluginState('my-plugin', { settings: request.body });
|
|
112
|
+
return { ok: true };
|
|
113
|
+
});
|
|
114
|
+
}</code></pre>
|
|
115
|
+
|
|
116
|
+
<p>Routes are registered under the prefix <code>/api/plugins/{name}</code> automatically. You do not set the
|
|
117
|
+
prefix yourself — it is always locked to your plugin's directory name.</p>
|
|
118
|
+
|
|
119
|
+
<hr>
|
|
120
|
+
|
|
121
|
+
<h3>3. config.js — settings defaults</h3>
|
|
122
|
+
<p>Export a plain object of default settings. These are merged with any user overrides stored in <code>config/plugins.json</code>
|
|
123
|
+
when <code>getPluginSettings()</code> is called.</p>
|
|
124
|
+
|
|
125
|
+
<pre class="code-block"><code>export default {
|
|
126
|
+
greeting: 'Hello, world!',
|
|
127
|
+
enableFeature: true,
|
|
128
|
+
maxItems: 10
|
|
129
|
+
};</code></pre>
|
|
130
|
+
|
|
131
|
+
<p><code>config.js</code> is only loaded for <strong>enabled</strong> plugins. Side-effect code here will not
|
|
132
|
+
run for disabled plugins.</p>
|
|
133
|
+
|
|
134
|
+
<hr>
|
|
135
|
+
|
|
136
|
+
<h3>4. Admin views (optional)</h3>
|
|
137
|
+
<p>To add a page to the admin panel, declare the route and view in <code>plugin.json</code>:</p>
|
|
138
|
+
|
|
139
|
+
<pre class="code-block"><code>"admin": {
|
|
140
|
+
"sidebar": [
|
|
141
|
+
{
|
|
142
|
+
"id": "my-plugin",
|
|
143
|
+
"text": "My Plugin",
|
|
144
|
+
"icon": "star",
|
|
145
|
+
"url": "#/plugins/my-plugin",
|
|
146
|
+
"section": "#/plugins/my-plugin"
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
"routes": [
|
|
150
|
+
{
|
|
151
|
+
"path": "/plugins/my-plugin",
|
|
152
|
+
"view": "plugin-my-plugin",
|
|
153
|
+
"title": "My Plugin - Domma CMS"
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
"views": {
|
|
157
|
+
"plugin-my-plugin": {
|
|
158
|
+
"entry": "my-plugin/admin/views/my-view.js",
|
|
159
|
+
"exportName": "myPluginView"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}</code></pre>
|
|
163
|
+
|
|
164
|
+
<p>The view file follows the standard Domma view pattern — a <code>templateUrl</code> and an <code>onMount($container)</code>
|
|
165
|
+
function:</p>
|
|
166
|
+
|
|
167
|
+
<pre class="code-block"><code>// admin/views/my-view.js
|
|
168
|
+
export const myPluginView = {
|
|
169
|
+
templateUrl: '/plugins/my-plugin/admin/templates/my-view.html',
|
|
170
|
+
|
|
171
|
+
async onMount($container) {
|
|
172
|
+
const res = await fetch('/api/plugins/my-plugin/settings', {
|
|
173
|
+
headers: { 'Authorization': 'Bearer ' + (S.get('auth_token') || '') }
|
|
174
|
+
});
|
|
175
|
+
const settings = await res.json();
|
|
176
|
+
|
|
177
|
+
$container.find('#greeting').text(settings.greeting);
|
|
178
|
+
Domma.icons.scan();
|
|
179
|
+
}
|
|
180
|
+
};</code></pre>
|
|
181
|
+
|
|
182
|
+
<p>The template is a plain HTML fragment (no <code><html></code> wrapper). Use the same card and form
|
|
183
|
+
patterns as the rest of the admin panel:</p>
|
|
184
|
+
|
|
185
|
+
<pre class="code-block"><code><!-- admin/templates/my-view.html -->
|
|
186
|
+
<div class="view-header">
|
|
187
|
+
<h1><span data-icon="star"></span> My Plugin</h1>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<div class="card">
|
|
191
|
+
<div class="card-body">
|
|
192
|
+
<p id="greeting">Loading…</p>
|
|
193
|
+
</div>
|
|
194
|
+
</div></code></pre>
|
|
195
|
+
|
|
196
|
+
<hr>
|
|
197
|
+
|
|
198
|
+
<h3>5. Injection snippets (optional)</h3>
|
|
199
|
+
<p>HTML snippets declared in <code>inject.head</code> and <code>inject.bodyEnd</code> are read from the plugin's
|
|
200
|
+
<code>public/</code> directory and inserted into every public page. Use this for analytics scripts,
|
|
201
|
+
stylesheets, or widgets.</p>
|
|
202
|
+
|
|
203
|
+
<pre class="code-block"><code><!-- public/inject-body.html -->
|
|
204
|
+
<script>
|
|
205
|
+
(function () {
|
|
206
|
+
// This runs on every public page
|
|
207
|
+
fetch('/api/plugins/my-plugin/hello')
|
|
208
|
+
.then(r => r.json())
|
|
209
|
+
.then(d => console.log(d.message));
|
|
210
|
+
})();
|
|
211
|
+
</script></code></pre>
|
|
212
|
+
|
|
213
|
+
<p>Snippet paths are validated — they must stay within the plugin's own directory. Paths containing
|
|
214
|
+
<code>..</code> are blocked.</p>
|
|
215
|
+
|
|
216
|
+
<hr>
|
|
217
|
+
|
|
218
|
+
<h3>6. Registering and testing</h3>
|
|
219
|
+
<ol>
|
|
220
|
+
<li>Create the <code>plugins/my-plugin/</code> directory with all three required files.</li>
|
|
221
|
+
<li>Restart the server — you should see <code>[plugins] Loaded N plugins: …, my-plugin</code> in the log.</li>
|
|
222
|
+
<li>Go to the <a href="#/plugins">Plugins page</a> and enable your plugin.</li>
|
|
223
|
+
<li>Restart the server again to register the routes.</li>
|
|
224
|
+
<li>Verify your endpoint: <code>GET /api/plugins/my-plugin/hello</code></li>
|
|
225
|
+
</ol>
|
|
226
|
+
|
|
227
|
+
<p class="text-muted" style="font-size:.9rem">Tip: use <code>npm run dev</code> during development — the server
|
|
228
|
+
restarts automatically on file changes.</p>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="user"></span> <span id="editor-title">New User</span></h1>
|
|
3
|
+
<div class="view-header-actions">
|
|
4
|
+
<button id="cancel-btn" class="btn btn-ghost">Cancel</button>
|
|
5
|
+
</div>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="card">
|
|
9
|
+
<div class="card-body">
|
|
10
|
+
<div id="user-form-container"></div>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="users"></span> Users</h1>
|
|
3
|
+
<a href="#/users/new" class="btn btn-primary"><span data-icon="plus"></span> New User</a>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="card">
|
|
7
|
+
<div class="card-body">
|
|
8
|
+
<div id="users-table"></div>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard View
|
|
3
|
+
* Shows page count stats and a recent pages table.
|
|
4
|
+
*/
|
|
5
|
+
import { api } from '../api.js';
|
|
6
|
+
|
|
7
|
+
export const dashboardView = {
|
|
8
|
+
templateUrl: '/admin/js/templates/dashboard.html',
|
|
9
|
+
|
|
10
|
+
async onMount($container) {
|
|
11
|
+
const pages = await api.pages.list().catch(() => []);
|
|
12
|
+
const total = pages.length;
|
|
13
|
+
const published = pages.filter(p => p.status === 'published').length;
|
|
14
|
+
const drafts = pages.filter(p => p.status === 'draft').length;
|
|
15
|
+
|
|
16
|
+
$container.find('#stat-total').text(total);
|
|
17
|
+
$container.find('#stat-published').text(published);
|
|
18
|
+
$container.find('#stat-drafts').text(drafts);
|
|
19
|
+
|
|
20
|
+
Domma.effects.counter('#stat-total, #stat-published, #stat-drafts', {
|
|
21
|
+
trigger: 'immediate',
|
|
22
|
+
duration: 1000,
|
|
23
|
+
stagger: 120,
|
|
24
|
+
easing: 'ease-out'
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const recent = [...pages]
|
|
28
|
+
.sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''))
|
|
29
|
+
.slice(0, 10);
|
|
30
|
+
|
|
31
|
+
T.create('#recent-pages-table', {
|
|
32
|
+
data: recent,
|
|
33
|
+
columns: [
|
|
34
|
+
{ key: 'title', label: 'Title' },
|
|
35
|
+
{ key: 'urlPath', label: 'URL' },
|
|
36
|
+
{ key: 'status', label: 'Status', render: (val) => `<span class="badge badge-${val === 'published' ? 'success' : 'warning'}">${val}</span>` },
|
|
37
|
+
{ key: 'updatedAt', label: 'Updated', render: (val) => val ? D(val).format('DD MMM YYYY') : '—' },
|
|
38
|
+
{ key: 'urlPath', label: '', render: (val) => `<a href="#/pages/edit${val}" class="btn btn-sm btn-outline">Edit</a>` }
|
|
39
|
+
],
|
|
40
|
+
emptyMessage: 'No pages yet. <a href="#/pages/new">Create your first page</a>.'
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
Domma.icons.scan();
|
|
44
|
+
|
|
45
|
+
Domma.effects.reveal('.stat-card', { animation: 'fade', stagger: 80, duration: 400 });
|
|
46
|
+
Domma.effects.reveal('.card.mt-4', { animation: 'fade', delay: 200, duration: 400 });
|
|
47
|
+
}
|
|
48
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation View
|
|
3
|
+
* Usage reference for Domma CMS — content, structure, plugins, settings.
|
|
4
|
+
*/
|
|
5
|
+
export const documentationView = {
|
|
6
|
+
templateUrl: '/admin/js/templates/documentation.html',
|
|
7
|
+
|
|
8
|
+
async onMount($container) {
|
|
9
|
+
Domma.icons.scan();
|
|
10
|
+
Domma.syntax.scan();
|
|
11
|
+
}
|
|
12
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View Registry
|
|
3
|
+
* Maps route view names to their view modules.
|
|
4
|
+
*/
|
|
5
|
+
import {dashboardView} from './dashboard.js';
|
|
6
|
+
import {pagesView} from './pages.js';
|
|
7
|
+
import {pageEditorView} from './page-editor.js';
|
|
8
|
+
import {settingsView} from './settings.js';
|
|
9
|
+
import {navigationView} from './navigation.js';
|
|
10
|
+
import {layoutsView} from './layouts.js';
|
|
11
|
+
import {mediaView} from './media.js';
|
|
12
|
+
import {loginView} from './login.js';
|
|
13
|
+
import {usersView} from './users.js';
|
|
14
|
+
import {userEditorView} from './user-editor.js';
|
|
15
|
+
import {pluginsView} from './plugins.js';
|
|
16
|
+
import {documentationView} from './documentation.js';
|
|
17
|
+
import {tutorialsView} from './tutorials.js';
|
|
18
|
+
|
|
19
|
+
export const views = {
|
|
20
|
+
dashboard: dashboardView,
|
|
21
|
+
pages: pagesView,
|
|
22
|
+
pageEditor: pageEditorView,
|
|
23
|
+
settings: settingsView,
|
|
24
|
+
navigation: navigationView,
|
|
25
|
+
layouts: layoutsView,
|
|
26
|
+
media: mediaView,
|
|
27
|
+
login: loginView,
|
|
28
|
+
users: usersView,
|
|
29
|
+
userEditor: userEditorView,
|
|
30
|
+
plugins: pluginsView,
|
|
31
|
+
documentation: documentationView,
|
|
32
|
+
tutorials: tutorialsView
|
|
33
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layouts View
|
|
3
|
+
*/
|
|
4
|
+
import { api } from '../api.js';
|
|
5
|
+
|
|
6
|
+
export const layoutsView = {
|
|
7
|
+
templateUrl: '/admin/js/templates/layouts.html',
|
|
8
|
+
|
|
9
|
+
async onMount($container) {
|
|
10
|
+
let presets = await api.layouts.get().catch(() => ({}));
|
|
11
|
+
const $grid = $container.find('#presets-grid').empty();
|
|
12
|
+
|
|
13
|
+
Object.entries(presets).forEach(([key, preset]) => {
|
|
14
|
+
$grid.append(`
|
|
15
|
+
<div class="preset-card card" data-key="${key}">
|
|
16
|
+
<div class="card-header">
|
|
17
|
+
<h3>${preset.label || key}</h3>
|
|
18
|
+
<span class="badge">${key}</span>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="card-body">
|
|
21
|
+
<p class="text-muted">${preset.description || ''}</p>
|
|
22
|
+
<div class="preset-toggles">
|
|
23
|
+
<label class="toggle-label"><input type="checkbox" class="form-check preset-navbar" ${preset.navbar ? 'checked' : ''}> Navbar</label>
|
|
24
|
+
<label class="toggle-label"><input type="checkbox" class="form-check preset-footer" ${preset.footer ? 'checked' : ''}> Footer</label>
|
|
25
|
+
<label class="toggle-label"><input type="checkbox" class="form-check preset-sidebar" ${preset.sidebar ? 'checked' : ''}> Sidebar</label>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
`);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
$container.find('#save-layouts-btn').on('click', async () => {
|
|
33
|
+
$container.find('.preset-card').each(function () {
|
|
34
|
+
const key = $(this).data('key');
|
|
35
|
+
if (presets[key]) {
|
|
36
|
+
presets[key].navbar = $(this).find('.preset-navbar').is(':checked');
|
|
37
|
+
presets[key].footer = $(this).find('.preset-footer').is(':checked');
|
|
38
|
+
presets[key].sidebar = $(this).find('.preset-sidebar').is(':checked');
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
try {
|
|
42
|
+
await api.layouts.save(presets);
|
|
43
|
+
E.toast('Layouts saved.', { type: 'success' });
|
|
44
|
+
} catch {
|
|
45
|
+
E.toast('Failed to save layouts.', { type: 'error' });
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
};
|