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
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
function buildBlueprintFromFields(fields) {
|
|
18
18
|
var blueprint = {};
|
|
19
19
|
fields.forEach(function (field) {
|
|
20
|
-
|
|
20
|
+
if (field.type === 'page-break' || field.type === 'spacer') return;
|
|
21
21
|
var def = {
|
|
22
22
|
type: field.type,
|
|
23
23
|
label: field.label || field.name,
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
group = [];
|
|
53
53
|
title = field.label || ('Step ' + (steps.length + 1));
|
|
54
54
|
desc = field.description || '';
|
|
55
|
-
} else {
|
|
55
|
+
} else if (field.type !== 'spacer') {
|
|
56
56
|
group.push(field);
|
|
57
57
|
}
|
|
58
58
|
});
|
|
@@ -101,6 +101,12 @@
|
|
|
101
101
|
}
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
// Init conditional logic engine on wizard form
|
|
105
|
+
if (window.FormLogicEngine && fields.some(function (f) { return f.logic; })) {
|
|
106
|
+
var wizardRuntime = new window.FormLogicEngine.FormLogicRuntime(form, wrapper);
|
|
107
|
+
wizardRuntime.init();
|
|
108
|
+
}
|
|
109
|
+
|
|
104
110
|
// Inject honeypot into the first step's form element
|
|
105
111
|
if (settings.honeypot) {
|
|
106
112
|
var firstForm = wrapper.querySelector('form');
|
|
@@ -121,6 +127,18 @@
|
|
|
121
127
|
}
|
|
122
128
|
});
|
|
123
129
|
|
|
130
|
+
// Init conditional logic engine on standard form
|
|
131
|
+
if (window.FormLogicEngine && fields.some(function (f) { return f.logic; })) {
|
|
132
|
+
var stdRuntime = new window.FormLogicEngine.FormLogicRuntime(form, wrapper);
|
|
133
|
+
stdRuntime.init();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (fields.some(function (f) {
|
|
137
|
+
return f.type === 'spacer';
|
|
138
|
+
})) {
|
|
139
|
+
injectSpacers(wrapper, fields);
|
|
140
|
+
}
|
|
141
|
+
|
|
124
142
|
if (settings.honeypot) {
|
|
125
143
|
var formEl = wrapper.querySelector('form');
|
|
126
144
|
if (formEl) injectHoneypot(formEl);
|
|
@@ -129,7 +147,9 @@
|
|
|
129
147
|
|
|
130
148
|
} else {
|
|
131
149
|
// Fallback: manual form rendering (no Domma dependency)
|
|
132
|
-
|
|
150
|
+
renderManualForm(wrapper, fields.filter(function (f) {
|
|
151
|
+
return f.type !== 'page-break' && f.type !== 'spacer';
|
|
152
|
+
}), settings, slug, form);
|
|
133
153
|
}
|
|
134
154
|
})
|
|
135
155
|
.catch(function (err) {
|
|
@@ -141,6 +161,33 @@
|
|
|
141
161
|
});
|
|
142
162
|
}
|
|
143
163
|
|
|
164
|
+
/**
|
|
165
|
+
* After Domma renders a form, insert .fb-spacer divs at the positions
|
|
166
|
+
* where spacer pseudo-fields appear in the original fields array.
|
|
167
|
+
*/
|
|
168
|
+
function injectSpacers(wrapper, allFields) {
|
|
169
|
+
var form = wrapper.querySelector('form');
|
|
170
|
+
if (!form) return;
|
|
171
|
+
var groups = Array.from(form.querySelectorAll('.form-group'));
|
|
172
|
+
var groupIdx = 0;
|
|
173
|
+
allFields.forEach(function (field) {
|
|
174
|
+
if (field.type === 'spacer') {
|
|
175
|
+
var spacer = document.createElement('div');
|
|
176
|
+
spacer.className = 'fb-spacer';
|
|
177
|
+
var target = groups[groupIdx];
|
|
178
|
+
if (target) {
|
|
179
|
+
form.insertBefore(spacer, target);
|
|
180
|
+
} else {
|
|
181
|
+
var submitBtn = form.querySelector('[type="submit"]');
|
|
182
|
+
if (submitBtn) form.insertBefore(spacer, submitBtn);
|
|
183
|
+
else form.appendChild(spacer);
|
|
184
|
+
}
|
|
185
|
+
} else if (field.type !== 'page-break') {
|
|
186
|
+
groupIdx++;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
144
191
|
function injectHoneypot(formEl) {
|
|
145
192
|
var hpWrap = document.createElement('div');
|
|
146
193
|
hpWrap.className = 'fb-form-honeypot';
|
|
@@ -186,7 +233,7 @@
|
|
|
186
233
|
}
|
|
187
234
|
|
|
188
235
|
// Manual form renderer — used when Domma forms API is unavailable
|
|
189
|
-
function renderManualForm(wrapper, fields, settings, slug) {
|
|
236
|
+
function renderManualForm(wrapper, fields, settings, slug, formDef) {
|
|
190
237
|
var form = document.createElement('form');
|
|
191
238
|
form.noValidate = true;
|
|
192
239
|
|
|
@@ -246,8 +293,6 @@
|
|
|
246
293
|
|
|
247
294
|
form.addEventListener('submit', function (e) {
|
|
248
295
|
e.preventDefault();
|
|
249
|
-
wrapper.classList.add('fb-form-loading');
|
|
250
|
-
submitBtn.disabled = true;
|
|
251
296
|
|
|
252
297
|
var data = {};
|
|
253
298
|
fields.forEach(function (field) {
|
|
@@ -255,6 +300,37 @@
|
|
|
255
300
|
if (el) data[field.name] = el.value;
|
|
256
301
|
});
|
|
257
302
|
|
|
303
|
+
// Client-side logic validation for manual fallback
|
|
304
|
+
if (window.FormLogicEngine && formDef) {
|
|
305
|
+
var Engine = window.FormLogicEngine;
|
|
306
|
+
var requiredErrors = [];
|
|
307
|
+
var validationErrors = [];
|
|
308
|
+
fields.forEach(function (field) {
|
|
309
|
+
var vis = Engine.evaluateFieldVisibility(field, data);
|
|
310
|
+
if (vis === 'hidden') {
|
|
311
|
+
delete data[field.name];
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
var required = Engine.evaluateFieldRequirement(field, data);
|
|
315
|
+
var val = data[field.name];
|
|
316
|
+
if (required && (!val || !String(val).trim())) {
|
|
317
|
+
requiredErrors.push(field.label || field.name);
|
|
318
|
+
}
|
|
319
|
+
var errors = Engine.validateField(field, val || '', data);
|
|
320
|
+
if (errors.length) validationErrors.push(errors[0].message);
|
|
321
|
+
});
|
|
322
|
+
if (requiredErrors.length || validationErrors.length) {
|
|
323
|
+
var errParts = [];
|
|
324
|
+
if (requiredErrors.length) errParts.push('Required: ' + requiredErrors.join(', '));
|
|
325
|
+
if (validationErrors.length) errParts.push(validationErrors.join('; '));
|
|
326
|
+
showMessage(wrapper, errParts.join('. '), 'error');
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
wrapper.classList.add('fb-form-loading');
|
|
332
|
+
submitBtn.disabled = true;
|
|
333
|
+
|
|
258
334
|
submitForm(slug, data, settings, wrapper, form).finally(function () {
|
|
259
335
|
wrapper.classList.remove('fb-form-loading');
|
|
260
336
|
submitBtn.disabled = false;
|
|
@@ -262,6 +338,12 @@
|
|
|
262
338
|
});
|
|
263
339
|
|
|
264
340
|
wrapper.appendChild(form);
|
|
341
|
+
|
|
342
|
+
// Init conditional logic engine on manual form
|
|
343
|
+
if (window.FormLogicEngine && formDef && fields.some(function (f) { return f.logic; })) {
|
|
344
|
+
var manualRuntime = new window.FormLogicEngine.FormLogicRuntime(formDef, wrapper);
|
|
345
|
+
manualRuntime.init();
|
|
346
|
+
}
|
|
265
347
|
}
|
|
266
348
|
|
|
267
349
|
// Initialise all form targets on the page
|
|
@@ -39,4 +39,20 @@
|
|
|
39
39
|
.wizard-step-title { font-size: 1.1rem; font-weight: 600; margin-bottom: .25rem; }
|
|
40
40
|
.wizard-step-description { font-size: .875rem; color: var(--text-muted, #888); margin-bottom: 1rem; }
|
|
41
41
|
.wizard-nav { display: flex; justify-content: space-between; gap: .5rem; margin-top: 1rem; }
|
|
42
|
+
|
|
43
|
+
.fb-field-hidden { display: none !important; }
|
|
44
|
+
.fb-validation-error { color: var(--danger, #ef4444); font-size: .8rem; margin-top: .25rem; }
|
|
45
|
+
|
|
46
|
+
.fb-form-wrapper .domma-form-field {
|
|
47
|
+
margin-bottom: 2rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.fb-form-wrapper .form-group:not(.form-buttons) {
|
|
51
|
+
margin-bottom: 2rem;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.fb-spacer {
|
|
55
|
+
height: 2rem;
|
|
56
|
+
}
|
|
42
57
|
</style>
|
|
58
|
+
<script src="/plugins/form-builder/public/form-logic-engine.js"></script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "type": "commonjs" }
|
package/public/css/site.css
CHANGED
|
@@ -29,6 +29,11 @@ body, button, input, select, textarea {
|
|
|
29
29
|
overflow: hidden;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
.table-responsive {
|
|
33
|
+
overflow-x: auto;
|
|
34
|
+
-webkit-overflow-scrolling: touch;
|
|
35
|
+
}
|
|
36
|
+
|
|
32
37
|
.container {
|
|
33
38
|
max-width: 860px;
|
|
34
39
|
margin: 0 auto;
|
|
@@ -147,6 +152,8 @@ body, button, input, select, textarea {
|
|
|
147
152
|
/* Navbar icon tweaks */
|
|
148
153
|
.navbar-link span[data-icon],
|
|
149
154
|
.navbar-link svg,
|
|
155
|
+
.navbar-dropdown-toggle span[data-icon],
|
|
156
|
+
.navbar-dropdown-toggle svg,
|
|
150
157
|
.navbar-dropdown-item span[data-icon],
|
|
151
158
|
.navbar-dropdown-item svg {
|
|
152
159
|
width: 13px !important;
|
|
@@ -154,6 +161,33 @@ body, button, input, select, textarea {
|
|
|
154
161
|
margin-right: 10px !important;
|
|
155
162
|
}
|
|
156
163
|
|
|
164
|
+
/* Normalise dropdown toggle font size to match regular nav links at every breakpoint */
|
|
165
|
+
.navbar-dropdown-toggle {
|
|
166
|
+
font-size: var(--dm-font-size-base);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@media (min-width: 993px) {
|
|
170
|
+
.navbar-dropdown-toggle {
|
|
171
|
+
font-size: var(--dm-font-size-sm);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@media (min-width: 1201px) {
|
|
176
|
+
.navbar-dropdown-toggle {
|
|
177
|
+
font-size: var(--dm-font-size-xs);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Reduced motion override — applied when user enables the footer toggle */
|
|
182
|
+
.dm-reduced-motion *,
|
|
183
|
+
.dm-reduced-motion *::before,
|
|
184
|
+
.dm-reduced-motion *::after {
|
|
185
|
+
animation-duration: 0.001ms !important;
|
|
186
|
+
animation-iteration-count: 1 !important;
|
|
187
|
+
transition-duration: 0.001ms !important;
|
|
188
|
+
scroll-behavior: auto !important;
|
|
189
|
+
}
|
|
190
|
+
|
|
157
191
|
/* Footer */
|
|
158
192
|
.page-footer {
|
|
159
193
|
border-top: 1px solid var(--border-color, rgba(255,255,255,.08));
|
|
@@ -183,7 +217,86 @@ body, button, input, select, textarea {
|
|
|
183
217
|
|
|
184
218
|
.footer-links a:hover { color: var(--text, #eee); }
|
|
185
219
|
|
|
220
|
+
.footer-social {
|
|
221
|
+
display: flex;
|
|
222
|
+
gap: 0.5rem;
|
|
223
|
+
align-items: center;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.footer-social-link {
|
|
227
|
+
display: inline-flex;
|
|
228
|
+
align-items: center;
|
|
229
|
+
justify-content: center;
|
|
230
|
+
width: 1.75rem;
|
|
231
|
+
height: 1.75rem;
|
|
232
|
+
color: var(--text-muted, #888);
|
|
233
|
+
transition: color 0.15s;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.footer-social-link:hover {
|
|
237
|
+
color: var(--text, #eee);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.footer-social-link svg {
|
|
241
|
+
width: 1rem;
|
|
242
|
+
height: 1rem;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.footer-motion-switch {
|
|
246
|
+
font-size: .8rem;
|
|
247
|
+
color: var(--text-muted, #888);
|
|
248
|
+
white-space: nowrap;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.footer-motion-switch .form-switch-label {
|
|
252
|
+
color: var(--text-muted, #888);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.footer-motion-switch .form-switch-input {
|
|
256
|
+
width: 2rem;
|
|
257
|
+
height: 1.125rem;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.footer-motion-switch .form-switch-input::after {
|
|
261
|
+
width: .875rem;
|
|
262
|
+
height: .875rem;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.footer-motion-switch .form-switch-input:checked::after {
|
|
266
|
+
transform: translateX(.875rem);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* Slideover panel layout */
|
|
270
|
+
.dm-slideover-header {
|
|
271
|
+
display: flex;
|
|
272
|
+
align-items: center;
|
|
273
|
+
justify-content: space-between;
|
|
274
|
+
padding: .875rem 1.25rem;
|
|
275
|
+
border-bottom: 1px solid var(--border-color, rgba(255, 255, 255, .08));
|
|
276
|
+
flex-shrink: 0;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.dm-slideover-title {
|
|
280
|
+
margin: 0;
|
|
281
|
+
font-size: 1rem;
|
|
282
|
+
font-weight: 600;
|
|
283
|
+
line-height: 1.4;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.dm-slideover-body {
|
|
287
|
+
padding: 1.25rem;
|
|
288
|
+
overflow-y: auto;
|
|
289
|
+
flex: 1;
|
|
290
|
+
}
|
|
291
|
+
|
|
186
292
|
@media (max-width: 768px) {
|
|
187
293
|
.site-main.with-sidebar { grid-template-columns: 1fr; }
|
|
188
294
|
.site-sidebar { display: none; }
|
|
189
295
|
}
|
|
296
|
+
|
|
297
|
+
/* Hero full-width breakout — escapes the .container to span the full viewport */
|
|
298
|
+
.hero-breakout {
|
|
299
|
+
width: 100vw;
|
|
300
|
+
margin-left: calc(50% - 50vw);
|
|
301
|
+
margin-right: calc(50% - 50vw);
|
|
302
|
+
}
|
package/public/js/btt.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var cfg = window.__CMS_SITE__ && window.__CMS_SITE__.backToTop;
|
|
5
|
+
if (!cfg) return;
|
|
6
|
+
|
|
7
|
+
var BTN_ID = 'back-to-top-btn';
|
|
8
|
+
var threshold = cfg.scrollThreshold || 300;
|
|
9
|
+
var position = cfg.position || 'bottom-right';
|
|
10
|
+
var label = cfg.label || '';
|
|
11
|
+
var smooth = cfg.smooth !== false;
|
|
12
|
+
var offset = cfg.offset || 32;
|
|
13
|
+
|
|
14
|
+
var btn = document.createElement('button');
|
|
15
|
+
btn.id = BTN_ID;
|
|
16
|
+
btn.type = 'button';
|
|
17
|
+
btn.setAttribute('aria-label', 'Back to top');
|
|
18
|
+
btn.setAttribute('title', 'Back to top');
|
|
19
|
+
|
|
20
|
+
var svgNS = 'http://www.w3.org/2000/svg';
|
|
21
|
+
var svg = document.createElementNS(svgNS, 'svg');
|
|
22
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
23
|
+
svg.setAttribute('width', '20');
|
|
24
|
+
svg.setAttribute('height', '20');
|
|
25
|
+
svg.setAttribute('fill', 'none');
|
|
26
|
+
svg.setAttribute('stroke', 'currentColor');
|
|
27
|
+
svg.setAttribute('stroke-width', '2');
|
|
28
|
+
svg.setAttribute('stroke-linecap', 'round');
|
|
29
|
+
svg.setAttribute('stroke-linejoin', 'round');
|
|
30
|
+
var path = document.createElementNS(svgNS, 'path');
|
|
31
|
+
path.setAttribute('d', 'M12 19V5M5 12l7-7 7 7');
|
|
32
|
+
svg.appendChild(path);
|
|
33
|
+
btn.appendChild(svg);
|
|
34
|
+
|
|
35
|
+
if (label) {
|
|
36
|
+
var span = document.createElement('span');
|
|
37
|
+
span.textContent = label;
|
|
38
|
+
span.style.marginLeft = '6px';
|
|
39
|
+
btn.appendChild(span);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
var isRight = position !== 'bottom-left';
|
|
43
|
+
Object.assign(btn.style, {
|
|
44
|
+
position: 'fixed',
|
|
45
|
+
bottom: offset + 'px',
|
|
46
|
+
right: isRight ? offset + 'px' : 'auto',
|
|
47
|
+
left: isRight ? 'auto' : offset + 'px',
|
|
48
|
+
zIndex: '9999',
|
|
49
|
+
display: 'flex',
|
|
50
|
+
alignItems: 'center',
|
|
51
|
+
padding: '10px',
|
|
52
|
+
borderRadius: '50%',
|
|
53
|
+
border: 'none',
|
|
54
|
+
cursor: 'pointer',
|
|
55
|
+
background: 'rgba(0,0,0,0.5)',
|
|
56
|
+
color: '#fff',
|
|
57
|
+
backdropFilter: 'blur(4px)',
|
|
58
|
+
opacity: '0',
|
|
59
|
+
transform: 'translateY(12px)',
|
|
60
|
+
transition: 'opacity .25s, transform .25s',
|
|
61
|
+
pointerEvents: 'none'
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (label) {
|
|
65
|
+
btn.style.borderRadius = '999px';
|
|
66
|
+
btn.style.padding = '10px 16px';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
document.body.appendChild(btn);
|
|
70
|
+
|
|
71
|
+
function show() {
|
|
72
|
+
btn.style.opacity = '1';
|
|
73
|
+
btn.style.transform = 'translateY(0)';
|
|
74
|
+
btn.style.pointerEvents = 'auto';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function hide() {
|
|
78
|
+
btn.style.opacity = '0';
|
|
79
|
+
btn.style.transform = 'translateY(12px)';
|
|
80
|
+
btn.style.pointerEvents = 'none';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
window.addEventListener('scroll', function () {
|
|
84
|
+
if (window.scrollY > threshold) { show(); } else { hide(); }
|
|
85
|
+
}, {passive: true});
|
|
86
|
+
|
|
87
|
+
btn.addEventListener('click', function () {
|
|
88
|
+
window.scrollTo({top: 0, behavior: smooth ? 'smooth' : 'auto'});
|
|
89
|
+
});
|
|
90
|
+
})();
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
if (!window.Domma || !window.Domma.components || !window.Domma.components.cookieConsent) return;
|
|
5
|
+
|
|
6
|
+
var s = window.__CMS_SITE__ && window.__CMS_SITE__.cookieConsent;
|
|
7
|
+
if (!s) return;
|
|
8
|
+
|
|
9
|
+
var categories = {
|
|
10
|
+
necessary: {
|
|
11
|
+
label: 'Necessary Cookies',
|
|
12
|
+
description: 'These cookies are essential for the website to function properly.',
|
|
13
|
+
required: true
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
if (s.showFunctional !== false) {
|
|
18
|
+
categories.functional = {
|
|
19
|
+
label: 'Functional Cookies',
|
|
20
|
+
description: 'These cookies enable personalised features and functionality.',
|
|
21
|
+
required: false
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (s.showAnalytics !== false) {
|
|
26
|
+
categories.analytics = {
|
|
27
|
+
label: 'Analytics Cookies',
|
|
28
|
+
description: 'These cookies help us understand how visitors interact with our website.',
|
|
29
|
+
required: false
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (s.showMarketing !== false) {
|
|
34
|
+
categories.marketing = {
|
|
35
|
+
label: 'Marketing Cookies',
|
|
36
|
+
description: 'These cookies are used to deliver relevant ads and marketing campaigns.',
|
|
37
|
+
required: false
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
var cfg = {
|
|
42
|
+
message: s.message,
|
|
43
|
+
acceptAllText: s.acceptAllText,
|
|
44
|
+
rejectAllText: s.rejectAllText,
|
|
45
|
+
customizeText: s.customizeText,
|
|
46
|
+
savePreferencesText: s.savePreferencesText,
|
|
47
|
+
privacyPolicyText: s.privacyPolicyText,
|
|
48
|
+
privacyPolicyUrl: s.privacyPolicyUrl || null,
|
|
49
|
+
cookiePolicyText: s.cookiePolicyText,
|
|
50
|
+
cookiePolicyUrl: s.cookiePolicyUrl || null,
|
|
51
|
+
position: s.position || 'bottom',
|
|
52
|
+
layout: s.layout || 'bar',
|
|
53
|
+
theme: s.theme || 'dark',
|
|
54
|
+
categories: categories,
|
|
55
|
+
consentVersion: s.consentVersion || '1.0',
|
|
56
|
+
autoShow: true,
|
|
57
|
+
animation: true
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
Domma.components.cookieConsent(cfg);
|
|
61
|
+
})();
|
package/public/js/site.js
CHANGED
|
@@ -10,29 +10,93 @@ $(() => {
|
|
|
10
10
|
// Initialise navbar
|
|
11
11
|
const $navbar = $('#site-navbar');
|
|
12
12
|
if ($navbar.length && nav.brand) {
|
|
13
|
+
// Build brand.html when an icon is configured — Domma navbar doesn't natively support data-icon spans
|
|
14
|
+
const brand = {...nav.brand};
|
|
15
|
+
if (brand.icon) {
|
|
16
|
+
let html = `<span data-icon="${brand.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`;
|
|
17
|
+
if (brand.text) html += `<span class="navbar-brand-text">${brand.text}</span>`;
|
|
18
|
+
brand.html = html;
|
|
19
|
+
}
|
|
13
20
|
Domma.elements.navbar('#site-navbar', {
|
|
14
|
-
|
|
21
|
+
brand,
|
|
15
22
|
items: nav.items || [],
|
|
16
23
|
variant: nav.variant || 'dark',
|
|
17
24
|
position: nav.position || 'sticky',
|
|
18
25
|
collapsible: true
|
|
19
26
|
});
|
|
27
|
+
Domma.icons.scan('#site-navbar');
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
// Initialise footer
|
|
23
31
|
const $footer = $('#site-footer');
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
if ($footer.length) {
|
|
33
|
+
const social = site.social || {};
|
|
34
|
+
const SOCIAL_ICONS = {
|
|
35
|
+
twitter: {
|
|
36
|
+
label: 'X / Twitter',
|
|
37
|
+
svg: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'
|
|
38
|
+
},
|
|
39
|
+
facebook: {
|
|
40
|
+
label: 'Facebook',
|
|
41
|
+
svg: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'
|
|
42
|
+
},
|
|
43
|
+
instagram: {
|
|
44
|
+
label: 'Instagram',
|
|
45
|
+
svg: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'
|
|
46
|
+
},
|
|
47
|
+
linkedin: {
|
|
48
|
+
label: 'LinkedIn',
|
|
49
|
+
svg: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'
|
|
50
|
+
},
|
|
51
|
+
github: {
|
|
52
|
+
label: 'GitHub',
|
|
53
|
+
svg: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'
|
|
54
|
+
},
|
|
55
|
+
youtube: {
|
|
56
|
+
label: 'YouTube',
|
|
57
|
+
svg: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
let html = '<div class="footer-inner container">';
|
|
62
|
+
|
|
63
|
+
if (site.footer) {
|
|
64
|
+
const footer = site.footer;
|
|
65
|
+
html += `<p>${footer.copyright || ''}</p>`;
|
|
66
|
+
if (footer.links?.length) {
|
|
67
|
+
html += `<nav class="footer-links">`;
|
|
68
|
+
footer.links.forEach(link => {
|
|
69
|
+
html += `<a href="${link.url}">${link.text}</a>`;
|
|
70
|
+
});
|
|
71
|
+
html += `</nav>`;
|
|
72
|
+
}
|
|
73
|
+
const activeSocial = Object.keys(SOCIAL_ICONS).filter(k => social[k]);
|
|
74
|
+
if (activeSocial.length) {
|
|
75
|
+
html += `<div class="footer-social">`;
|
|
76
|
+
activeSocial.forEach(k => {
|
|
77
|
+
const {label, svg} = SOCIAL_ICONS[k];
|
|
78
|
+
html += `<a href="${social[k]}" target="_blank" rel="noopener noreferrer" aria-label="${label}" class="footer-social-link">${svg}</a>`;
|
|
79
|
+
});
|
|
34
80
|
html += `</div>`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const storedPref = S.get('reduced_motion');
|
|
85
|
+
const motionOn = storedPref !== null
|
|
86
|
+
? !!storedPref
|
|
87
|
+
: !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
|
|
88
|
+
html += `<label class="form-switch footer-motion-switch" title="Reduce motion">` +
|
|
89
|
+
`<input type="checkbox" class="form-switch-input" id="dm-motion-switch"${motionOn ? ' checked' : ''}>` +
|
|
90
|
+
`<span class="form-switch-label">Reduce motion</span>` +
|
|
91
|
+
`</label>`;
|
|
92
|
+
|
|
93
|
+
html += '</div>';
|
|
35
94
|
$footer.html(html, { safe: false });
|
|
95
|
+
|
|
96
|
+
document.getElementById('dm-motion-switch').addEventListener('change', function () {
|
|
97
|
+
S.set('reduced_motion', this.checked);
|
|
98
|
+
window.location.reload();
|
|
99
|
+
});
|
|
36
100
|
}
|
|
37
101
|
|
|
38
102
|
// Initialise sidebar (auto-generate from headings if enabled)
|
|
@@ -50,34 +114,17 @@ $(() => {
|
|
|
50
114
|
// Icons
|
|
51
115
|
Domma.icons.scan();
|
|
52
116
|
|
|
53
|
-
// Back to top
|
|
54
|
-
if (document.querySelector('.site-main')) {
|
|
55
|
-
Domma.elements.backToTop?.();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
117
|
// -------------------------------------------------------
|
|
59
118
|
// Auto-initialise Domma components inside page content
|
|
60
119
|
// -------------------------------------------------------
|
|
61
120
|
const $pageBody = $('.page-body');
|
|
62
121
|
if ($pageBody.length) {
|
|
63
122
|
|
|
64
|
-
//
|
|
65
|
-
$pageBody.find('.dm-reveal').each(function () {
|
|
66
|
-
const $el = $(this);
|
|
67
|
-
const opts = {};
|
|
68
|
-
const classes = $el.attr('class') || '';
|
|
69
|
-
const m = classes.match(/dm-reveal-(fade|slide-up|slide-down|slide-left|slide-right|zoom|flip)/);
|
|
70
|
-
if (m) opts.animation = m[1];
|
|
71
|
-
if ($el.data('reveal-duration')) opts.duration = parseInt($el.data('reveal-duration'), 10);
|
|
72
|
-
if ($el.data('reveal-delay')) opts.delay = parseInt($el.data('reveal-delay'), 10);
|
|
73
|
-
if ($el.data('reveal-threshold')) opts.threshold = parseFloat($el.data('reveal-threshold'));
|
|
74
|
-
if ($el.data('reveal-once') === 'false') opts.once = false;
|
|
75
|
-
Domma.elements.reveal(this, opts);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Accordion
|
|
123
|
+
// Accordion — reads data-multi attribute set by [accordion multiple="true"] shortcode
|
|
79
124
|
$pageBody.find('.accordion').each(function () {
|
|
80
|
-
Domma.elements.accordion(this
|
|
125
|
+
Domma.elements.accordion(this, {
|
|
126
|
+
allowMultiple: this.dataset.multi === 'true'
|
|
127
|
+
});
|
|
81
128
|
});
|
|
82
129
|
|
|
83
130
|
// Tabs
|
|
@@ -85,9 +132,23 @@ $(() => {
|
|
|
85
132
|
Domma.elements.tabs(this);
|
|
86
133
|
});
|
|
87
134
|
|
|
88
|
-
// Carousel
|
|
135
|
+
// Carousel — reads data attributes set by [carousel] shortcode
|
|
89
136
|
$pageBody.find('.carousel').each(function () {
|
|
90
|
-
Domma.elements.carousel(this
|
|
137
|
+
Domma.elements.carousel(this, {
|
|
138
|
+
autoplay: this.dataset.autoplay === 'true',
|
|
139
|
+
interval: parseInt(this.dataset.interval, 10) || 5000,
|
|
140
|
+
loop: this.dataset.loop !== 'false',
|
|
141
|
+
animation: this.dataset.animation || 'slide'
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Countdown — initialises .dm-countdown elements via E.timer()
|
|
146
|
+
$pageBody.find('.dm-countdown').each(function () {
|
|
147
|
+
const opts = {autoStart: true};
|
|
148
|
+
if (this.dataset.to) opts.targetDate = new Date(this.dataset.to);
|
|
149
|
+
if (this.dataset.duration) opts.duration = parseInt(this.dataset.duration, 10);
|
|
150
|
+
if (this.dataset.format) opts.format = this.dataset.format;
|
|
151
|
+
Domma.elements.timer(this, opts);
|
|
91
152
|
});
|
|
92
153
|
|
|
93
154
|
// Tooltips
|
|
@@ -105,5 +166,39 @@ $(() => {
|
|
|
105
166
|
header.addEventListener('click', () => this.classList.toggle('is-collapsed'));
|
|
106
167
|
}
|
|
107
168
|
});
|
|
169
|
+
|
|
170
|
+
// Slideover triggers — wired from [slideover] shortcode
|
|
171
|
+
$pageBody.find('.dm-so-trigger').each(function () {
|
|
172
|
+
this.addEventListener('click', () => {
|
|
173
|
+
const targetId = this.dataset.soTarget;
|
|
174
|
+
const contentEl = document.getElementById(targetId);
|
|
175
|
+
if (!contentEl) return;
|
|
176
|
+
const so = E.slideover({
|
|
177
|
+
title: contentEl.dataset.soTitle || '',
|
|
178
|
+
size: contentEl.dataset.soSize || 'md',
|
|
179
|
+
position: contentEl.dataset.soPosition || 'right'
|
|
180
|
+
});
|
|
181
|
+
contentEl.style.display = '';
|
|
182
|
+
so.setContent(contentEl);
|
|
183
|
+
so.open();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// DConfig — merge window.__CMS_DCONFIG__ (editor section) with inline [dconfig] shortcodes
|
|
189
|
+
// Inline shortcodes win on selector conflict (assigned last via Object.assign)
|
|
190
|
+
if (typeof $.setup === 'function') {
|
|
191
|
+
const merged = Object.assign({}, window.__CMS_DCONFIG__ || {});
|
|
192
|
+
document.querySelectorAll('.dm-page-config[data-config]').forEach(el => {
|
|
193
|
+
try {
|
|
194
|
+
const decoded = atob(el.dataset.config);
|
|
195
|
+
const parsed = JSON.parse(decoded);
|
|
196
|
+
Object.assign(merged, parsed);
|
|
197
|
+
} catch { /* skip malformed blocks */
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
if (Object.keys(merged).length) {
|
|
201
|
+
$.setup(merged);
|
|
202
|
+
}
|
|
108
203
|
}
|
|
109
204
|
});
|