domma-cms 1.6.60 → 1.7.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/CHANGELOG.md +4344 -0
- package/README.md +1 -1
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +2 -2
- package/admin/js/lib/page-editor-reference.js +5 -5
- package/admin/js/lib/shortcode-tags.js +1 -1
- package/admin/js/templates/docs/usage-shortcodes.html +39 -0
- package/docs/DommaFeatures.md +3005 -0
- package/docs/MultiTenant.md +176 -0
- package/docs/api-reference.md +1667 -0
- package/docs/cache.md +96 -0
- package/docs/configuration.md +1811 -0
- package/docs/editions-and-licences.md +190 -0
- package/docs/getting-started.md +201 -0
- package/docs/guides/form-triggers.md +260 -0
- package/docs/guides/mongodb-storage-adapter.md +282 -0
- package/docs/guides/multi-page-forms.md +121 -0
- package/docs/images/scribe-composer.png +0 -0
- package/docs/markdown-shortcodes.md +1738 -0
- package/docs/pentest-report-2026-04-03.md +181 -0
- package/docs/plans/2026-02-28-admin-topbar-login-forms.md +349 -0
- package/docs/plans/2026-02-28-domma-ui-polish.md +286 -0
- package/docs/plans/2026-02-28-reset-seed-fresh-commands.md +520 -0
- package/docs/plans/2026-03-02-cookie-consent-design.md +86 -0
- package/docs/plans/2026-03-02-cookie-consent-plugin.md +453 -0
- package/docs/plans/2026-03-02-tooltip-sweep-and-back-to-top-plugin-design.md +126 -0
- package/docs/plans/2026-03-02-tooltip-sweep-back-to-top.md +427 -0
- package/docs/plans/2026-03-05-cookie-consent-btt-core.md +115 -0
- package/docs/plans/2026-03-05-custom-css-migrate-to-core-design.md +35 -0
- package/docs/plans/2026-03-05-custom-css-migrate-to-core.md +352 -0
- package/docs/plans/2026-03-05-scribe-script-shortcode.md +373 -0
- package/docs/plans/2026-03-05-user-types-design.md +139 -0
- package/docs/plans/2026-03-05-user-types-implementation.md +1035 -0
- package/docs/plans/2026-03-09-page-editor-view-cta-inserts-design.md +78 -0
- package/docs/plans/2026-03-09-page-editor-view-cta-inserts.md +388 -0
- package/docs/plans/2026-03-09-pro-documentation-design.md +80 -0
- package/docs/plans/2026-03-09-pro-documentation.md +1017 -0
- package/docs/plans/2026-06-10-core-project-design.md +240 -0
- package/docs/plugin-development.md +827 -0
- package/docs/recruitment-recipe.md +501 -0
- package/docs/scaffolding.md +312 -0
- package/docs/screenshots/calculator-floating.png +0 -0
- package/docs/screenshots/calculator-standard.png +0 -0
- package/docs/screenshots/email-settings.png +0 -0
- package/docs/screenshots/messages-count.png +0 -0
- package/docs/structure/user-types.md +107 -0
- package/docs/superpowers/plans/2026-05-25-admin-sidebar-via-menus.md +1112 -0
- package/docs/superpowers/plans/2026-06-08-navbar-mobile-drawer-and-menu-enrichment.md +1161 -0
- package/docs/superpowers/plans/2026-06-25-domma-docs-project.md +695 -0
- package/docs/superpowers/plans/2026-06-26-surveys-HANDOFF.md +68 -0
- package/docs/superpowers/plans/2026-06-26-surveys-pro-plugin.md +1902 -0
- package/docs/superpowers/plans/2026-08-19-collection-context-sort-export.md +1577 -0
- package/docs/superpowers/plans/2026-10-09-content-api.md +1192 -0
- package/docs/superpowers/specs/2026-03-12-form-followup-actions-design.md +152 -0
- package/docs/superpowers/specs/2026-03-12-page-editor-form-insert-and-per-page-theme-design.md +115 -0
- package/docs/superpowers/specs/2026-03-17-nav-hidden-items-design.md +85 -0
- package/docs/superpowers/specs/2026-03-19-accordion-insert-design.md +175 -0
- package/docs/superpowers/specs/2026-03-25-static-block-shortcode-design.md +153 -0
- package/docs/superpowers/specs/2026-03-25-text-shortcode-design.md +168 -0
- package/docs/superpowers/specs/2026-04-05-card-builder-design.md +315 -0
- package/docs/superpowers/specs/2026-04-06-insert-builders-design.md +278 -0
- package/docs/superpowers/specs/2026-04-06-layouts-design.md +226 -0
- package/docs/superpowers/specs/2026-04-07-blog-plugin-design.md +300 -0
- package/docs/superpowers/specs/2026-04-07-calendar-plugin-design.md +538 -0
- package/docs/superpowers/specs/2026-04-17-components-area-design.md +368 -0
- package/docs/superpowers/specs/2026-05-02-effects-slideover-overhaul-design.md +193 -0
- package/docs/superpowers/specs/2026-05-06-dashboard-rebuild-design.md +257 -0
- package/docs/superpowers/specs/2026-05-24-menus-design.md +466 -0
- package/docs/superpowers/specs/2026-05-25-admin-sidebar-via-menus-design.md +358 -0
- package/docs/superpowers/specs/2026-05-25-projects-design.md +380 -0
- package/docs/superpowers/specs/2026-06-08-navbar-mobile-drawer-and-menu-enrichment-design.md +292 -0
- package/docs/superpowers/specs/2026-06-08-sidebar-tools-plugins-folders-design.md +59 -0
- package/docs/superpowers/specs/2026-06-25-domma-docs-project-design.md +192 -0
- package/docs/superpowers/specs/2026-06-26-domma-charts-upstream-proposal.md +44 -0
- package/docs/superpowers/specs/2026-06-26-manager-scheduler-upstream-proposal.md +46 -0
- package/docs/superpowers/specs/2026-06-26-surveys-pro-plugin-design.md +206 -0
- package/docs/superpowers/specs/2026-07-02-shortcode-context-menu-design.md +96 -0
- package/docs/superpowers/specs/2026-08-19-collection-context-sort-export-design.md +330 -0
- package/docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md +1150 -0
- package/docs/superpowers/specs/2026-10-09-content-api-design.md +325 -0
- package/docs/superpowers/specs/plans/2026-03-12-form-followup-actions.md +624 -0
- package/docs/superpowers/specs/plans/2026-03-12-page-editor-form-insert-and-per-page-theme.md +125 -0
- package/docs/superpowers/specs/plans/2026-03-17-nav-hidden-items.md +578 -0
- package/docs/superpowers/specs/plans/2026-03-19-accordion-insert.md +635 -0
- package/docs/superpowers/specs/plans/2026-03-25-text-shortcode.md +545 -0
- package/docs/superpowers/specs/plans/2026-04-05-card-builder.md +1970 -0
- package/docs/superpowers/specs/plans/2026-04-06-insert-builders.md +1049 -0
- package/docs/superpowers/specs/plans/2026-04-06-layouts.md +1015 -0
- package/docs/superpowers/specs/plans/2026-04-07-calendar-plugin.md +1526 -0
- package/docs/superpowers/specs/plans/2026-04-08-plugin-monetisation-split.md +242 -0
- package/docs/superpowers/specs/plans/2026-04-17-components-area.md +2038 -0
- package/docs/superpowers/specs/plans/2026-05-02-effects-slideover-overhaul.md +2335 -0
- package/docs/superpowers/specs/plans/2026-05-06-dashboard-rebuild.md +1999 -0
- package/docs/superpowers/specs/plans/2026-05-17-cache-system.md +1436 -0
- package/docs/superpowers/specs/plans/2026-05-24-menus.md +3096 -0
- package/docs/superpowers/specs/plans/2026-05-25-projects.md +2869 -0
- package/docs/superpowers/specs/plans/2026-09-22-marketplace-step2-consolidation.md +505 -0
- package/docs/superpowers/specs/plans/2026-09-22-marketplace-step3-catalogue-and-key.md +154 -0
- package/docs/superpowers/specs/plans/2026-09-22-tools-promotion.md +260 -0
- package/docs/theming.md +320 -0
- package/package.json +2 -2
- package/public/js/celebrations/core/canvas.js +1 -1
- package/public/js/celebrations/index.js +1 -1
- package/public/js/celebrations/licenses/ABeeZee-Regular-OFL.txt +93 -0
- package/public/js/celebrations/licenses/Allura-Regular-OFL.txt +93 -0
- package/public/js/celebrations/message-attrs.mjs +1 -0
- package/public/js/celebrations/themes/anniversary.js +1 -0
- package/public/js/celebrations/themes/autumn.js +1 -0
- package/public/js/celebrations/themes/birthday.js +1 -0
- package/public/js/celebrations/themes/butterfly-flight.js +1 -0
- package/public/js/celebrations/themes/calendar.js +1 -0
- package/public/js/celebrations/themes/chinese-new-year-dates.js +1 -0
- package/public/js/celebrations/themes/chinese-new-year.js +1 -0
- package/public/js/celebrations/themes/christmas.js +1 -1
- package/public/js/celebrations/themes/easter.js +1 -0
- package/public/js/celebrations/themes/garden-drawing.js +1 -0
- package/public/js/celebrations/themes/garden.js +1 -0
- package/public/js/celebrations/themes/graduation.js +1 -0
- package/public/js/celebrations/themes/greeting.js +1 -0
- package/public/js/celebrations/themes/make-it-rain.js +1 -0
- package/public/js/celebrations/themes/money-rain.js +1 -0
- package/public/js/celebrations/themes/new-year.js +1 -0
- package/public/js/celebrations/themes/occasion-drawing.js +1 -0
- package/public/js/celebrations/themes/occasions.js +1 -0
- package/public/js/celebrations/themes/rain-control.js +1 -0
- package/public/js/celebrations/themes/sparkler-font-data.js +1 -0
- package/public/js/celebrations/themes/sparkler-font.js +1 -0
- package/public/js/celebrations/themes/sparkler-writing.js +1 -0
- package/public/js/celebrations/themes/summer-garden.js +1 -0
- package/public/js/celebrations/themes/winter.js +1 -0
- package/public/js/celebrations/trait-data.mjs +1 -1
- package/public/js/effects.js +1 -1
- package/scripts/build.js +4 -0
- package/server/services/markdown.js +5 -2
- package/server/templates/page.html +1 -1
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
# Cookie Consent Plugin
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Build a CMS plugin that injects Domma's built-in cookie consent banner into every public page, with all
|
|
6
|
+
wording, appearance, and category settings configurable from the admin panel.
|
|
7
|
+
|
|
8
|
+
**Architecture:** Follows the exact `back-to-top` and `contact-form` plugin structure. Server exposes `GET /settings` (
|
|
9
|
+
public, called by the IIFE) and `PUT /settings` (admin-auth). The public `inject-body.html` IIFE fetches settings and
|
|
10
|
+
calls `Domma.components.cookieConsent(cfg)` - Domma is already loaded on all public pages before `{{bodyEndInject}}` is
|
|
11
|
+
injected, so no extra dependencies are needed.
|
|
12
|
+
|
|
13
|
+
**Tech Stack:** Fastify 5 (ESM), Domma SPA (admin view), `Domma.components.cookieConsent()` (public banner).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Task 1: Create plugin.json
|
|
18
|
+
|
|
19
|
+
**File:** `plugins/cookie-consent/plugin.json`
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"name": "cookie-consent",
|
|
24
|
+
"displayName": "Cookie Consent",
|
|
25
|
+
"version": "1.0.0",
|
|
26
|
+
"description": "Injects a configurable GDPR cookie consent banner into every public page.",
|
|
27
|
+
"author": "Darryl Waterhouse",
|
|
28
|
+
"date": "2026-03-02",
|
|
29
|
+
"icon": "shield",
|
|
30
|
+
"admin": {
|
|
31
|
+
"sidebar": [
|
|
32
|
+
{ "id": "cookie-consent", "text": "Cookie Consent", "icon": "shield", "url": "#/plugins/cookie-consent", "section": "#/plugins/cookie-consent" }
|
|
33
|
+
],
|
|
34
|
+
"routes": [
|
|
35
|
+
{ "path": "/plugins/cookie-consent", "view": "plugin-cookie-consent-settings", "title": "Cookie Consent - Domma CMS" }
|
|
36
|
+
],
|
|
37
|
+
"views": {
|
|
38
|
+
"plugin-cookie-consent-settings": {
|
|
39
|
+
"entry": "cookie-consent/admin/views/cookie-consent-settings.js",
|
|
40
|
+
"exportName": "cookieConsentSettingsView"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"inject": {
|
|
45
|
+
"bodyEnd": "public/inject-body.html"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Task 2: Create config.js
|
|
53
|
+
|
|
54
|
+
**File:** `plugins/cookie-consent/config.js`
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
/**
|
|
58
|
+
* Cookie Consent Plugin - Default Configuration
|
|
59
|
+
*/
|
|
60
|
+
export default {
|
|
61
|
+
// Wording
|
|
62
|
+
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.',
|
|
63
|
+
acceptAllText: 'Accept All',
|
|
64
|
+
rejectAllText: 'Reject All',
|
|
65
|
+
customizeText: 'Customize',
|
|
66
|
+
savePreferencesText: 'Save Preferences',
|
|
67
|
+
|
|
68
|
+
// Policy links (empty cookiePolicyUrl hides the cookie policy link)
|
|
69
|
+
privacyPolicyText: 'Privacy Policy',
|
|
70
|
+
privacyPolicyUrl: '/privacy-policy',
|
|
71
|
+
cookiePolicyText: 'Cookie Policy',
|
|
72
|
+
cookiePolicyUrl: '',
|
|
73
|
+
|
|
74
|
+
// Appearance
|
|
75
|
+
position: 'bottom',
|
|
76
|
+
layout: 'bar',
|
|
77
|
+
theme: 'dark',
|
|
78
|
+
|
|
79
|
+
// Categories (Necessary is always shown and always required)
|
|
80
|
+
showFunctional: true,
|
|
81
|
+
showAnalytics: true,
|
|
82
|
+
showMarketing: true,
|
|
83
|
+
|
|
84
|
+
// Consent version - increment to re-prompt users who already accepted
|
|
85
|
+
consentVersion: '1.0'
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Task 3: Create plugin.js (server)
|
|
92
|
+
|
|
93
|
+
**File:** `plugins/cookie-consent/plugin.js`
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
/**
|
|
97
|
+
* Cookie Consent Plugin - Server
|
|
98
|
+
*
|
|
99
|
+
* Endpoints (prefix: /api/plugins/cookie-consent):
|
|
100
|
+
* GET /settings - public (called by inject-body IIFE)
|
|
101
|
+
* PUT /settings - admin-auth - saves user overrides
|
|
102
|
+
*/
|
|
103
|
+
import { getPluginSettings, savePluginState } from '../../server/services/plugins.js';
|
|
104
|
+
|
|
105
|
+
export default async function cookieConsentPlugin(fastify, options) {
|
|
106
|
+
const { authenticate, requireAdmin } = options.auth;
|
|
107
|
+
|
|
108
|
+
// GET /settings - no auth, public read
|
|
109
|
+
fastify.get('/settings', async () => {
|
|
110
|
+
return getPluginSettings('cookie-consent');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// PUT /settings - admin only
|
|
114
|
+
fastify.put('/settings', { preHandler: [authenticate, requireAdmin] }, async (request) => {
|
|
115
|
+
const body = request.body || {};
|
|
116
|
+
savePluginState('cookie-consent', { settings: body });
|
|
117
|
+
return { ok: true };
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Task 4: Create admin HTML template
|
|
125
|
+
|
|
126
|
+
**File:** `plugins/cookie-consent/admin/templates/cookie-consent-settings.html`
|
|
127
|
+
|
|
128
|
+
```html
|
|
129
|
+
<div class="view-header">
|
|
130
|
+
<h1><span data-icon="shield"></span> Cookie Consent</h1>
|
|
131
|
+
<div>
|
|
132
|
+
<button id="save-settings-btn" class="btn btn-primary">
|
|
133
|
+
<span data-icon="save"></span> Save
|
|
134
|
+
</button>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div class="card mb-4">
|
|
139
|
+
<div class="card-header"><h2>Banner Wording</h2></div>
|
|
140
|
+
<div class="card-body">
|
|
141
|
+
<div class="row mb-3">
|
|
142
|
+
<div class="col">
|
|
143
|
+
<label class="form-label">Message</label>
|
|
144
|
+
<textarea id="field-message" class="form-input" rows="3"></textarea>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="row mb-3">
|
|
148
|
+
<div class="col-6">
|
|
149
|
+
<label class="form-label">Accept All button</label>
|
|
150
|
+
<input id="field-accept-all-text" type="text" class="form-input">
|
|
151
|
+
</div>
|
|
152
|
+
<div class="col-6">
|
|
153
|
+
<label class="form-label">Reject All button</label>
|
|
154
|
+
<input id="field-reject-all-text" type="text" class="form-input">
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="row mb-3">
|
|
158
|
+
<div class="col-6">
|
|
159
|
+
<label class="form-label">Customize button</label>
|
|
160
|
+
<input id="field-customize-text" type="text" class="form-input">
|
|
161
|
+
</div>
|
|
162
|
+
<div class="col-6">
|
|
163
|
+
<label class="form-label">Save Preferences button</label>
|
|
164
|
+
<input id="field-save-preferences-text" type="text" class="form-input">
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
<div class="row mb-3">
|
|
168
|
+
<div class="col-6">
|
|
169
|
+
<label class="form-label">Privacy Policy link text</label>
|
|
170
|
+
<input id="field-privacy-policy-text" type="text" class="form-input">
|
|
171
|
+
</div>
|
|
172
|
+
<div class="col-6">
|
|
173
|
+
<label class="form-label">Privacy Policy URL</label>
|
|
174
|
+
<input id="field-privacy-policy-url" type="text" class="form-input" placeholder="/privacy-policy">
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="row">
|
|
178
|
+
<div class="col-6">
|
|
179
|
+
<label class="form-label">Cookie Policy link text</label>
|
|
180
|
+
<input id="field-cookie-policy-text" type="text" class="form-input">
|
|
181
|
+
</div>
|
|
182
|
+
<div class="col-6">
|
|
183
|
+
<label class="form-label">Cookie Policy URL <span class="text-muted">(leave empty to hide)</span></label>
|
|
184
|
+
<input id="field-cookie-policy-url" type="text" class="form-input" placeholder="/cookie-policy">
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<div class="card mb-4">
|
|
191
|
+
<div class="card-header"><h2>Appearance & Categories</h2></div>
|
|
192
|
+
<div class="card-body">
|
|
193
|
+
<div class="row mb-3">
|
|
194
|
+
<div class="col-4">
|
|
195
|
+
<label class="form-label">Position</label>
|
|
196
|
+
<select id="field-position" class="form-select">
|
|
197
|
+
<option value="bottom">Bottom</option>
|
|
198
|
+
<option value="top">Top</option>
|
|
199
|
+
</select>
|
|
200
|
+
</div>
|
|
201
|
+
<div class="col-4">
|
|
202
|
+
<label class="form-label">Layout</label>
|
|
203
|
+
<select id="field-layout" class="form-select">
|
|
204
|
+
<option value="bar">Bar</option>
|
|
205
|
+
<option value="modal">Modal</option>
|
|
206
|
+
</select>
|
|
207
|
+
</div>
|
|
208
|
+
<div class="col-4">
|
|
209
|
+
<label class="form-label">Theme</label>
|
|
210
|
+
<select id="field-theme" class="form-select">
|
|
211
|
+
<option value="dark">Dark</option>
|
|
212
|
+
<option value="light">Light</option>
|
|
213
|
+
</select>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
<div class="row mb-3">
|
|
217
|
+
<div class="col">
|
|
218
|
+
<label class="form-label">Cookie categories shown in Customize panel</label>
|
|
219
|
+
<span class="form-hint">Necessary cookies are always shown and always required.</span>
|
|
220
|
+
<div style="margin-top:.75rem;display:flex;flex-direction:column;gap:.5rem;">
|
|
221
|
+
<label class="form-check-label">
|
|
222
|
+
<input id="field-show-functional" type="checkbox"> Functional Cookies
|
|
223
|
+
</label>
|
|
224
|
+
<label class="form-check-label">
|
|
225
|
+
<input id="field-show-analytics" type="checkbox"> Analytics Cookies
|
|
226
|
+
</label>
|
|
227
|
+
<label class="form-check-label">
|
|
228
|
+
<input id="field-show-marketing" type="checkbox"> Marketing Cookies
|
|
229
|
+
</label>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
<div class="row">
|
|
234
|
+
<div class="col-4">
|
|
235
|
+
<label class="form-label">Consent version</label>
|
|
236
|
+
<input id="field-consent-version" type="text" class="form-input" placeholder="1.0">
|
|
237
|
+
<span class="form-hint">Increment to re-prompt users who already accepted.</span>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Task 5: Create admin settings view
|
|
247
|
+
|
|
248
|
+
**File:** `plugins/cookie-consent/admin/views/cookie-consent-settings.js`
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
/**
|
|
252
|
+
* Cookie Consent Plugin - Admin Settings View
|
|
253
|
+
*/
|
|
254
|
+
export const cookieConsentSettingsView = {
|
|
255
|
+
templateUrl: '/plugins/cookie-consent/admin/templates/cookie-consent-settings.html',
|
|
256
|
+
|
|
257
|
+
async onMount($container) {
|
|
258
|
+
let settings = {};
|
|
259
|
+
try {
|
|
260
|
+
const res = await fetch('/api/plugins/cookie-consent/settings', {
|
|
261
|
+
headers: { 'Authorization': 'Bearer ' + (S.get('auth_token') || '') }
|
|
262
|
+
});
|
|
263
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
264
|
+
settings = await res.json();
|
|
265
|
+
} catch {
|
|
266
|
+
E.toast('Could not load settings.', { type: 'error' });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Wording
|
|
270
|
+
$container.find('#field-message').val(settings.message || '');
|
|
271
|
+
$container.find('#field-accept-all-text').val(settings.acceptAllText || '');
|
|
272
|
+
$container.find('#field-reject-all-text').val(settings.rejectAllText || '');
|
|
273
|
+
$container.find('#field-customize-text').val(settings.customizeText || '');
|
|
274
|
+
$container.find('#field-save-preferences-text').val(settings.savePreferencesText || '');
|
|
275
|
+
$container.find('#field-privacy-policy-text').val(settings.privacyPolicyText || '');
|
|
276
|
+
$container.find('#field-privacy-policy-url').val(settings.privacyPolicyUrl || '');
|
|
277
|
+
$container.find('#field-cookie-policy-text').val(settings.cookiePolicyText || '');
|
|
278
|
+
$container.find('#field-cookie-policy-url').val(settings.cookiePolicyUrl || '');
|
|
279
|
+
|
|
280
|
+
// Appearance
|
|
281
|
+
$container.find('#field-position').val(settings.position || 'bottom');
|
|
282
|
+
$container.find('#field-layout').val(settings.layout || 'bar');
|
|
283
|
+
$container.find('#field-theme').val(settings.theme || 'dark');
|
|
284
|
+
|
|
285
|
+
// Categories
|
|
286
|
+
$container.find('#field-show-functional').prop('checked', settings.showFunctional !== false);
|
|
287
|
+
$container.find('#field-show-analytics').prop('checked', settings.showAnalytics !== false);
|
|
288
|
+
$container.find('#field-show-marketing').prop('checked', settings.showMarketing !== false);
|
|
289
|
+
|
|
290
|
+
// Version
|
|
291
|
+
$container.find('#field-consent-version').val(settings.consentVersion || '1.0');
|
|
292
|
+
|
|
293
|
+
$container.find('#save-settings-btn').off('click').on('click', async () => {
|
|
294
|
+
const data = {
|
|
295
|
+
message: $container.find('#field-message').val().trim(),
|
|
296
|
+
acceptAllText: $container.find('#field-accept-all-text').val().trim(),
|
|
297
|
+
rejectAllText: $container.find('#field-reject-all-text').val().trim(),
|
|
298
|
+
customizeText: $container.find('#field-customize-text').val().trim(),
|
|
299
|
+
savePreferencesText: $container.find('#field-save-preferences-text').val().trim(),
|
|
300
|
+
privacyPolicyText: $container.find('#field-privacy-policy-text').val().trim(),
|
|
301
|
+
privacyPolicyUrl: $container.find('#field-privacy-policy-url').val().trim(),
|
|
302
|
+
cookiePolicyText: $container.find('#field-cookie-policy-text').val().trim(),
|
|
303
|
+
cookiePolicyUrl: $container.find('#field-cookie-policy-url').val().trim(),
|
|
304
|
+
position: $container.find('#field-position').val(),
|
|
305
|
+
layout: $container.find('#field-layout').val(),
|
|
306
|
+
theme: $container.find('#field-theme').val(),
|
|
307
|
+
showFunctional: $container.find('#field-show-functional').prop('checked'),
|
|
308
|
+
showAnalytics: $container.find('#field-show-analytics').prop('checked'),
|
|
309
|
+
showMarketing: $container.find('#field-show-marketing').prop('checked'),
|
|
310
|
+
consentVersion: $container.find('#field-consent-version').val().trim() || '1.0'
|
|
311
|
+
};
|
|
312
|
+
try {
|
|
313
|
+
const res = await fetch('/api/plugins/cookie-consent/settings', {
|
|
314
|
+
method: 'PUT',
|
|
315
|
+
headers: {
|
|
316
|
+
'Content-Type': 'application/json',
|
|
317
|
+
'Authorization': 'Bearer ' + (S.get('auth_token') || '')
|
|
318
|
+
},
|
|
319
|
+
body: JSON.stringify(data)
|
|
320
|
+
});
|
|
321
|
+
if (!res.ok) throw new Error('Save failed');
|
|
322
|
+
E.toast('Settings saved.', { type: 'success' });
|
|
323
|
+
} catch {
|
|
324
|
+
E.toast('Failed to save settings.', { type: 'error' });
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
Domma.icons.scan();
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Task 6: Create public inject-body.html
|
|
336
|
+
|
|
337
|
+
**File:** `plugins/cookie-consent/public/inject-body.html`
|
|
338
|
+
|
|
339
|
+
`Domma.components.cookieConsent()` is available because `domma.min.js` is loaded and `Domma.init()` runs before
|
|
340
|
+
`{{bodyEndInject}}` in `server/templates/page.html`.
|
|
341
|
+
|
|
342
|
+
The IIFE maps the flat config to the Domma component's `categories` object - only including categories that are enabled
|
|
343
|
+
in settings. Necessary is always included.
|
|
344
|
+
|
|
345
|
+
```html
|
|
346
|
+
<script>
|
|
347
|
+
(function () {
|
|
348
|
+
'use strict';
|
|
349
|
+
|
|
350
|
+
if (!window.Domma || !window.Domma.components || !window.Domma.components.cookieConsent) return;
|
|
351
|
+
|
|
352
|
+
fetch('/api/plugins/cookie-consent/settings')
|
|
353
|
+
.then(function (r) { return r.json(); })
|
|
354
|
+
.then(function (s) {
|
|
355
|
+
var categories = {
|
|
356
|
+
necessary: {
|
|
357
|
+
label: 'Necessary Cookies',
|
|
358
|
+
description: 'These cookies are essential for the website to function properly.',
|
|
359
|
+
required: true
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
if (s.showFunctional !== false) {
|
|
364
|
+
categories.functional = {
|
|
365
|
+
label: 'Functional Cookies',
|
|
366
|
+
description: 'These cookies enable personalised features and functionality.',
|
|
367
|
+
required: false
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (s.showAnalytics !== false) {
|
|
372
|
+
categories.analytics = {
|
|
373
|
+
label: 'Analytics Cookies',
|
|
374
|
+
description: 'These cookies help us understand how visitors interact with our website.',
|
|
375
|
+
required: false
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (s.showMarketing !== false) {
|
|
380
|
+
categories.marketing = {
|
|
381
|
+
label: 'Marketing Cookies',
|
|
382
|
+
description: 'These cookies are used to deliver relevant ads and marketing campaigns.',
|
|
383
|
+
required: false
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
var cfg = {
|
|
388
|
+
message: s.message,
|
|
389
|
+
acceptAllText: s.acceptAllText,
|
|
390
|
+
rejectAllText: s.rejectAllText,
|
|
391
|
+
customizeText: s.customizeText,
|
|
392
|
+
savePreferencesText: s.savePreferencesText,
|
|
393
|
+
privacyPolicyText: s.privacyPolicyText,
|
|
394
|
+
privacyPolicyUrl: s.privacyPolicyUrl || null,
|
|
395
|
+
cookiePolicyText: s.cookiePolicyText,
|
|
396
|
+
cookiePolicyUrl: s.cookiePolicyUrl || null,
|
|
397
|
+
position: s.position || 'bottom',
|
|
398
|
+
layout: s.layout || 'bar',
|
|
399
|
+
theme: s.theme || 'dark',
|
|
400
|
+
categories: categories,
|
|
401
|
+
consentVersion: s.consentVersion || '1.0',
|
|
402
|
+
autoShow: true,
|
|
403
|
+
animation: true
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
Domma.components.cookieConsent(cfg);
|
|
407
|
+
})
|
|
408
|
+
.catch(function () {
|
|
409
|
+
// Settings fetch failed - don't show banner
|
|
410
|
+
});
|
|
411
|
+
})();
|
|
412
|
+
</script>
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Task 7: Enable plugin in config/plugins.json
|
|
418
|
+
|
|
419
|
+
**Modify:** `config/plugins.json`
|
|
420
|
+
|
|
421
|
+
Add the `cookie-consent` entry:
|
|
422
|
+
|
|
423
|
+
```json
|
|
424
|
+
{
|
|
425
|
+
"example-analytics": { "enabled": true, "settings": {} },
|
|
426
|
+
"contact-form": { "enabled": true, "settings": {} },
|
|
427
|
+
"back-to-top": { "enabled": true, "settings": {} },
|
|
428
|
+
"cookie-consent": { "enabled": true, "settings": {} }
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## Verification
|
|
435
|
+
|
|
436
|
+
Restart the server (`node server/server.js`) and check:
|
|
437
|
+
|
|
438
|
+
1. **Server log** - `[plugins] Loaded 4 plugins: back-to-top, contact-form, cookie-consent, example-analytics`
|
|
439
|
+
|
|
440
|
+
2. **Settings endpoint** - `curl http://localhost:4096/api/plugins/cookie-consent/settings` returns the full defaults
|
|
441
|
+
JSON.
|
|
442
|
+
|
|
443
|
+
3. **Public injection** - `curl http://localhost:4096/ | grep cookie-consent` shows the IIFE script in the page HTML.
|
|
444
|
+
|
|
445
|
+
4. **Banner appears** - visit the public site in a browser (clear localStorage first). The cookie consent bar should
|
|
446
|
+
appear at the bottom of the page. Clicking "Customize" should open the category panel with Necessary, Functional,
|
|
447
|
+
Analytics, and Marketing options.
|
|
448
|
+
|
|
449
|
+
5. **Admin settings** - navigate to `#/plugins/cookie-consent` in the admin. The settings form should load with all
|
|
450
|
+
defaults populated. Change the message, save, reload the public site - the new message should appear.
|
|
451
|
+
|
|
452
|
+
6. **PUT protected** - `curl -X PUT http://localhost:4096/api/plugins/cookie-consent/settings` without a token returns
|
|
453
|
+
`401`.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Tooltip Sweep & Back-to-Top Plugin
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-03-02
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Tooltip Sweep
|
|
8
|
+
|
|
9
|
+
### Problem
|
|
10
|
+
|
|
11
|
+
Icon-only interactive buttons across the admin have no accessible labels or hover hints. The sidebar and topbar already
|
|
12
|
+
have tooltips; the rest of the UI does not.
|
|
13
|
+
|
|
14
|
+
### Scope
|
|
15
|
+
|
|
16
|
+
Add `data-tooltip` to icon-only buttons in:
|
|
17
|
+
|
|
18
|
+
- **Pages table** - Edit, Delete row actions (rendered by `pages.js` via `T.create`)
|
|
19
|
+
- **User editor** - Save/Cancel header buttons (icon + text, but Save has an icon)
|
|
20
|
+
- **Navigation view** - Add/Delete/move row action buttons
|
|
21
|
+
- **Page editor header** - Save/Cancel buttons already have text; no change needed
|
|
22
|
+
- **Plugin cards** - Toggle enable/disable buttons (if icon-only)
|
|
23
|
+
|
|
24
|
+
Out of scope: buttons that already have visible text labels.
|
|
25
|
+
|
|
26
|
+
### Implementation
|
|
27
|
+
|
|
28
|
+
Each view that renders icon-only actions via `T.create` columns or dynamic HTML passes `data-tooltip="..."` on the
|
|
29
|
+
element, then calls `E.tooltip('[data-tooltip]', { placement: 'top' })` at the end of the render function - matching the
|
|
30
|
+
pattern already used in `media.js`.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 2. Back-to-Top Plugin
|
|
35
|
+
|
|
36
|
+
### Overview
|
|
37
|
+
|
|
38
|
+
A lightweight CMS plugin that injects a scroll-to-top button into every public page. Configurable from the admin panel.
|
|
39
|
+
Follows the established plugin pattern (contact-form as reference).
|
|
40
|
+
|
|
41
|
+
### File Structure
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
plugins/back-to-top/
|
|
45
|
+
├── plugin.json - metadata, admin routes, inject references
|
|
46
|
+
├── plugin.js - Fastify plugin: GET/PUT /settings
|
|
47
|
+
├── config.js - default settings
|
|
48
|
+
├── admin/
|
|
49
|
+
│ └── views/
|
|
50
|
+
│ └── back-to-top-settings.js - admin SPA view
|
|
51
|
+
│ └── templates/
|
|
52
|
+
│ └── back-to-top-settings.html - admin template
|
|
53
|
+
└── public/
|
|
54
|
+
└── inject-body.html - IIFE injected at </body> on every public page
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Config Defaults (`config.js`)
|
|
58
|
+
|
|
59
|
+
| Key | Default | Description |
|
|
60
|
+
|-------------------|------------------|-----------------------------------------------|
|
|
61
|
+
| `scrollThreshold` | `300` | px scrolled before button appears |
|
|
62
|
+
| `position` | `'bottom-right'` | `'bottom-right'` or `'bottom-left'` |
|
|
63
|
+
| `label` | `''` | Optional text next to icon; empty = icon only |
|
|
64
|
+
| `smooth` | `true` | Smooth scroll vs instant |
|
|
65
|
+
| `offset` | `32` | px from bottom/side edge |
|
|
66
|
+
|
|
67
|
+
### Server (`plugin.js`)
|
|
68
|
+
|
|
69
|
+
Two endpoints under `/api/plugins/back-to-top/`:
|
|
70
|
+
|
|
71
|
+
- `GET /settings` - admin-auth - returns merged settings
|
|
72
|
+
- `PUT /settings` - admin-auth - saves user overrides via `savePluginState`
|
|
73
|
+
|
|
74
|
+
No public endpoints needed.
|
|
75
|
+
|
|
76
|
+
### Public Injection (`inject-body.html`)
|
|
77
|
+
|
|
78
|
+
Self-contained IIFE:
|
|
79
|
+
|
|
80
|
+
1. Fetches `/api/plugins/back-to-top/settings` (unauthenticated - settings are public read)
|
|
81
|
+
2. Creates a `<button id="back-to-top-btn">` with inline styles driven by config
|
|
82
|
+
3. Uses `IntersectionObserver` on a sentinel element (or `scroll` event) to show/hide
|
|
83
|
+
4. Clicking scrolls to top using `window.scrollTo` (smooth or instant per config)
|
|
84
|
+
5. Uses a Domma `arrow-up` icon (`data-icon` pattern won't work outside admin - renders a plain SVG inline)
|
|
85
|
+
|
|
86
|
+
The button is self-styled (no dependency on site.css) so it works regardless of theme.
|
|
87
|
+
|
|
88
|
+
### Admin Panel (`back-to-top-settings.js`)
|
|
89
|
+
|
|
90
|
+
Simple settings form matching the contact-form settings pattern:
|
|
91
|
+
|
|
92
|
+
- Number input: Scroll threshold (px)
|
|
93
|
+
- Select: Position (Bottom right / Bottom left)
|
|
94
|
+
- Text input: Button label (optional)
|
|
95
|
+
- Checkbox: Smooth scroll
|
|
96
|
+
- Number input: Edge offset (px)
|
|
97
|
+
- Save button with success toast
|
|
98
|
+
|
|
99
|
+
### plugin.json Admin Section
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
"admin": {
|
|
103
|
+
"sidebar": [{ "id": "back-to-top", "text": "Back to Top", "icon": "arrow-up", "url": "#/plugins/back-to-top" }],
|
|
104
|
+
"routes": [{ "path": "/plugins/back-to-top", "view": "plugin-back-to-top-settings", "title": "Back to Top - Domma CMS" }],
|
|
105
|
+
"views": {
|
|
106
|
+
"plugin-back-to-top-settings": {
|
|
107
|
+
"entry": "back-to-top/admin/views/back-to-top-settings.js",
|
|
108
|
+
"exportName": "backToTopSettingsView"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Note on Settings Endpoint Auth
|
|
115
|
+
|
|
116
|
+
The `GET /settings` endpoint is called from the public site (unauthenticated). It returns only non-sensitive display
|
|
117
|
+
config (threshold, position, label, smooth, offset) - no credentials. This is intentional and safe.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Decisions
|
|
122
|
+
|
|
123
|
+
- Plugin follows contact-form structure exactly (no new patterns)
|
|
124
|
+
- Public button is self-contained (no Domma dependency on public side)
|
|
125
|
+
- Settings fetched client-side (no server-side variable substitution needed)
|
|
126
|
+
- Tooltip sweep is targeted (icon-only buttons only, not all interactive elements)
|