domma-cms 1.6.59 → 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/api.js +1 -1
- package/admin/js/app.js +2 -2
- package/admin/js/lib/draft-owner.js +1 -0
- 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/admin/js/views/user-editor.js +1 -1
- 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/plugins/_lib/admin/mail/keyboard.js +49 -0
- package/plugins/_lib/admin/mail/reader-view.js +4 -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,427 @@
|
|
|
1
|
+
# Tooltip Sweep & Back-to-Top Plugin
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add `data-tooltip` to all remaining icon-only admin buttons, and build a complete `back-to-top` CMS plugin.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Tooltip sweep targets two files (`pages.js`, `navigation.js`) - replace `title=""` with
|
|
8
|
+
`data-tooltip=""` and call `E.tooltip()` after icons scan. Plugin follows the exact `contact-form` structure:
|
|
9
|
+
`plugin.json` + `plugin.js` + `config.js` + admin view/template + public `inject-body.html` IIFE.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Task 1: Tooltip sweep - pages.js
|
|
14
|
+
|
|
15
|
+
**Modify:** `admin/js/views/pages.js`
|
|
16
|
+
|
|
17
|
+
Two changes, both inside `renderTable()`:
|
|
18
|
+
|
|
19
|
+
**Step 1: Replace `title` with `data-tooltip` on the view link (line 26)**
|
|
20
|
+
|
|
21
|
+
Old:
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<a href="${row.urlPath}" target="_blank" class="btn btn-sm btn-ghost" title="View"><span data-icon="external-link"></span></a>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
New:
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<a href="${row.urlPath}" target="_blank" class="btn btn-sm btn-ghost" data-tooltip="View"><span data-icon="external-link"></span></a>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Step 2: Add `E.tooltip()` call after `Domma.icons.scan()` (line 33)**
|
|
34
|
+
|
|
35
|
+
Old:
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
Domma.icons.scan();
|
|
39
|
+
Domma.effects.reveal('.card', { animation: 'fade', duration: 350 });
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
New:
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
Domma.icons.scan();
|
|
46
|
+
E.tooltip('[data-tooltip]', { placement: 'top' });
|
|
47
|
+
Domma.effects.reveal('.card', { animation: 'fade', duration: 350 });
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Task 2: Tooltip sweep - navigation.js
|
|
53
|
+
|
|
54
|
+
**Modify:** `admin/js/views/navigation.js`
|
|
55
|
+
|
|
56
|
+
Two changes, both inside `renderItems()`:
|
|
57
|
+
|
|
58
|
+
**Step 1: Replace `title` with `data-tooltip` on the Remove button (line 90)**
|
|
59
|
+
|
|
60
|
+
Old:
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<button class="btn btn-sm btn-danger btn-remove-item" data-id="${item._id}" title="Remove"><span data-icon="trash"></span></button>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
New:
|
|
67
|
+
|
|
68
|
+
```html
|
|
69
|
+
<button class="btn btn-sm btn-danger btn-remove-item" data-id="${item._id}" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Step 2: Add `E.tooltip()` call after `Domma.icons.scan('#nav-items-list')` (line 95)**
|
|
73
|
+
|
|
74
|
+
Old:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
Domma.icons.scan('#nav-items-list');
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
New:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
Domma.icons.scan('#nav-items-list');
|
|
84
|
+
E.tooltip('[data-tooltip]', { placement: 'top' });
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Task 3: Create plugins/back-to-top/plugin.json
|
|
90
|
+
|
|
91
|
+
**New file:** `plugins/back-to-top/plugin.json`
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"name": "back-to-top",
|
|
96
|
+
"displayName": "Back to Top",
|
|
97
|
+
"version": "1.0.0",
|
|
98
|
+
"description": "Injects a configurable scroll-to-top button into every public page.",
|
|
99
|
+
"author": "Darryl Waterhouse",
|
|
100
|
+
"date": "2026-03-02",
|
|
101
|
+
"icon": "arrow-up",
|
|
102
|
+
"admin": {
|
|
103
|
+
"sidebar": [
|
|
104
|
+
{ "id": "back-to-top", "text": "Back to Top", "icon": "arrow-up", "url": "#/plugins/back-to-top", "section": "#/plugins/back-to-top" }
|
|
105
|
+
],
|
|
106
|
+
"routes": [
|
|
107
|
+
{ "path": "/plugins/back-to-top", "view": "plugin-back-to-top-settings", "title": "Back to Top - Domma CMS" }
|
|
108
|
+
],
|
|
109
|
+
"views": {
|
|
110
|
+
"plugin-back-to-top-settings": {
|
|
111
|
+
"entry": "back-to-top/admin/views/back-to-top-settings.js",
|
|
112
|
+
"exportName": "backToTopSettingsView"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"inject": {
|
|
117
|
+
"bodyEnd": "public/inject-body.html"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Task 4: Create plugins/back-to-top/config.js
|
|
125
|
+
|
|
126
|
+
**New file:** `plugins/back-to-top/config.js`
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
/**
|
|
130
|
+
* Back to Top Plugin - Default Configuration
|
|
131
|
+
*/
|
|
132
|
+
export default {
|
|
133
|
+
scrollThreshold: 300,
|
|
134
|
+
position: 'bottom-right',
|
|
135
|
+
label: '',
|
|
136
|
+
smooth: true,
|
|
137
|
+
offset: 32
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Task 5: Create plugins/back-to-top/plugin.js
|
|
144
|
+
|
|
145
|
+
**New file:** `plugins/back-to-top/plugin.js`
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
/**
|
|
149
|
+
* Back to Top Plugin - Server
|
|
150
|
+
*
|
|
151
|
+
* Endpoints (prefix: /api/plugins/back-to-top):
|
|
152
|
+
* GET /settings - public (called from public site IIFE)
|
|
153
|
+
* PUT /settings - admin-auth - saves user overrides
|
|
154
|
+
*/
|
|
155
|
+
import { getPluginSettings, savePluginState } from '../../server/services/plugins.js';
|
|
156
|
+
|
|
157
|
+
export default async function backToTopPlugin(fastify, options) {
|
|
158
|
+
const { authenticate, requireAdmin } = options.auth;
|
|
159
|
+
|
|
160
|
+
// GET /settings - no auth, public read (returns non-sensitive display config)
|
|
161
|
+
fastify.get('/settings', async () => {
|
|
162
|
+
return getPluginSettings('back-to-top');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// PUT /settings - admin only
|
|
166
|
+
fastify.put('/settings', { preHandler: [authenticate, requireAdmin] }, async (request) => {
|
|
167
|
+
const body = request.body || {};
|
|
168
|
+
savePluginState('back-to-top', { settings: body });
|
|
169
|
+
return { ok: true };
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Task 6: Create admin template
|
|
177
|
+
|
|
178
|
+
**New file:** `plugins/back-to-top/admin/templates/back-to-top-settings.html`
|
|
179
|
+
|
|
180
|
+
```html
|
|
181
|
+
<div class="view-header">
|
|
182
|
+
<h1><span data-icon="arrow-up"></span> Back to Top</h1>
|
|
183
|
+
<div>
|
|
184
|
+
<button id="save-settings-btn" class="btn btn-primary">
|
|
185
|
+
<span data-icon="save"></span> Save
|
|
186
|
+
</button>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<div class="card mb-4">
|
|
191
|
+
<div class="card-header"><h2>Appearance</h2></div>
|
|
192
|
+
<div class="card-body">
|
|
193
|
+
<div class="row mb-3">
|
|
194
|
+
<div class="col-6">
|
|
195
|
+
<label class="form-label">Position</label>
|
|
196
|
+
<select id="field-position" class="form-select">
|
|
197
|
+
<option value="bottom-right">Bottom right</option>
|
|
198
|
+
<option value="bottom-left">Bottom left</option>
|
|
199
|
+
</select>
|
|
200
|
+
</div>
|
|
201
|
+
<div class="col-6">
|
|
202
|
+
<label class="form-label">Edge offset (px)</label>
|
|
203
|
+
<input id="field-offset" type="number" class="form-input" min="0" max="200" placeholder="32">
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
<div class="row">
|
|
207
|
+
<div class="col">
|
|
208
|
+
<label class="form-label">Button label <span class="text-muted">(optional text beside icon)</span></label>
|
|
209
|
+
<input id="field-label" type="text" class="form-input" placeholder="Leave empty for icon only">
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<div class="card mb-4">
|
|
216
|
+
<div class="card-header"><h2>Behaviour</h2></div>
|
|
217
|
+
<div class="card-body">
|
|
218
|
+
<div class="row mb-3">
|
|
219
|
+
<div class="col-6">
|
|
220
|
+
<label class="form-label">Scroll threshold (px)</label>
|
|
221
|
+
<input id="field-threshold" type="number" class="form-input" min="0" max="9999" placeholder="300">
|
|
222
|
+
<span class="form-hint">Button appears after scrolling this far down.</span>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="row">
|
|
226
|
+
<div class="col">
|
|
227
|
+
<label class="form-check-label">
|
|
228
|
+
<input id="field-smooth" type="checkbox">
|
|
229
|
+
Smooth scroll
|
|
230
|
+
</label>
|
|
231
|
+
<span class="form-hint">Animates scroll to top instead of jumping instantly.</span>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Task 7: Create admin settings view
|
|
241
|
+
|
|
242
|
+
**New file:** `plugins/back-to-top/admin/views/back-to-top-settings.js`
|
|
243
|
+
|
|
244
|
+
```js
|
|
245
|
+
/**
|
|
246
|
+
* Back to Top Plugin - Admin Settings View
|
|
247
|
+
*/
|
|
248
|
+
export const backToTopSettingsView = {
|
|
249
|
+
templateUrl: '/plugins/back-to-top/admin/templates/back-to-top-settings.html',
|
|
250
|
+
|
|
251
|
+
async onMount($container) {
|
|
252
|
+
let settings = {};
|
|
253
|
+
try {
|
|
254
|
+
const res = await fetch('/api/plugins/back-to-top/settings', {
|
|
255
|
+
headers: { 'Authorization': 'Bearer ' + (S.get('auth_token') || '') }
|
|
256
|
+
});
|
|
257
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
258
|
+
settings = await res.json();
|
|
259
|
+
} catch {
|
|
260
|
+
E.toast('Could not load settings.', { type: 'error' });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
$container.find('#field-threshold').val(settings.scrollThreshold ?? 300);
|
|
264
|
+
$container.find('#field-position').val(settings.position || 'bottom-right');
|
|
265
|
+
$container.find('#field-label').val(settings.label || '');
|
|
266
|
+
$container.find('#field-smooth').prop('checked', settings.smooth !== false);
|
|
267
|
+
$container.find('#field-offset').val(settings.offset ?? 32);
|
|
268
|
+
|
|
269
|
+
$container.find('#save-settings-btn').off('click').on('click', async () => {
|
|
270
|
+
const data = {
|
|
271
|
+
scrollThreshold: parseInt($container.find('#field-threshold').val(), 10) || 300,
|
|
272
|
+
position: $container.find('#field-position').val(),
|
|
273
|
+
label: $container.find('#field-label').val().trim(),
|
|
274
|
+
smooth: $container.find('#field-smooth').prop('checked'),
|
|
275
|
+
offset: parseInt($container.find('#field-offset').val(), 10) || 32
|
|
276
|
+
};
|
|
277
|
+
try {
|
|
278
|
+
const res = await fetch('/api/plugins/back-to-top/settings', {
|
|
279
|
+
method: 'PUT',
|
|
280
|
+
headers: {
|
|
281
|
+
'Content-Type': 'application/json',
|
|
282
|
+
'Authorization': 'Bearer ' + (S.get('auth_token') || '')
|
|
283
|
+
},
|
|
284
|
+
body: JSON.stringify(data)
|
|
285
|
+
});
|
|
286
|
+
if (!res.ok) throw new Error('Save failed');
|
|
287
|
+
E.toast('Settings saved.', { type: 'success' });
|
|
288
|
+
} catch {
|
|
289
|
+
E.toast('Failed to save settings.', { type: 'error' });
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
Domma.icons.scan();
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Task 8: Create public inject-body.html
|
|
301
|
+
|
|
302
|
+
**New file:** `plugins/back-to-top/public/inject-body.html`
|
|
303
|
+
|
|
304
|
+
Self-contained IIFE. No Domma dependency. Inline SVG for the arrow-up icon (path from Domma's built-in icon set).
|
|
305
|
+
|
|
306
|
+
```html
|
|
307
|
+
<script>
|
|
308
|
+
(function () {
|
|
309
|
+
'use strict';
|
|
310
|
+
|
|
311
|
+
var BTN_ID = 'back-to-top-btn';
|
|
312
|
+
|
|
313
|
+
fetch('/api/plugins/back-to-top/settings')
|
|
314
|
+
.then(function (r) { return r.json(); })
|
|
315
|
+
.then(function (cfg) {
|
|
316
|
+
var threshold = cfg.scrollThreshold || 300;
|
|
317
|
+
var position = cfg.position || 'bottom-right';
|
|
318
|
+
var label = cfg.label || '';
|
|
319
|
+
var smooth = cfg.smooth !== false;
|
|
320
|
+
var offset = cfg.offset || 32;
|
|
321
|
+
|
|
322
|
+
var btn = document.createElement('button');
|
|
323
|
+
btn.id = BTN_ID;
|
|
324
|
+
btn.type = 'button';
|
|
325
|
+
btn.setAttribute('aria-label', 'Back to top');
|
|
326
|
+
btn.setAttribute('title', 'Back to top');
|
|
327
|
+
|
|
328
|
+
// Inline SVG - arrow-up (Domma icon set path)
|
|
329
|
+
var svgNS = 'http://www.w3.org/2000/svg';
|
|
330
|
+
var svg = document.createElementNS(svgNS, 'svg');
|
|
331
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
332
|
+
svg.setAttribute('width', '20');
|
|
333
|
+
svg.setAttribute('height', '20');
|
|
334
|
+
svg.setAttribute('fill', 'none');
|
|
335
|
+
svg.setAttribute('stroke', 'currentColor');
|
|
336
|
+
svg.setAttribute('stroke-width', '2');
|
|
337
|
+
svg.setAttribute('stroke-linecap', 'round');
|
|
338
|
+
svg.setAttribute('stroke-linejoin', 'round');
|
|
339
|
+
var path = document.createElementNS(svgNS, 'path');
|
|
340
|
+
path.setAttribute('d', 'M12 19V5M5 12l7-7 7 7');
|
|
341
|
+
svg.appendChild(path);
|
|
342
|
+
btn.appendChild(svg);
|
|
343
|
+
|
|
344
|
+
if (label) {
|
|
345
|
+
var span = document.createElement('span');
|
|
346
|
+
span.textContent = label;
|
|
347
|
+
span.style.marginLeft = '6px';
|
|
348
|
+
btn.appendChild(span);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Inline styles - self-contained, no site.css dependency
|
|
352
|
+
var isRight = position !== 'bottom-left';
|
|
353
|
+
Object.assign(btn.style, {
|
|
354
|
+
position: 'fixed',
|
|
355
|
+
bottom: offset + 'px',
|
|
356
|
+
right: isRight ? offset + 'px' : 'auto',
|
|
357
|
+
left: isRight ? 'auto' : offset + 'px',
|
|
358
|
+
zIndex: '9999',
|
|
359
|
+
display: 'flex',
|
|
360
|
+
alignItems: 'center',
|
|
361
|
+
padding: '10px',
|
|
362
|
+
borderRadius: '50%',
|
|
363
|
+
border: 'none',
|
|
364
|
+
cursor: 'pointer',
|
|
365
|
+
background: 'rgba(0,0,0,0.5)',
|
|
366
|
+
color: '#fff',
|
|
367
|
+
backdropFilter: 'blur(4px)',
|
|
368
|
+
opacity: '0',
|
|
369
|
+
transform: 'translateY(12px)',
|
|
370
|
+
transition: 'opacity .25s, transform .25s',
|
|
371
|
+
pointerEvents: 'none'
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
if (label) {
|
|
375
|
+
btn.style.borderRadius = '999px';
|
|
376
|
+
btn.style.padding = '10px 16px';
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
document.body.appendChild(btn);
|
|
380
|
+
|
|
381
|
+
function show() {
|
|
382
|
+
btn.style.opacity = '1';
|
|
383
|
+
btn.style.transform = 'translateY(0)';
|
|
384
|
+
btn.style.pointerEvents = 'auto';
|
|
385
|
+
}
|
|
386
|
+
function hide() {
|
|
387
|
+
btn.style.opacity = '0';
|
|
388
|
+
btn.style.transform = 'translateY(12px)';
|
|
389
|
+
btn.style.pointerEvents = 'none';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
window.addEventListener('scroll', function () {
|
|
393
|
+
if (window.scrollY > threshold) { show(); } else { hide(); }
|
|
394
|
+
}, { passive: true });
|
|
395
|
+
|
|
396
|
+
btn.addEventListener('click', function () {
|
|
397
|
+
window.scrollTo({ top: 0, behavior: smooth ? 'smooth' : 'auto' });
|
|
398
|
+
});
|
|
399
|
+
})
|
|
400
|
+
.catch(function () {
|
|
401
|
+
// Settings fetch failed - don't inject button
|
|
402
|
+
});
|
|
403
|
+
})();
|
|
404
|
+
</script>
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Verification
|
|
410
|
+
|
|
411
|
+
After all tasks:
|
|
412
|
+
|
|
413
|
+
1. **Pages view tooltips** - navigate to Pages. Hover the external-link icon on a row. Tooltip "View" should appear.
|
|
414
|
+
|
|
415
|
+
2. **Navigation view tooltips** - navigate to Navigation. Hover the trash button on an item. Tooltip "Remove" should
|
|
416
|
+
appear.
|
|
417
|
+
|
|
418
|
+
3. **Plugin appears in sidebar** - restart the server. Navigate to Plugins. The "Back to Top" plugin card should appear.
|
|
419
|
+
The sidebar should show "Back to Top" under the Plugins section.
|
|
420
|
+
|
|
421
|
+
4. **Settings page loads** - click the Back to Top sidebar link. Settings form should show with defaults (threshold 300,
|
|
422
|
+
position bottom-right, smooth checked, offset 32).
|
|
423
|
+
|
|
424
|
+
5. **Save settings** - change threshold to 200, click Save. Toast "Settings saved." should appear.
|
|
425
|
+
|
|
426
|
+
6. **Public injection** - visit the public site (e.g. `/`). Scroll down more than 300px. The back-to-top button should
|
|
427
|
+
appear in the bottom-right corner. Clicking it should scroll back to the top.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Cookie Consent + Back to Top - Migrate to Core
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-03-05
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
Cookie Consent and Back to Top are currently implemented as plugins. They are universal features needed on every site - not optional add-ons. Living as plugins creates friction: they must be individually enabled, their settings are separate from Site Settings, and they clutter the plugin list with non-optional items. The plugin directories will be deleted entirely.
|
|
8
|
+
|
|
9
|
+
## Approach
|
|
10
|
+
|
|
11
|
+
**Piggyback on `window.__CMS_SITE__`** (Approach B).
|
|
12
|
+
|
|
13
|
+
The renderer already injects `window.__CMS_SITE__` (the full site config) into every public page. The new scripts will read their configuration directly from that global, eliminating any HTTP fetch on page load. The renderer conditionally injects each `<script>` tag based on the feature's `enabled` flag.
|
|
14
|
+
|
|
15
|
+
## Data Model
|
|
16
|
+
|
|
17
|
+
Two new top-level keys added to `config/site.json`:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
"backToTop": {
|
|
21
|
+
"enabled": false,
|
|
22
|
+
"scrollThreshold": 300,
|
|
23
|
+
"position": "bottom-right",
|
|
24
|
+
"label": "Back to top",
|
|
25
|
+
"smooth": true,
|
|
26
|
+
"offset": 40
|
|
27
|
+
},
|
|
28
|
+
"cookieConsent": {
|
|
29
|
+
"enabled": false,
|
|
30
|
+
"message": "We use cookies to improve your experience.",
|
|
31
|
+
"acceptButton": "Accept All",
|
|
32
|
+
"declineButton": "Decline",
|
|
33
|
+
"policyLink": "",
|
|
34
|
+
"policyText": "Privacy Policy",
|
|
35
|
+
"position": "bottom",
|
|
36
|
+
"layout": "bar",
|
|
37
|
+
"theme": "dark",
|
|
38
|
+
"consentVersion": "1",
|
|
39
|
+
"categories": {
|
|
40
|
+
"functional": true,
|
|
41
|
+
"analytics": false,
|
|
42
|
+
"marketing": false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`enabled: false` means the script tag is not injected - no runtime overhead for disabled features.
|
|
48
|
+
|
|
49
|
+
## Renderer Changes
|
|
50
|
+
|
|
51
|
+
`server/services/renderer.js` - after assembling `bodyEndInject`, conditionally append script tags:
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
if (site.backToTop?.enabled) {
|
|
55
|
+
bodyEndInject += '\n<script src="/public/js/btt.js"></script>';
|
|
56
|
+
}
|
|
57
|
+
if (site.cookieConsent?.enabled) {
|
|
58
|
+
bodyEndInject += '\n<script src="/public/js/cookie-consent.js"></script>';
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The scripts are served as static files from `public/js/` via Fastify's existing static serving.
|
|
63
|
+
|
|
64
|
+
## Public Scripts
|
|
65
|
+
|
|
66
|
+
### `public/js/btt.js`
|
|
67
|
+
|
|
68
|
+
Ported from `plugins/back-to-top/public/inject-body.html`. Key change: reads config from `window.__CMS_SITE__.backToTop` instead of fetching `/api/plugins/back-to-top/settings`. Same scroll-listener + button-inject logic.
|
|
69
|
+
|
|
70
|
+
### `public/js/cookie-consent.js`
|
|
71
|
+
|
|
72
|
+
Ported from `plugins/cookie-consent/public/inject-body.html`. Key change: reads config from `window.__CMS_SITE__.cookieConsent` instead of fetching `/api/plugins/cookie-consent/settings`. Same consent-bar + localStorage logic.
|
|
73
|
+
|
|
74
|
+
## Admin - Site Settings Tabs
|
|
75
|
+
|
|
76
|
+
Two new tabs added to `admin/js/templates/settings.html` alongside General / SEO / Footer / Email:
|
|
77
|
+
|
|
78
|
+
- **Back to Top** - fields: Enabled toggle, Scroll threshold, Position (bottom-right/bottom-left), Label, Smooth scroll, Bottom offset
|
|
79
|
+
- **Cookie Consent** - fields: Enabled toggle, Message, Accept/Decline button text, Policy link + text, Position (bottom/top), Layout (bar/modal), Theme (dark/light), Consent version, Category toggles (functional/analytics/marketing)
|
|
80
|
+
|
|
81
|
+
`admin/js/views/settings.js` - extends existing save/load logic to include `backToTop` and `cookieConsent` keys when reading/writing `config/site.json` via `PUT /api/settings/site`.
|
|
82
|
+
|
|
83
|
+
## Deletions
|
|
84
|
+
|
|
85
|
+
- Delete `plugins/back-to-top/` directory entirely
|
|
86
|
+
- Delete `plugins/cookie-consent/` directory entirely
|
|
87
|
+
- Remove their entries from `config/plugins.json`
|
|
88
|
+
|
|
89
|
+
No migration of existing plugin settings is needed - defaults in `config/site.json` will be used, and admins can re-enable and configure via Site Settings.
|
|
90
|
+
|
|
91
|
+
## Files Summary
|
|
92
|
+
|
|
93
|
+
### New Files
|
|
94
|
+
- `public/js/btt.js` - Back to Top script (reads from `window.__CMS_SITE__`)
|
|
95
|
+
- `public/js/cookie-consent.js` - Cookie Consent script (reads from `window.__CMS_SITE__`)
|
|
96
|
+
|
|
97
|
+
### Modified Files
|
|
98
|
+
- `config/site.json` - Add `backToTop` and `cookieConsent` keys
|
|
99
|
+
- `server/services/renderer.js` - Conditionally inject script tags
|
|
100
|
+
- `admin/js/templates/settings.html` - Add Back to Top and Cookie Consent tab panels
|
|
101
|
+
- `admin/js/views/settings.js` - Handle new settings sections on load/save
|
|
102
|
+
|
|
103
|
+
### Deleted
|
|
104
|
+
- `plugins/back-to-top/` (entire directory)
|
|
105
|
+
- `plugins/cookie-consent/` (entire directory)
|
|
106
|
+
- Entries for both in `config/plugins.json`
|
|
107
|
+
|
|
108
|
+
## Verification
|
|
109
|
+
|
|
110
|
+
1. Enable Back to Top in Site Settings → save → visit public page → scroll down → button appears
|
|
111
|
+
2. Disable Back to Top → save → visit public page → no button, no script tag in source
|
|
112
|
+
3. Enable Cookie Consent → save → visit public page in private window → consent bar appears
|
|
113
|
+
4. Disable Cookie Consent → save → visit public page → no consent bar, no script tag
|
|
114
|
+
5. Plugin list in admin → back-to-top and cookie-consent no longer appear
|
|
115
|
+
6. Site Settings → two new tabs visible (Back to Top, Cookie Consent) with correct fields
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Custom CSS - Migrate to Core
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-03-05
|
|
4
|
+
**Status:** Approved
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Move the Custom CSS plugin into core, eliminating the plugin directory. The feature becomes a tab in Site Settings backed by a content file and core API routes.
|
|
9
|
+
|
|
10
|
+
## Storage
|
|
11
|
+
|
|
12
|
+
CSS is stored at `content/custom.css` - a plain text file, consistent with other content files. Empty by default.
|
|
13
|
+
|
|
14
|
+
## Renderer
|
|
15
|
+
|
|
16
|
+
`server/services/renderer.js` reads `content/custom.css` at render time. If the file is non-empty, an inline `<style>` block is injected into the `headLate` slot. The string `</style>` inside CSS values is escaped to `<\/style>` to prevent injection.
|
|
17
|
+
|
|
18
|
+
## Admin API
|
|
19
|
+
|
|
20
|
+
Two new routes added to `server/routes/api/settings.js`:
|
|
21
|
+
|
|
22
|
+
- `GET /api/settings/custom-css` - authenticated, returns `{ css: "..." }`
|
|
23
|
+
- `PUT /api/settings/custom-css` - requireAdmin, validates string type + 100 KB limit, writes `content/custom.css`
|
|
24
|
+
|
|
25
|
+
## Admin UI
|
|
26
|
+
|
|
27
|
+
A new **Custom CSS** tab added to `admin/js/templates/settings.html` containing a full-height monospace `<textarea id="field-custom-css">`.
|
|
28
|
+
|
|
29
|
+
Since CSS is a file rather than part of the JSON config blob, it gets its own **Save CSS** button inside the tab. `admin/js/views/settings.js` wires up the tab: loads CSS on mount, saves via the new API endpoint on button click.
|
|
30
|
+
|
|
31
|
+
## Cleanup
|
|
32
|
+
|
|
33
|
+
- Delete `plugins/custom-css/` directory entirely
|
|
34
|
+
- Remove `custom-css` entry from `config/plugins.json`
|
|
35
|
+
- No sidebar entry needed - accessible via Settings → Custom CSS tab
|