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
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Settings View
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import {api, apiRequest} from '../api.js';
|
|
5
5
|
|
|
6
6
|
export const settingsView = {
|
|
7
7
|
templateUrl: '/admin/js/templates/settings.html',
|
|
8
8
|
|
|
9
9
|
async onMount($container) {
|
|
10
|
+
E.tabs($container.find('#settings-tabs').get(0));
|
|
11
|
+
|
|
10
12
|
const s = await api.settings.get().catch(() => ({}));
|
|
11
13
|
$container.find('#field-site-title').val(s.title || '');
|
|
12
14
|
$container.find('#field-tagline').val(s.tagline || '');
|
|
15
|
+
$container.find('#field-font-family').val(s.fontFamily || 'Roboto');
|
|
16
|
+
$container.find('#field-font-size').val(s.fontSize || 16);
|
|
13
17
|
$container.find('#field-theme').val(s.theme || 'charcoal-dark');
|
|
14
18
|
$container.find('#field-admin-theme').val(s.adminTheme || 'charcoal-dark');
|
|
15
19
|
$container.find('#field-seo-title').val(s.seo?.defaultTitle || '');
|
|
@@ -17,7 +21,14 @@ export const settingsView = {
|
|
|
17
21
|
$container.find('#field-seo-desc').val(s.seo?.defaultDescription || '');
|
|
18
22
|
$container.find('#field-footer-copy').val(s.footer?.copyright || '');
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
// Social fields
|
|
25
|
+
$container.find('#field-social-twitter').val(s.social?.twitter || '');
|
|
26
|
+
$container.find('#field-social-facebook').val(s.social?.facebook || '');
|
|
27
|
+
$container.find('#field-social-instagram').val(s.social?.instagram || '');
|
|
28
|
+
$container.find('#field-social-linkedin').val(s.social?.linkedin || '');
|
|
29
|
+
$container.find('#field-social-github').val(s.social?.github || '');
|
|
30
|
+
$container.find('#field-social-youtube').val(s.social?.youtube || '');
|
|
31
|
+
|
|
21
32
|
$container.find('#field-smtp-host').val(s.smtp?.host || '');
|
|
22
33
|
$container.find('#field-smtp-port').val(s.smtp?.port || 587);
|
|
23
34
|
$container.find('#field-smtp-user').val(s.smtp?.user || '');
|
|
@@ -26,11 +37,50 @@ export const settingsView = {
|
|
|
26
37
|
$container.find('#field-smtp-from-address').val(s.smtp?.fromAddress || '');
|
|
27
38
|
$container.find('#field-smtp-from-name').val(s.smtp?.fromName || '');
|
|
28
39
|
|
|
40
|
+
// Back to Top
|
|
41
|
+
const btt = s.backToTop || {};
|
|
42
|
+
$container.find('#field-btt-enabled').prop('checked', btt.enabled !== false);
|
|
43
|
+
$container.find('#field-btt-threshold').val(btt.scrollThreshold ?? 300);
|
|
44
|
+
$container.find('#field-btt-position').val(btt.position || 'bottom-right');
|
|
45
|
+
$container.find('#field-btt-offset').val(btt.offset ?? 32);
|
|
46
|
+
$container.find('#field-btt-label').val(btt.label || '');
|
|
47
|
+
$container.find('#field-btt-smooth').prop('checked', btt.smooth !== false);
|
|
48
|
+
|
|
49
|
+
// Cookie Consent
|
|
50
|
+
const cc = s.cookieConsent || {};
|
|
51
|
+
$container.find('#field-cc-enabled').prop('checked', cc.enabled !== false);
|
|
52
|
+
$container.find('#field-cc-message').val(cc.message || '');
|
|
53
|
+
$container.find('#field-cc-accept-all').val(cc.acceptAllText || 'Accept All');
|
|
54
|
+
$container.find('#field-cc-reject-all').val(cc.rejectAllText || 'Reject All');
|
|
55
|
+
$container.find('#field-cc-customize').val(cc.customizeText || 'Customize');
|
|
56
|
+
$container.find('#field-cc-save-prefs').val(cc.savePreferencesText || 'Save Preferences');
|
|
57
|
+
$container.find('#field-cc-privacy-text').val(cc.privacyPolicyText || 'Privacy Policy');
|
|
58
|
+
$container.find('#field-cc-privacy-url').val(cc.privacyPolicyUrl || '');
|
|
59
|
+
$container.find('#field-cc-cookie-text').val(cc.cookiePolicyText || 'Cookie Policy');
|
|
60
|
+
$container.find('#field-cc-cookie-url').val(cc.cookiePolicyUrl || '');
|
|
61
|
+
$container.find('#field-cc-position').val(cc.position || 'bottom');
|
|
62
|
+
$container.find('#field-cc-layout').val(cc.layout || 'bar');
|
|
63
|
+
$container.find('#field-cc-theme').val(cc.theme || 'dark');
|
|
64
|
+
$container.find('#field-cc-show-functional').prop('checked', cc.showFunctional !== false);
|
|
65
|
+
$container.find('#field-cc-show-analytics').prop('checked', cc.showAnalytics !== false);
|
|
66
|
+
$container.find('#field-cc-show-marketing').prop('checked', cc.showMarketing !== false);
|
|
67
|
+
$container.find('#field-cc-version').val(cc.consentVersion || '1.0');
|
|
68
|
+
|
|
69
|
+
// Custom CSS
|
|
70
|
+
try {
|
|
71
|
+
const { css } = await apiRequest('/settings/custom-css');
|
|
72
|
+
$container.find('#field-custom-css').val(css || '');
|
|
73
|
+
} catch {
|
|
74
|
+
// non-fatal — textarea stays empty
|
|
75
|
+
}
|
|
76
|
+
|
|
29
77
|
$container.find('#save-settings-btn').on('click', async () => {
|
|
30
78
|
const adminTheme = $container.find('#field-admin-theme').val();
|
|
31
79
|
const data = {
|
|
32
80
|
title: $container.find('#field-site-title').val().trim(),
|
|
33
81
|
tagline: $container.find('#field-tagline').val().trim(),
|
|
82
|
+
fontFamily: $container.find('#field-font-family').val() || 'Roboto',
|
|
83
|
+
fontSize: parseInt($container.find('#field-font-size').val(), 10) || 16,
|
|
34
84
|
theme: $container.find('#field-theme').val(),
|
|
35
85
|
adminTheme,
|
|
36
86
|
seo: {
|
|
@@ -42,6 +92,14 @@ export const settingsView = {
|
|
|
42
92
|
copyright: $container.find('#field-footer-copy').val().trim(),
|
|
43
93
|
links: s.footer?.links || []
|
|
44
94
|
},
|
|
95
|
+
social: {
|
|
96
|
+
twitter: $container.find('#field-social-twitter').val().trim(),
|
|
97
|
+
facebook: $container.find('#field-social-facebook').val().trim(),
|
|
98
|
+
instagram: $container.find('#field-social-instagram').val().trim(),
|
|
99
|
+
linkedin: $container.find('#field-social-linkedin').val().trim(),
|
|
100
|
+
github: $container.find('#field-social-github').val().trim(),
|
|
101
|
+
youtube: $container.find('#field-social-youtube').val().trim()
|
|
102
|
+
},
|
|
45
103
|
smtp: {
|
|
46
104
|
host: $container.find('#field-smtp-host').val().trim(),
|
|
47
105
|
port: parseInt($container.find('#field-smtp-port').val(), 10) || 587,
|
|
@@ -50,14 +108,91 @@ export const settingsView = {
|
|
|
50
108
|
secure: $container.find('#field-smtp-secure').prop('checked'),
|
|
51
109
|
fromAddress: $container.find('#field-smtp-from-address').val().trim(),
|
|
52
110
|
fromName: $container.find('#field-smtp-from-name').val().trim()
|
|
111
|
+
},
|
|
112
|
+
backToTop: {
|
|
113
|
+
enabled: $container.find('#field-btt-enabled').prop('checked'),
|
|
114
|
+
scrollThreshold: parseInt($container.find('#field-btt-threshold').val(), 10) || 300,
|
|
115
|
+
position: $container.find('#field-btt-position').val() || 'bottom-right',
|
|
116
|
+
offset: parseInt($container.find('#field-btt-offset').val(), 10) || 32,
|
|
117
|
+
label: $container.find('#field-btt-label').val().trim(),
|
|
118
|
+
smooth: $container.find('#field-btt-smooth').prop('checked')
|
|
119
|
+
},
|
|
120
|
+
cookieConsent: {
|
|
121
|
+
enabled: $container.find('#field-cc-enabled').prop('checked'),
|
|
122
|
+
message: $container.find('#field-cc-message').val().trim(),
|
|
123
|
+
acceptAllText: $container.find('#field-cc-accept-all').val().trim(),
|
|
124
|
+
rejectAllText: $container.find('#field-cc-reject-all').val().trim(),
|
|
125
|
+
customizeText: $container.find('#field-cc-customize').val().trim(),
|
|
126
|
+
savePreferencesText: $container.find('#field-cc-save-prefs').val().trim(),
|
|
127
|
+
privacyPolicyText: $container.find('#field-cc-privacy-text').val().trim(),
|
|
128
|
+
privacyPolicyUrl: $container.find('#field-cc-privacy-url').val().trim(),
|
|
129
|
+
cookiePolicyText: $container.find('#field-cc-cookie-text').val().trim(),
|
|
130
|
+
cookiePolicyUrl: $container.find('#field-cc-cookie-url').val().trim(),
|
|
131
|
+
position: $container.find('#field-cc-position').val(),
|
|
132
|
+
layout: $container.find('#field-cc-layout').val(),
|
|
133
|
+
theme: $container.find('#field-cc-theme').val(),
|
|
134
|
+
showFunctional: $container.find('#field-cc-show-functional').prop('checked'),
|
|
135
|
+
showAnalytics: $container.find('#field-cc-show-analytics').prop('checked'),
|
|
136
|
+
showMarketing: $container.find('#field-cc-show-marketing').prop('checked'),
|
|
137
|
+
consentVersion: $container.find('#field-cc-version').val().trim() || '1.0'
|
|
53
138
|
}
|
|
54
139
|
};
|
|
140
|
+
const $btn = $container.find('#save-settings-btn');
|
|
141
|
+
$btn.prop('disabled', true);
|
|
55
142
|
try {
|
|
56
143
|
await api.settings.save(data);
|
|
57
144
|
Domma.theme.set(adminTheme);
|
|
58
145
|
E.toast('Settings saved.', { type: 'success' });
|
|
59
146
|
} catch {
|
|
60
147
|
E.toast('Failed to save settings.', { type: 'error' });
|
|
148
|
+
} finally {
|
|
149
|
+
$btn.prop('disabled', false);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Send test email
|
|
154
|
+
$container.find('#send-test-email-btn').on('click', async () => {
|
|
155
|
+
const to = $container.find('#field-test-email-to').val().trim();
|
|
156
|
+
const resultEl = $container.find('#test-email-result').get(0);
|
|
157
|
+
const $btn = $container.find('#send-test-email-btn');
|
|
158
|
+
|
|
159
|
+
$btn.prop('disabled', true);
|
|
160
|
+
if (resultEl) { resultEl.textContent = 'Sending…'; resultEl.style.color = ''; }
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const res = await apiRequest('/settings/test-email', {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
body: JSON.stringify({ to: to || undefined })
|
|
166
|
+
});
|
|
167
|
+
if (resultEl) {
|
|
168
|
+
resultEl.textContent = res.message || 'Test email sent.';
|
|
169
|
+
resultEl.style.color = 'var(--success,#4ade80)';
|
|
170
|
+
}
|
|
171
|
+
} catch (err) {
|
|
172
|
+
if (resultEl) {
|
|
173
|
+
resultEl.textContent = err.message || 'Failed to send test email.';
|
|
174
|
+
resultEl.style.color = 'var(--danger,#f87171)';
|
|
175
|
+
}
|
|
176
|
+
} finally {
|
|
177
|
+
$btn.prop('disabled', false);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Save Custom CSS
|
|
182
|
+
$container.find('#save-css-btn').on('click', async () => {
|
|
183
|
+
const css = $container.find('#field-custom-css').val();
|
|
184
|
+
const $btn = $container.find('#save-css-btn');
|
|
185
|
+
$btn.prop('disabled', true);
|
|
186
|
+
try {
|
|
187
|
+
await apiRequest('/settings/custom-css', {
|
|
188
|
+
method: 'PUT',
|
|
189
|
+
body: JSON.stringify({ css })
|
|
190
|
+
});
|
|
191
|
+
E.toast('Custom CSS saved.', { type: 'success' });
|
|
192
|
+
} catch (err) {
|
|
193
|
+
E.toast(err.message || 'Failed to save CSS.', { type: 'error' });
|
|
194
|
+
} finally {
|
|
195
|
+
$btn.prop('disabled', false);
|
|
61
196
|
}
|
|
62
197
|
});
|
|
63
198
|
}
|
package/admin/js/views/users.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Users List View
|
|
3
3
|
* Shows all users in a Domma table with role badges.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import {api, getUser} from '../api.js';
|
|
6
6
|
|
|
7
7
|
const ROLE_BADGE = {
|
|
8
8
|
admin: 'badge-danger',
|
|
@@ -22,18 +22,21 @@ export const usersView = {
|
|
|
22
22
|
T.create('#users-table', {
|
|
23
23
|
data,
|
|
24
24
|
columns: [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
{key: 'name', title: 'Name'},
|
|
26
|
+
{key: 'email', title: 'Email'},
|
|
27
|
+
{
|
|
28
|
+
key: 'role', title: 'Role',
|
|
28
29
|
render: (val) => `<span class="badge ${ROLE_BADGE[val] || 'badge-secondary'}">${val}</span>` },
|
|
29
|
-
|
|
30
|
+
{
|
|
31
|
+
key: 'isActive', title: 'Status',
|
|
30
32
|
render: (val) => val
|
|
31
33
|
? '<span class="badge badge-success">Active</span>'
|
|
32
34
|
: '<span class="badge badge-secondary">Inactive</span>' },
|
|
33
|
-
|
|
35
|
+
{
|
|
36
|
+
key: 'lastLogin', title: 'Last login',
|
|
34
37
|
render: (val) => val ? D(val).format('DD MMM YYYY HH:mm') : 'Never' },
|
|
35
38
|
{
|
|
36
|
-
|
|
39
|
+
key: 'actions', title: 'Actions',
|
|
37
40
|
render: (_, row) => {
|
|
38
41
|
const isSelf = row.id === currentUser?.id;
|
|
39
42
|
return `
|
package/bin/cli.js
CHANGED
|
@@ -152,8 +152,26 @@ done();
|
|
|
152
152
|
// ---------------------------------------------------------------------------
|
|
153
153
|
|
|
154
154
|
step('Resetting plugin data');
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
// Read scaffold.reset entries from each plugin.json and apply them
|
|
156
|
+
const copiedPluginsDir = path.join(target, 'plugins');
|
|
157
|
+
try {
|
|
158
|
+
const pluginDirs = readdirSync(copiedPluginsDir, {withFileTypes: true})
|
|
159
|
+
.filter(e => e.isDirectory())
|
|
160
|
+
.map(e => e.name);
|
|
161
|
+
for (const pluginName of pluginDirs) {
|
|
162
|
+
const manifestPath = path.join(copiedPluginsDir, pluginName, 'plugin.json');
|
|
163
|
+
try {
|
|
164
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
165
|
+
for (const {path: relPath, content} of (manifest.scaffold?.reset || [])) {
|
|
166
|
+
const absPath = path.join(copiedPluginsDir, pluginName, relPath);
|
|
167
|
+
mkdirSync(path.dirname(absPath), {recursive: true});
|
|
168
|
+
writeFileSync(absPath, content + '\n', 'utf8');
|
|
169
|
+
}
|
|
170
|
+
} catch { /* missing or invalid plugin.json — skip */
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} catch { /* plugins dir not found — skip */
|
|
174
|
+
}
|
|
157
175
|
done();
|
|
158
176
|
|
|
159
177
|
// ---------------------------------------------------------------------------
|
|
@@ -189,14 +207,23 @@ const DEFAULT_NAV = {
|
|
|
189
207
|
position: 'sticky'
|
|
190
208
|
};
|
|
191
209
|
|
|
192
|
-
//
|
|
193
|
-
const DEFAULT_PLUGINS = {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
210
|
+
// Discover all plugins and default each to disabled in a fresh project
|
|
211
|
+
const DEFAULT_PLUGINS = {};
|
|
212
|
+
try {
|
|
213
|
+
const pluginsRoot = path.join(target, 'plugins');
|
|
214
|
+
const pluginDirs = readdirSync(pluginsRoot, {withFileTypes: true})
|
|
215
|
+
.filter(e => e.isDirectory())
|
|
216
|
+
.map(e => e.name);
|
|
217
|
+
for (const name of pluginDirs) {
|
|
218
|
+
const manifestPath = path.join(pluginsRoot, name, 'plugin.json');
|
|
219
|
+
try {
|
|
220
|
+
JSON.parse(readFileSync(manifestPath, 'utf8')); // validate manifest exists
|
|
221
|
+
DEFAULT_PLUGINS[name] = {enabled: false, settings: {}};
|
|
222
|
+
} catch { /* no valid plugin.json — skip */
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch { /* plugins dir not found — leave empty */
|
|
226
|
+
}
|
|
200
227
|
|
|
201
228
|
step('Writing default config');
|
|
202
229
|
writeFileSync(path.join(target, 'config/site.json'), JSON.stringify(DEFAULT_SITE, null, 4) + '\n', 'utf8');
|
package/config/auth.json
CHANGED
package/config/content.json
CHANGED
package/config/navigation.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"brand": {
|
|
3
|
-
"text": "My Site",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"text": "My Dead Good Site",
|
|
4
|
+
"url": "/",
|
|
5
|
+
"icon": "home"
|
|
6
6
|
},
|
|
7
7
|
"items": [
|
|
8
8
|
{
|
|
@@ -54,10 +54,20 @@
|
|
|
54
54
|
"text": "Shortcode Reference",
|
|
55
55
|
"url": "/resources/shortcodes",
|
|
56
56
|
"icon": "code"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"text": "Effects",
|
|
60
|
+
"url": "/resources/effects",
|
|
61
|
+
"icon": "zap"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"text": "Interactive",
|
|
65
|
+
"url": "/resources/interactive",
|
|
66
|
+
"icon": "mouse-pointer"
|
|
57
67
|
}
|
|
58
68
|
]
|
|
59
69
|
}
|
|
60
70
|
],
|
|
61
|
-
"variant": "
|
|
71
|
+
"variant": "transparent",
|
|
62
72
|
"position": "sticky"
|
|
63
73
|
}
|
package/config/plugins.json
CHANGED
|
@@ -17,24 +17,6 @@
|
|
|
17
17
|
"fromName": "Website Forms"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
|
-
"back-to-top": {
|
|
21
|
-
"enabled": true,
|
|
22
|
-
"settings": {
|
|
23
|
-
"scrollThreshold": 150,
|
|
24
|
-
"position": "bottom-right",
|
|
25
|
-
"label": "",
|
|
26
|
-
"smooth": true,
|
|
27
|
-
"offset": 32
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
"cookie-consent": {
|
|
31
|
-
"enabled": true,
|
|
32
|
-
"settings": {}
|
|
33
|
-
},
|
|
34
|
-
"custom-css": {
|
|
35
|
-
"enabled": true,
|
|
36
|
-
"settings": {}
|
|
37
|
-
},
|
|
38
20
|
"domma-effects": {
|
|
39
21
|
"enabled": true,
|
|
40
22
|
"settings": {
|
package/config/presets.json
CHANGED
|
@@ -4,31 +4,27 @@
|
|
|
4
4
|
"description": "Standard page with navbar and footer",
|
|
5
5
|
"navbar": true,
|
|
6
6
|
"footer": true,
|
|
7
|
-
|
|
8
|
-
"features": ["back-to-top", "icon-scan"]
|
|
7
|
+
"sidebar": false
|
|
9
8
|
},
|
|
10
9
|
"with-sidebar": {
|
|
11
10
|
"label": "With Sidebar",
|
|
12
11
|
"description": "Page with navbar, sidebar, and footer",
|
|
13
12
|
"navbar": true,
|
|
14
13
|
"footer": true,
|
|
15
|
-
|
|
16
|
-
"features": ["back-to-top", "icon-scan", "scroll-spy"]
|
|
14
|
+
"sidebar": true
|
|
17
15
|
},
|
|
18
16
|
"minimal": {
|
|
19
17
|
"label": "Minimal",
|
|
20
18
|
"description": "Clean page with no navbar or footer",
|
|
21
19
|
"navbar": false,
|
|
22
20
|
"footer": false,
|
|
23
|
-
|
|
24
|
-
"features": []
|
|
21
|
+
"sidebar": false
|
|
25
22
|
},
|
|
26
23
|
"landing": {
|
|
27
24
|
"label": "Landing Page",
|
|
28
25
|
"description": "Full-width landing page layout",
|
|
29
26
|
"navbar": true,
|
|
30
27
|
"footer": true,
|
|
31
|
-
|
|
32
|
-
"features": ["back-to-top", "icon-scan"]
|
|
28
|
+
"sidebar": false
|
|
33
29
|
}
|
|
34
30
|
}
|
package/config/site.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"title": "My Boss Site",
|
|
3
|
-
"tagline": "My Dead Good
|
|
4
|
-
"
|
|
3
|
+
"tagline": "My Dead Good Site",
|
|
4
|
+
"fontFamily": "Roboto",
|
|
5
|
+
"fontSize": 16,
|
|
6
|
+
"theme": "charcoal-dark",
|
|
5
7
|
"adminTheme": "charcoal-dark",
|
|
6
8
|
"seo": {
|
|
7
9
|
"defaultTitle": "My Boss Site",
|
|
@@ -9,7 +11,7 @@
|
|
|
9
11
|
"defaultDescription": "A site built with Domma CMS"
|
|
10
12
|
},
|
|
11
13
|
"footer": {
|
|
12
|
-
"copyright": "© 2026 DCBW Consulting Ltd
|
|
14
|
+
"copyright": "© 2026 DCBW Consulting Ltd",
|
|
13
15
|
"links": [
|
|
14
16
|
{
|
|
15
17
|
"text": "Privacy Policy",
|
|
@@ -18,9 +20,21 @@
|
|
|
18
20
|
{
|
|
19
21
|
"text": "Contact",
|
|
20
22
|
"url": "/contact"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"text": "GDPR",
|
|
26
|
+
"url": "/gdpr"
|
|
21
27
|
}
|
|
22
28
|
]
|
|
23
29
|
},
|
|
30
|
+
"social": {
|
|
31
|
+
"twitter": "",
|
|
32
|
+
"facebook": "",
|
|
33
|
+
"instagram": "",
|
|
34
|
+
"linkedin": "",
|
|
35
|
+
"github": "https://github.com/pinpointzero73/",
|
|
36
|
+
"youtube": ""
|
|
37
|
+
},
|
|
24
38
|
"smtp": {
|
|
25
39
|
"host": "localhost",
|
|
26
40
|
"port": 1025,
|
|
@@ -29,5 +43,32 @@
|
|
|
29
43
|
"secure": false,
|
|
30
44
|
"fromAddress": "",
|
|
31
45
|
"fromName": ""
|
|
46
|
+
},
|
|
47
|
+
"backToTop": {
|
|
48
|
+
"enabled": true,
|
|
49
|
+
"scrollThreshold": 150,
|
|
50
|
+
"position": "bottom-right",
|
|
51
|
+
"label": "",
|
|
52
|
+
"smooth": true,
|
|
53
|
+
"offset": 48
|
|
54
|
+
},
|
|
55
|
+
"cookieConsent": {
|
|
56
|
+
"enabled": true,
|
|
57
|
+
"message": "We use cookies to enhance your browsing experience, serve personalised content, and analyse our traffic. By clicking \"Accept All\", you consent to our use of cookies.",
|
|
58
|
+
"acceptAllText": "Accept All",
|
|
59
|
+
"rejectAllText": "Reject All",
|
|
60
|
+
"customizeText": "Customize",
|
|
61
|
+
"savePreferencesText": "Save Preferences",
|
|
62
|
+
"privacyPolicyText": "Privacy Policy",
|
|
63
|
+
"privacyPolicyUrl": "/privacy-policy",
|
|
64
|
+
"cookiePolicyText": "Cookie Policy",
|
|
65
|
+
"cookiePolicyUrl": "",
|
|
66
|
+
"position": "bottom",
|
|
67
|
+
"layout": "bar",
|
|
68
|
+
"theme": "dark",
|
|
69
|
+
"showFunctional": true,
|
|
70
|
+
"showAnalytics": true,
|
|
71
|
+
"showMarketing": true,
|
|
72
|
+
"consentVersion": "1.0"
|
|
32
73
|
}
|
|
33
74
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domma-cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/server.js",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"scripts/"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
+
"build": "node scripts/build.js",
|
|
20
21
|
"start": "pm2 start server/server.js -i max --name domma-cms",
|
|
21
22
|
"dev": "PORT=3050 node --watch server/server.js",
|
|
22
23
|
"prod": "node server/server.js",
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
"@fastify/multipart": "^9.3.0",
|
|
55
56
|
"@fastify/static": "^8.1.0",
|
|
56
57
|
"bcryptjs": "^3.0.3",
|
|
57
|
-
"domma-js": "^0.
|
|
58
|
+
"domma-js": "^0.19.1",
|
|
58
59
|
"dotenv": "^17.2.3",
|
|
59
60
|
"fastify": "^5.7.3",
|
|
60
61
|
"gray-matter": "^4.0.3",
|
|
@@ -63,5 +64,8 @@
|
|
|
63
64
|
"sanitize-html": "^2.17.0",
|
|
64
65
|
"sharp": "^0.34.5",
|
|
65
66
|
"uuid": "^13.0.0"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"esbuild": "^0.27.3"
|
|
66
70
|
}
|
|
67
71
|
}
|
|
@@ -176,12 +176,101 @@ Content here.
|
|
|
176
176
|
<!-- Text tab -->
|
|
177
177
|
<div id="tab-text" class="effects-tab-panel">
|
|
178
178
|
<h3 class="mb-2">Typewriter (Scribe)</h3>
|
|
179
|
-
<p class="text-muted mb-2">Types text character by character.
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
<p class="text-muted mb-2">Types text character by character. Supports a simple one-shot mode or a full
|
|
180
|
+
action script for sequenced multi-step animations.</p>
|
|
181
|
+
|
|
182
|
+
<h4 class="mb-1" style="font-size:.9rem;">Simple mode</h4>
|
|
183
|
+
<pre class="code-block mb-2">[scribe speed="50" cursor="true" loop="false"]
|
|
182
184
|
Your text appears letter by letter.
|
|
183
185
|
[/scribe]</pre>
|
|
184
186
|
|
|
187
|
+
<h4 class="mb-1" style="font-size:.9rem;">Script mode — action shortcodes</h4>
|
|
188
|
+
<pre class="code-block mb-2">[scribe loop="true" loop-delay="2000"]
|
|
189
|
+
[render effect="fade"]Hello, world![/render]
|
|
190
|
+
[wait]1500[/wait]
|
|
191
|
+
[undo /]
|
|
192
|
+
[render]Something else entirely.[/render]
|
|
193
|
+
[/scribe]</pre>
|
|
194
|
+
<p class="text-muted mb-2" style="font-size:.85rem;">When any <code>[render]</code>, <code>[wait]</code>, or
|
|
195
|
+
<code>[undo]</code> shortcode is present inside the body, script mode activates automatically.</p>
|
|
196
|
+
<table class="table mb-2">
|
|
197
|
+
<thead>
|
|
198
|
+
<tr>
|
|
199
|
+
<th>Action</th>
|
|
200
|
+
<th>Syntax</th>
|
|
201
|
+
<th>Description</th>
|
|
202
|
+
</tr>
|
|
203
|
+
</thead>
|
|
204
|
+
<tbody>
|
|
205
|
+
<tr>
|
|
206
|
+
<td><code>[render]</code></td>
|
|
207
|
+
<td><code>[render effect="fade"]Text[/render]</code></td>
|
|
208
|
+
<td>Type the text. Optional <code>effect</code>: fade, slide (for appearance)</td>
|
|
209
|
+
</tr>
|
|
210
|
+
<tr>
|
|
211
|
+
<td><code>[wait]</code></td>
|
|
212
|
+
<td><code>[wait]1500[/wait]</code></td>
|
|
213
|
+
<td>Pause for N milliseconds (number) or a CSS duration string (e.g. <code>2s</code>)</td>
|
|
214
|
+
</tr>
|
|
215
|
+
<tr>
|
|
216
|
+
<td><code>[undo]</code></td>
|
|
217
|
+
<td><code>[undo /]</code> or <code>[undo count="3" /]</code> or <code>[undo all="true" /]</code></td>
|
|
218
|
+
<td>Delete the last render action, N renders, or all renders</td>
|
|
219
|
+
</tr>
|
|
220
|
+
</tbody>
|
|
221
|
+
</table>
|
|
222
|
+
<table class="table mb-3">
|
|
223
|
+
<thead>
|
|
224
|
+
<tr>
|
|
225
|
+
<th>Attribute</th>
|
|
226
|
+
<th>Default</th>
|
|
227
|
+
<th>Description</th>
|
|
228
|
+
</tr>
|
|
229
|
+
</thead>
|
|
230
|
+
<tbody>
|
|
231
|
+
<tr>
|
|
232
|
+
<td><code>speed</code></td>
|
|
233
|
+
<td>50</td>
|
|
234
|
+
<td>Typing speed (ms per character)</td>
|
|
235
|
+
</tr>
|
|
236
|
+
<tr>
|
|
237
|
+
<td><code>delete-speed</code></td>
|
|
238
|
+
<td>30</td>
|
|
239
|
+
<td>Deletion speed (ms per character)</td>
|
|
240
|
+
</tr>
|
|
241
|
+
<tr>
|
|
242
|
+
<td><code>cursor</code></td>
|
|
243
|
+
<td>true</td>
|
|
244
|
+
<td>Show blinking cursor (true/false)</td>
|
|
245
|
+
</tr>
|
|
246
|
+
<tr>
|
|
247
|
+
<td><code>cursor-char</code></td>
|
|
248
|
+
<td>|</td>
|
|
249
|
+
<td>Character to use as cursor</td>
|
|
250
|
+
</tr>
|
|
251
|
+
<tr>
|
|
252
|
+
<td><code>cursor-type</code></td>
|
|
253
|
+
<td>—</td>
|
|
254
|
+
<td>Cursor style (block, underline, beam)</td>
|
|
255
|
+
</tr>
|
|
256
|
+
<tr>
|
|
257
|
+
<td><code>loop</code></td>
|
|
258
|
+
<td>false</td>
|
|
259
|
+
<td>Repeat script continuously (true/false)</td>
|
|
260
|
+
</tr>
|
|
261
|
+
<tr>
|
|
262
|
+
<td><code>loop-delay</code></td>
|
|
263
|
+
<td>1000</td>
|
|
264
|
+
<td>Pause before repeating (ms)</td>
|
|
265
|
+
</tr>
|
|
266
|
+
<tr>
|
|
267
|
+
<td><code>pause-on-hover</code></td>
|
|
268
|
+
<td>false</td>
|
|
269
|
+
<td>Pause animation while hovered (true/false)</td>
|
|
270
|
+
</tr>
|
|
271
|
+
</tbody>
|
|
272
|
+
</table>
|
|
273
|
+
|
|
185
274
|
<h3 class="mb-2">Scramble</h3>
|
|
186
275
|
<p class="text-muted mb-2">Reveals text through a character-scramble animation. Great for tech/data
|
|
187
276
|
aesthetics.</p>
|