domma-cms 0.66.0 → 0.69.4
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/CLAUDE.md +64 -1
- package/README.md +14 -10
- package/admin/css/admin.css +1 -1
- package/admin/index.html +1 -0
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +4 -4
- package/admin/js/lib/analytics-shape.js +1 -0
- package/admin/js/lib/contacts-arrange.js +1 -0
- package/admin/js/lib/help-popover.js +1 -0
- package/admin/js/lib/notes-arrange.js +1 -0
- package/admin/js/lib/plugin-accent.js +1 -1
- package/admin/js/lib/plugin-chrome.js +1 -1
- package/admin/js/lib/sidebar-grouping.js +1 -1
- package/admin/js/lib/sidebar-grouping.test.js +1 -1
- package/admin/js/lib/sidebar-renderer.js +3 -3
- package/admin/js/lib/todo-arrange.js +1 -0
- package/admin/js/lib/tool-kit.js +1 -0
- package/admin/js/templates/analytics.html +138 -0
- package/admin/js/templates/contacts.html +156 -0
- package/admin/js/templates/notes.html +137 -0
- package/admin/js/templates/plugin-marketplace.html +17 -4
- package/admin/js/templates/plugins.html +13 -7
- package/admin/js/templates/theme.html +38 -0
- package/admin/js/templates/todo.html +113 -0
- package/admin/js/views/analytics.js +62 -0
- package/admin/js/views/contacts.js +174 -0
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/notes.js +104 -0
- package/admin/js/views/plugin-marketplace.js +1 -1
- package/admin/js/views/plugins.js +24 -22
- package/admin/js/views/theme.js +1 -1
- package/admin/js/views/todo.js +96 -0
- package/bin/cli.js +4 -10
- package/bin/lib/plugin-version.js +28 -0
- package/bin/update.js +14 -0
- package/config/menus/admin-sidebar.json +1 -1
- package/config/server.json +3 -1
- package/package.json +3 -7
- package/plugins/_lib/admin/mail/reader-view.js +20 -0
- package/plugins/_lib/mail/store.js +21 -1
- package/plugins/blog/CLAUDE.md +237 -0
- package/plugins/blog/admin/css/index.css +178 -1
- package/plugins/blog/admin/templates/blog.html +35 -53
- package/plugins/blog/admin/templates/categories.html +19 -6
- package/plugins/blog/admin/templates/comments.html +26 -9
- package/plugins/blog/admin/templates/post-editor.html +36 -13
- package/plugins/blog/admin/templates/settings.html +2 -11
- package/plugins/blog/admin/views/blog.js +288 -162
- package/plugins/blog/admin/views/categories.js +212 -201
- package/plugins/blog/admin/views/comments.js +207 -158
- package/plugins/blog/admin/views/kit.js +372 -0
- package/plugins/blog/admin/views/post-editor.js +269 -21
- package/plugins/blog/admin/views/settings-cog.js +56 -0
- package/plugins/blog/admin/views/settings.js +16 -86
- package/plugins/blog/blocks/blog-card-compact.css +17 -0
- package/plugins/blog/blocks/blog-card-compact.html +8 -0
- package/plugins/blog/blocks/blog-card-row.css +22 -0
- package/plugins/blog/blocks/blog-card-row.html +11 -0
- package/plugins/blog/blocks/blog-card.css +35 -0
- package/plugins/blog/blocks/blog-card.html +16 -0
- package/plugins/blog/blocks/blog-post-classic.css +15 -0
- package/plugins/blog/blocks/blog-post-classic.html +28 -0
- package/plugins/blog/blocks/blog-post-feature.css +20 -0
- package/plugins/blog/blocks/blog-post-feature.html +30 -0
- package/plugins/blog/blocks/blog-post-minimal.css +10 -0
- package/plugins/blog/blocks/blog-post-minimal.html +23 -0
- package/plugins/blog/blocks/blog-post-sidebar.css +22 -0
- package/plugins/blog/blocks/blog-post-sidebar.html +36 -0
- package/plugins/blog/collections/posts/schema.json +3 -1
- package/plugins/blog/config.js +6 -0
- package/plugins/blog/lib/layouts.js +304 -0
- package/plugins/blog/lib/page.js +158 -0
- package/plugins/blog/lib/render.js +119 -0
- package/plugins/blog/lib/samples.js +234 -0
- package/plugins/blog/plugin.js +225 -15
- package/plugins/blog/plugin.json +21 -81
- package/plugins/blog/plugin.public.js +115 -210
- package/plugins/blog/public/blog.css +177 -0
- package/plugins/blog/public/blog.js +586 -0
- package/plugins/blog/public/samples/aurora.svg +7 -0
- package/plugins/blog/public/samples/dusk.svg +7 -0
- package/plugins/blog/public/samples/ember.svg +7 -0
- package/plugins/blog/public/samples/harbour.svg +7 -0
- package/plugins/blog/public/samples/meadow.svg +7 -0
- package/plugins/blog/public/samples/rosewood.svg +7 -0
- package/plugins/blog/templates/index.html +9 -8
- package/plugins/blog/templates/post.html +4 -17
- package/plugins/blog/tests/layouts.test.js +97 -0
- package/plugins/blog/tests/public.test.js +71 -0
- package/plugins/free-tier.lock.json +74 -0
- package/plugins/mail-reader/CLAUDE.md +78 -0
- package/plugins/mail-reader/plugin.json +2 -1
- package/public/css/theme-switcher.css +1 -0
- package/public/js/analytics.js +1 -0
- package/public/js/theme-switcher.js +1 -0
- package/scripts/build.js +41 -2
- package/scripts/run-tests.mjs +94 -0
- package/{plugins/analytics/plugin.js → server/routes/api/analytics.js} +89 -41
- package/{plugins/contacts/plugin.js → server/routes/api/contacts.js} +71 -43
- package/server/routes/api/dashboard.js +8 -2
- package/{plugins/notes/plugin.js → server/routes/api/notes.js} +61 -17
- package/server/routes/api/plugin-marketplace.js +78 -23
- package/server/routes/api/plugins.js +148 -3
- package/server/routes/api/todo.js +178 -0
- package/server/server.js +61 -1
- package/server/services/collections.js +13 -3
- package/server/services/managerClient.js +72 -136
- package/server/services/markdown.js +54 -1
- package/server/services/permissionRegistry.js +50 -0
- package/server/services/pluginEntitlement.js +171 -0
- package/server/services/pluginEnvelope.js +242 -0
- package/server/services/pluginInstaller.js +246 -26
- package/server/services/pluginScaffold.js +5 -0
- package/server/services/plugins.js +117 -7
- package/server/services/presetCollections.js +344 -0
- package/server/services/promoted-plugins-migration.js +254 -0
- package/server/services/publicCors.js +46 -0
- package/server/services/renderer.js +83 -3
- package/server/services/sidebarBadges.js +37 -0
- package/server/services/siteGitignore.js +310 -0
- package/server/services/themeSettings.js +39 -0
- package/config/theme.json +0 -18
- package/plugins/analytics/admin/css/index.css +0 -1
- package/plugins/analytics/admin/templates/analytics.html +0 -70
- package/plugins/analytics/admin/views/analytics.js +0 -176
- package/plugins/analytics/config.js +0 -14
- package/plugins/analytics/plugin.json +0 -60
- package/plugins/analytics/public/inject-body.html +0 -60
- package/plugins/analytics/public/inject-head.html +0 -1
- package/plugins/blog/templates/author.html +0 -9
- package/plugins/blog/templates/category.html +0 -9
- package/plugins/blog/templates/tag.html +0 -9
- package/plugins/contacts/admin/templates/contacts.html +0 -254
- package/plugins/contacts/admin/views/contacts.js +0 -943
- package/plugins/contacts/collections/user-contact-groups/schema.json +0 -35
- package/plugins/contacts/collections/user-contacts/schema.json +0 -179
- package/plugins/contacts/config.js +0 -6
- package/plugins/contacts/plugin.json +0 -36
- package/plugins/demo-viewer/config.js +0 -4
- package/plugins/demo-viewer/plugin.js +0 -36
- package/plugins/demo-viewer/plugin.json +0 -9
- package/plugins/invoice/admin/css/index.css +0 -1
- package/plugins/invoice/admin/templates/editor.html +0 -220
- package/plugins/invoice/admin/templates/index.html +0 -173
- package/plugins/invoice/admin/templates/issuers.html +0 -2
- package/plugins/invoice/admin/templates/receivers.html +0 -2
- package/plugins/invoice/admin/views/contacts-source.js +0 -266
- package/plugins/invoice/admin/views/editor.js +0 -434
- package/plugins/invoice/admin/views/export.js +0 -199
- package/plugins/invoice/admin/views/help-content.js +0 -61
- package/plugins/invoice/admin/views/index.js +0 -643
- package/plugins/invoice/admin/views/issuers.js +0 -30
- package/plugins/invoice/admin/views/media.js +0 -172
- package/plugins/invoice/admin/views/party-view.js +0 -386
- package/plugins/invoice/admin/views/payments.js +0 -127
- package/plugins/invoice/admin/views/print.js +0 -130
- package/plugins/invoice/admin/views/receivers.js +0 -55
- package/plugins/invoice/admin/views/send.js +0 -212
- package/plugins/invoice/admin/views/settings.js +0 -594
- package/plugins/invoice/admin/views/view-lifecycle.js +0 -33
- package/plugins/invoice/collections/invoice-issuers/schema.json +0 -82
- package/plugins/invoice/collections/invoice-receivers/schema.json +0 -16
- package/plugins/invoice/collections/invoices/schema.json +0 -33
- package/plugins/invoice/config.js +0 -37
- package/plugins/invoice/pdf.js +0 -164
- package/plugins/invoice/plugin.js +0 -1456
- package/plugins/invoice/plugin.json +0 -86
- package/plugins/invoice/templates/_base.css +0 -1
- package/plugins/invoice/templates/classic-nologo.html +0 -100
- package/plugins/invoice/templates/classic.html +0 -91
- package/plugins/invoice/templates/invoice-print.html +0 -237
- package/plugins/invoice/templates/minimal.html +0 -99
- package/plugins/invoice/templates/modern-nologo.html +0 -114
- package/plugins/invoice/templates/modern.html +0 -113
- package/plugins/invoice/templates/templates.json +0 -11
- package/plugins/notes/admin/templates/notes.html +0 -83
- package/plugins/notes/admin/views/notes.js +0 -281
- package/plugins/notes/collections/user-notes/schema.json +0 -54
- package/plugins/notes/config.js +0 -6
- package/plugins/notes/plugin.json +0 -33
- package/plugins/surveys/admin/templates/audience.html +0 -47
- package/plugins/surveys/admin/templates/results.html +0 -58
- package/plugins/surveys/admin/templates/survey-editor.html +0 -141
- package/plugins/surveys/admin/templates/surveys.html +0 -25
- package/plugins/surveys/admin/views/audience.js +0 -301
- package/plugins/surveys/admin/views/results.js +0 -172
- package/plugins/surveys/admin/views/survey-editor.js +0 -211
- package/plugins/surveys/admin/views/surveys.js +0 -161
- package/plugins/surveys/collections/survey-contacts/schema.json +0 -13
- package/plugins/surveys/collections/survey-groups/schema.json +0 -11
- package/plugins/surveys/collections/survey-invites/schema.json +0 -16
- package/plugins/surveys/collections/surveys/schema.json +0 -23
- package/plugins/surveys/config.js +0 -8
- package/plugins/surveys/lib/audience.js +0 -37
- package/plugins/surveys/lib/campaigns.js +0 -43
- package/plugins/surveys/lib/ledger.js +0 -110
- package/plugins/surveys/lib/sending.js +0 -106
- package/plugins/surveys/lib/stats.js +0 -62
- package/plugins/surveys/lib/submit.js +0 -95
- package/plugins/surveys/lib/tokens.js +0 -28
- package/plugins/surveys/plugin.js +0 -174
- package/plugins/surveys/plugin.json +0 -65
- package/plugins/surveys/plugin.public.js +0 -149
- package/plugins/surveys/public/dist-shared.mjs +0 -1
- package/plugins/surveys/public/survey.css +0 -1
- package/plugins/surveys/public/survey.mjs +0 -1
- package/plugins/surveys/templates/survey-page.html +0 -63
- package/plugins/theme-switcher/admin/templates/theme-switcher.html +0 -86
- package/plugins/theme-switcher/admin/views/theme-switcher.js +0 -65
- package/plugins/theme-switcher/config.js +0 -10
- package/plugins/theme-switcher/plugin.js +0 -26
- package/plugins/theme-switcher/plugin.json +0 -74
- package/plugins/theme-switcher/public/inject-body.html +0 -153
- package/plugins/theme-switcher/public/inject-head.html +0 -260
- package/plugins/todo/admin/templates/todo.html +0 -158
- package/plugins/todo/admin/views/todo.js +0 -343
- package/plugins/todo/collections/todos/schema.json +0 -60
- package/plugins/todo/config.js +0 -7
- package/plugins/todo/plugin.js +0 -102
- package/plugins/todo/plugin.json +0 -33
- /package/{plugins/contacts/admin/css/index.css → admin/css/contacts.css} +0 -0
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "surveys",
|
|
3
|
-
"displayName": "Surveys",
|
|
4
|
-
"version": "1.0.0",
|
|
5
|
-
"description": "Targeted surveys over the Forms engine: shared audiences, tokenised invites, reminders, and per-survey statistics.",
|
|
6
|
-
"author": "Darryl Waterhouse",
|
|
7
|
-
"date": "2026-06-26",
|
|
8
|
-
"icon": "clipboard-list",
|
|
9
|
-
"admin": {
|
|
10
|
-
"sidebar": [
|
|
11
|
-
{
|
|
12
|
-
"id": "surveys",
|
|
13
|
-
"text": "Surveys",
|
|
14
|
-
"icon": "clipboard-list",
|
|
15
|
-
"url": "#/plugins/surveys",
|
|
16
|
-
"section": "#/plugins/surveys"
|
|
17
|
-
}
|
|
18
|
-
],
|
|
19
|
-
"routes": [
|
|
20
|
-
{
|
|
21
|
-
"path": "/plugins/surveys",
|
|
22
|
-
"view": "plugin-surveys",
|
|
23
|
-
"title": "Surveys - Domma CMS"
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"path": "/plugins/surveys/new",
|
|
27
|
-
"view": "plugin-survey-editor",
|
|
28
|
-
"title": "New Survey - Domma CMS"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"path": "/plugins/surveys/edit/:slug",
|
|
32
|
-
"view": "plugin-survey-editor",
|
|
33
|
-
"title": "Edit Survey - Domma CMS"
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"path": "/plugins/surveys/audience",
|
|
37
|
-
"view": "plugin-survey-audience",
|
|
38
|
-
"title": "Survey Audience - Domma CMS"
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"path": "/plugins/surveys/results/:slug",
|
|
42
|
-
"view": "plugin-survey-results",
|
|
43
|
-
"title": "Survey Results - Domma CMS"
|
|
44
|
-
}
|
|
45
|
-
],
|
|
46
|
-
"views": {
|
|
47
|
-
"plugin-surveys": {
|
|
48
|
-
"entry": "surveys/admin/views/surveys.js?v=86946458",
|
|
49
|
-
"exportName": "surveysView"
|
|
50
|
-
},
|
|
51
|
-
"plugin-survey-editor": {
|
|
52
|
-
"entry": "surveys/admin/views/survey-editor.js?v=c6b3e1b7",
|
|
53
|
-
"exportName": "surveyEditorView"
|
|
54
|
-
},
|
|
55
|
-
"plugin-survey-audience": {
|
|
56
|
-
"entry": "surveys/admin/views/audience.js?v=6aca096f",
|
|
57
|
-
"exportName": "audienceView"
|
|
58
|
-
},
|
|
59
|
-
"plugin-survey-results": {
|
|
60
|
-
"entry": "surveys/admin/views/results.js?v=97b8e1fd",
|
|
61
|
-
"exportName": "resultsView"
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Surveys Plugin - Public Routes
|
|
3
|
-
* Root-level public routes. Mounted at the Fastify root (no prefix).
|
|
4
|
-
*
|
|
5
|
-
* This file currently exposes the tokenised submit endpoint. The hosted survey
|
|
6
|
-
* page and scheduler contract routes are added in later tasks.
|
|
7
|
-
*/
|
|
8
|
-
import path from 'path';
|
|
9
|
-
import fs from 'fs/promises';
|
|
10
|
-
import {fileURLToPath} from 'url';
|
|
11
|
-
import {createEntry, getEntry, listEntries, updateEntry, getCollection} from '../../server/services/collections.js';
|
|
12
|
-
import {readForm} from '../../server/services/forms.js';
|
|
13
|
-
import {createTransport, sendFormEmail, sendEmail} from '../../server/services/email.js';
|
|
14
|
-
import {getConfig} from '../../server/config.js';
|
|
15
|
-
import {createCampaigns} from './lib/campaigns.js';
|
|
16
|
-
import {createAudience} from './lib/audience.js';
|
|
17
|
-
import {createLedger} from './lib/ledger.js';
|
|
18
|
-
import {processSurveySubmission, SurveyError} from './lib/submit.js';
|
|
19
|
-
import {runSend} from './lib/sending.js';
|
|
20
|
-
import {parseMarkdown} from '../../server/services/markdown.js';
|
|
21
|
-
|
|
22
|
-
/** Render a Markdown string to sanitised HTML, or '' when empty. */
|
|
23
|
-
async function renderMarkdown(md) {
|
|
24
|
-
const text = (md ?? '').trim();
|
|
25
|
-
if (!text) return '';
|
|
26
|
-
try {
|
|
27
|
-
const {html} = await parseMarkdown('---\n---\n' + text, {});
|
|
28
|
-
return html || '';
|
|
29
|
-
} catch {
|
|
30
|
-
return '';
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
35
|
-
const TEMPLATES_DIR = path.join(__dirname, 'templates');
|
|
36
|
-
const col = {createEntry, getEntry, listEntries, updateEntry};
|
|
37
|
-
|
|
38
|
-
function escapeHtml(str) {
|
|
39
|
-
return String(str ?? '')
|
|
40
|
-
.replace(/&/g, '&')
|
|
41
|
-
.replace(/</g, '<')
|
|
42
|
-
.replace(/>/g, '>')
|
|
43
|
-
.replace(/"/g, '"')
|
|
44
|
-
.replace(/'/g, ''');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export default async function surveysPublicPlugin(fastify, options) {
|
|
48
|
-
const campaigns = createCampaigns(col);
|
|
49
|
-
const audience = createAudience(col);
|
|
50
|
-
const ledger = createLedger(col);
|
|
51
|
-
const schedulerToken = options.settings?.schedulerToken || '';
|
|
52
|
-
|
|
53
|
-
async function sendCompletionEmail(form, data) {
|
|
54
|
-
const ea = form.actions?.email;
|
|
55
|
-
if (!ea?.enabled || !ea.recipients) return;
|
|
56
|
-
const smtp = getConfig('site').smtp || {};
|
|
57
|
-
const transport = await createTransport(smtp);
|
|
58
|
-
await sendFormEmail(transport, {
|
|
59
|
-
from: smtp.fromAddress, fromName: smtp.fromName, to: ea.recipients,
|
|
60
|
-
subject: `${ea.subjectPrefix || `[${form.title}]`} New survey response`,
|
|
61
|
-
formTitle: form.title, fields: form.fields, data
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const deps = {
|
|
66
|
-
col, ledger,
|
|
67
|
-
getSurveyBySlug: (slug) => campaigns.getBySlug(slug),
|
|
68
|
-
readForm,
|
|
69
|
-
getCollection,
|
|
70
|
-
sendCompletionEmail,
|
|
71
|
-
now: () => new Date().toISOString()
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
fastify.post('/api/plugins/surveys/submit/:slug', async (req, reply) => {
|
|
75
|
-
const {token, data} = req.body ?? {};
|
|
76
|
-
try {
|
|
77
|
-
const result = await processSurveySubmission(req.params.slug, token, data || {}, deps);
|
|
78
|
-
return reply.send(result);
|
|
79
|
-
} catch (err) {
|
|
80
|
-
const code = err instanceof SurveyError ? err.statusCode : 500;
|
|
81
|
-
return reply.code(code).send({error: err.message});
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
function requireScheduler(req, reply) {
|
|
86
|
-
const hdr = req.headers.authorization || '';
|
|
87
|
-
const tok = hdr.startsWith('Bearer ') ? hdr.slice(7) : '';
|
|
88
|
-
if (!schedulerToken || tok !== schedulerToken) { reply.code(403).send({error: 'Forbidden'}); return false; }
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
fastify.get('/api/plugins/surveys/due-work', async (req, reply) => {
|
|
93
|
-
if (!requireScheduler(req, reply)) return;
|
|
94
|
-
const now = new Date();
|
|
95
|
-
const surveys = (await campaigns.all()).filter(s => s.data.status === 'open');
|
|
96
|
-
const out = [];
|
|
97
|
-
for (const s of surveys) {
|
|
98
|
-
const due = await ledger.dueForReminder(s.id, Number(s.data.reminderAfterDays) || 7, now);
|
|
99
|
-
if (due.length) out.push({plugin: 'surveys', campaignSlug: s.data.slug, dueCount: due.length});
|
|
100
|
-
}
|
|
101
|
-
return reply.send(out);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
fastify.post('/api/plugins/surveys/run', async (req, reply) => {
|
|
105
|
-
if (!requireScheduler(req, reply)) return;
|
|
106
|
-
const {campaignSlug, audience: mode = 'non-responders'} = req.body ?? {};
|
|
107
|
-
const survey = await campaigns.getBySlug(campaignSlug);
|
|
108
|
-
if (!survey) return reply.code(404).send({error: 'Survey not found'});
|
|
109
|
-
const smtp = getConfig('site').smtp || {};
|
|
110
|
-
const transport = await createTransport(smtp);
|
|
111
|
-
// Absolute survey links: configured site URL, else this request's origin.
|
|
112
|
-
const baseUrl = (getConfig('site').url || '').replace(/\/$/, '')
|
|
113
|
-
|| `${req.protocol}://${req.headers.host}`;
|
|
114
|
-
const {results} = await runSend({
|
|
115
|
-
survey, mode: mode === 'non-responders' ? 'non-responders' : 'all',
|
|
116
|
-
audience, ledger, transport, sendEmail, smtp,
|
|
117
|
-
baseUrl, fromName: 'Surveys', nowIso: new Date().toISOString()
|
|
118
|
-
});
|
|
119
|
-
return reply.send({results});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Hosted survey page - token travels in ?t=, or the visitor enters a code.
|
|
123
|
-
fastify.get('/survey/:slug', async (req, reply) => {
|
|
124
|
-
const survey = await campaigns.getBySlug(req.params.slug);
|
|
125
|
-
if (!survey) {
|
|
126
|
-
return reply.code(404).type('text/html')
|
|
127
|
-
.send('<!DOCTYPE html><html lang="en-GB"><head><meta charset="UTF-8"><title>Survey unavailable</title></head><body><p>This survey is not available.</p></body></html>');
|
|
128
|
-
}
|
|
129
|
-
if (survey.data.status !== 'open') {
|
|
130
|
-
return reply.code(410).type('text/html')
|
|
131
|
-
.send('<!DOCTYPE html><html lang="en-GB"><head><meta charset="UTF-8"><title>Survey closed</title></head><body><p>This survey has closed.</p></body></html>');
|
|
132
|
-
}
|
|
133
|
-
const template = await fs.readFile(path.join(TEMPLATES_DIR, 'survey-page.html'), 'utf8');
|
|
134
|
-
const [introHtml, footerHtml] = await Promise.all([
|
|
135
|
-
renderMarkdown(survey.data.intro),
|
|
136
|
-
renderMarkdown(survey.data.footer)
|
|
137
|
-
]);
|
|
138
|
-
const html = template
|
|
139
|
-
.replace(/\{\{title\}\}/g, escapeHtml(survey.data.title))
|
|
140
|
-
.replace(/\{\{slug\}\}/g, escapeHtml(survey.data.slug))
|
|
141
|
-
.replace(/\{\{formSlug\}\}/g, escapeHtml(survey.data.formSlug))
|
|
142
|
-
// Intro/footer are already sanitised HTML from the Markdown pipeline.
|
|
143
|
-
.replace(/\{\{intro\}\}/g, introHtml)
|
|
144
|
-
.replace(/\{\{footer\}\}/g, footerHtml);
|
|
145
|
-
return reply.type('text/html').send(html);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
fastify.surveysPublic = {campaigns, audience, ledger, deps};
|
|
149
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export function renderDistribution(a){return'<div class="survey-dist">'+a.map(s=>`<div class="survey-dist-row"><span class="survey-dist-label">${e(s.label)}</span><span class="survey-dist-bar"><span class="survey-dist-fill" data-pct="${s.pct}" style="width:${s.pct}%"></span></span><span class="survey-dist-val">${s.value} (${s.pct}%)</span></div>`).join("")+"</div>"}function e(a){return String(a??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.survey-container{max-width:720px;margin:0 auto;padding:2rem 1rem 4rem}.survey-title{margin-bottom:1.5rem}.survey-code{margin-bottom:1.5rem;padding:1rem 1.25rem;border:1px solid var(--dm-border-color, #d0d0d0);border-radius:var(--dm-radius, 8px);background:var(--dm-surface, #fafafa)}.survey-code label{display:block;margin-bottom:.5rem;font-weight:600}.survey-code-row{display:flex;gap:.5rem;align-items:stretch;flex-wrap:wrap}.survey-code-row input{flex:1 1 200px;padding:.5rem .75rem;border:1px solid var(--dm-border-color, #d0d0d0);border-radius:var(--dm-radius, 6px);font:inherit}.survey-state{padding:1.5rem;border-radius:var(--dm-radius, 8px);text-align:center;font-size:1.05rem;line-height:1.5}.survey-state-success{background:var(--dm-success-bg, #e8f6ec);color:var(--dm-success-text, #1c6b34);border:1px solid var(--dm-success-border, #b6e0c2)}.survey-state-error{background:var(--dm-danger-bg, #fcebec);color:var(--dm-danger-text, #8a1f28);border:1px solid var(--dm-danger-border, #f0bfc4)}.survey-state-info{background:var(--dm-surface, #f4f4f4);color:var(--dm-text-muted, #555);border:1px solid var(--dm-border-color, #d8d8d8)}.survey-dist{display:flex;flex-direction:column;gap:.5rem;margin:.75rem 0}.survey-dist-row{display:grid;grid-template-columns:minmax(80px,30%) 1fr auto;align-items:center;gap:.75rem}.survey-dist-label{font-size:.9rem;color:var(--dm-text, #333);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.survey-dist-bar{position:relative;height:.9rem;background:var(--dm-surface-alt, #ececec);border-radius:var(--dm-radius, 6px);overflow:hidden}.survey-dist-fill{position:absolute;inset:0 auto 0 0;height:100%;background:var(--dm-primary, #3b6ef0);border-radius:inherit;transition:width .3s ease}.survey-dist-val{font-size:.85rem;color:var(--dm-text-muted, #666);min-width:3.5rem;text-align:right}.survey-intro{margin:0 0 1.5rem}.survey-footer{margin:1.5rem 0 0;padding-top:1rem;border-top:1px solid var(--dm-border, #e5e7eb);color:var(--dm-text-muted, #6b7280);font-size:.92rem}.survey-intro:empty,.survey-footer:empty{display:none;border:0;margin:0;padding:0}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const SKIP_TYPES=new Set(["page-break","spacer","heading","paragraph","html"]);function mapType(e){switch(e){case"textarea":return"textarea";case"select":case"dropdown":return"select";case"radio":return"radio";case"checkbox":case"checkboxes":return"checkbox";case"email":return"email";case"number":return"number";case"date":return"date";case"tel":case"phone":return"tel";default:return"text"}}function buildBlueprint(e){const r={};return(e||[]).forEach(t=>{!t||!t.name||SKIP_TYPES.has(t.type)||(r[t.name]={type:mapType(t.type),label:t.label||t.name,required:!!t.required,options:t.options})}),r}function toast(e,r){typeof E<"u"&&E&&typeof E.toast=="function"&&E.toast(e,{type:r||"info"})}function clear(e){for(;e.firstChild;)e.removeChild(e.firstChild)}function showState(e,r,t){clear(e);const o=document.createElement("div");o.className="survey-state survey-state-"+(t||"info"),o.textContent=r,e.appendChild(o)}async function submitResponse(e,r,t){if(typeof H<"u"&&H&&typeof H.post=="function")return H.post("/api/plugins/surveys/submit/"+e,{token:r,data:t});const o=await fetch("/api/plugins/surveys/submit/"+e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:r,data:t})}),s=await o.json().catch(()=>({}));if(!o.ok){const n=new Error(s.error||"Submission failed");throw n.body=s,n}return s}async function renderForm(e,r,t,o){let s;try{const i=await fetch("/api/forms/"+r+"/public");if(!i.ok)throw new Error("Form not found");s=await i.json()}catch{showState(e,"This survey form could not be loaded.","error");return}const n=s.fields||[],u=s.settings||{},c=buildBlueprint(n),f=async i=>{try{const a=await submitResponse(t,o,i||{});showState(e,a.message||"Thank you for completing this survey.","success"),toast(a.message||"Response recorded","success")}catch(a){const d=a.body&&a.body.error||a.message||"Submission failed";toast(d,"error"),showState(e,d,"error")}};clear(e),typeof Domma<"u"&&Domma.forms&&typeof Domma.forms.render=="function"?Domma.forms.render(e,c,{},{submitText:u.submitText||"Submit",onSubmit:f}):showState(e,"This survey requires JavaScript components that failed to load.","error")}function startMount(e){const r=e.getAttribute("data-survey-slug"),t=e.getAttribute("data-form-slug");if(!r||!t){showState(e,"This survey is misconfigured.","error");return}let s=new URLSearchParams(window.location.search).get("t");if(s){renderForm(e,t,r,s);return}const n=document.getElementById("survey-code");if(!n){showState(e,"Please use the personalised link from your invitation, or enter your code via the survey page.","info");return}n.style.display="",showState(e,"Enter your code above to begin.","info"),n.addEventListener("submit",u=>{u.preventDefault();const c=n.querySelector('[name="code"]');s=c?c.value.trim():"",s&&(n.style.display="none",clear(e),renderForm(e,t,r,s))})}function start(){const e=new Set,r=document.getElementById("survey-mount");r&&e.add(r),document.querySelectorAll(".survey-mount").forEach(t=>e.add(t)),e.forEach(startMount)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",start):start();
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en-GB">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>{{title}}</title>
|
|
7
|
-
<meta name="robots" content="noindex">
|
|
8
|
-
|
|
9
|
-
<!-- DommaJS CSS -->
|
|
10
|
-
<link rel="stylesheet" href="/dist/domma/domma.css">
|
|
11
|
-
<link rel="stylesheet" href="/dist/domma/grid.css">
|
|
12
|
-
<link rel="stylesheet" href="/dist/domma/elements.css">
|
|
13
|
-
<link rel="stylesheet" href="/dist/domma/themes/domma-themes.css">
|
|
14
|
-
|
|
15
|
-
<!-- Site CSS -->
|
|
16
|
-
<link rel="stylesheet" href="/public/css/site.css">
|
|
17
|
-
<link rel="stylesheet" href="/public/css/forms.css">
|
|
18
|
-
|
|
19
|
-
<!-- Survey CSS -->
|
|
20
|
-
<link rel="stylesheet" href="/plugins/surveys/public/survey.css">
|
|
21
|
-
</head>
|
|
22
|
-
<body class="dm-cloaked">
|
|
23
|
-
|
|
24
|
-
<main class="site-main">
|
|
25
|
-
<article class="site-content">
|
|
26
|
-
<div class="container survey-container">
|
|
27
|
-
<h1 class="survey-title">{{title}}</h1>
|
|
28
|
-
|
|
29
|
-
<form id="survey-code" class="survey-code" style="display:none">
|
|
30
|
-
<label for="survey-code-input">Enter your code</label>
|
|
31
|
-
<div class="survey-code-row">
|
|
32
|
-
<input id="survey-code-input" name="code" type="text" placeholder="Enter your code" autocomplete="off">
|
|
33
|
-
<button type="submit" class="btn btn-primary">Start</button>
|
|
34
|
-
</div>
|
|
35
|
-
</form>
|
|
36
|
-
|
|
37
|
-
<div class="survey-intro">{{intro}}</div>
|
|
38
|
-
|
|
39
|
-
<div id="survey-mount" data-survey-slug="{{slug}}" data-form-slug="{{formSlug}}"></div>
|
|
40
|
-
|
|
41
|
-
<div class="survey-footer">{{footer}}</div>
|
|
42
|
-
</div>
|
|
43
|
-
</article>
|
|
44
|
-
</main>
|
|
45
|
-
|
|
46
|
-
<!-- DOMPurify - must load before DommaJS -->
|
|
47
|
-
<script src="https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.min.js"></script>
|
|
48
|
-
|
|
49
|
-
<!-- DommaJS -->
|
|
50
|
-
<script src="/dist/domma/domma.min.js"></script>
|
|
51
|
-
<script>
|
|
52
|
-
if (window.Domma && typeof window.Domma.init === 'function') {
|
|
53
|
-
window.Domma.init();
|
|
54
|
-
}
|
|
55
|
-
</script>
|
|
56
|
-
|
|
57
|
-
<!-- Core Forms (logic engine must load before renderer) -->
|
|
58
|
-
<script src="/public/js/form-logic-engine.js"></script>
|
|
59
|
-
|
|
60
|
-
<!-- Survey client -->
|
|
61
|
-
<script type="module" src="/plugins/surveys/public/survey.mjs"></script>
|
|
62
|
-
</body>
|
|
63
|
-
</html>
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
<div class="view-header">
|
|
2
|
-
<h1><span data-icon="sun"></span> Theme Switcher</h1>
|
|
3
|
-
<div>
|
|
4
|
-
<button id="save-settings-btn" class="btn btn-primary">
|
|
5
|
-
<span data-icon="save"></span> Save
|
|
6
|
-
</button>
|
|
7
|
-
</div>
|
|
8
|
-
</div>
|
|
9
|
-
|
|
10
|
-
<div class="row mb-3">
|
|
11
|
-
<div class="col">
|
|
12
|
-
<div class="card">
|
|
13
|
-
<div class="card-header"><strong>Disc Position & Offset</strong></div>
|
|
14
|
-
<div class="card-body">
|
|
15
|
-
|
|
16
|
-
<div class="row" style="align-items:flex-start;gap:0;">
|
|
17
|
-
|
|
18
|
-
<!-- Position picker -->
|
|
19
|
-
<div class="col-auto" style="margin-right:1.5rem;margin-bottom:1rem;">
|
|
20
|
-
<label class="form-label" style="margin-bottom:.6rem;">Position</label>
|
|
21
|
-
<div id="ts-pos-grid"
|
|
22
|
-
style="display:grid;grid-template-columns:1fr 1fr;gap:2px;border:1px solid var(--border-color,rgba(255,255,255,.1));border-radius:10px;overflow:hidden;background:var(--border-color,rgba(255,255,255,.07));width:fit-content;">
|
|
23
|
-
|
|
24
|
-
<div class="ts-pos-btn" data-pos="top-left"
|
|
25
|
-
style="display:flex;flex-direction:column;align-items:flex-start;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-right:1px solid var(--border-color,rgba(255,255,255,.08));border-bottom:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
|
|
26
|
-
<span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Top</span>
|
|
27
|
-
<span style="font-weight:600;">↖ Left</span>
|
|
28
|
-
</div>
|
|
29
|
-
<div class="ts-pos-btn" data-pos="top-right"
|
|
30
|
-
style="display:flex;flex-direction:column;align-items:flex-end;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-bottom:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
|
|
31
|
-
<span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Top</span>
|
|
32
|
-
<span style="font-weight:600;">Right ↗</span>
|
|
33
|
-
</div>
|
|
34
|
-
|
|
35
|
-
<div class="ts-pos-btn" data-pos="left-center"
|
|
36
|
-
style="display:flex;flex-direction:column;align-items:flex-start;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-right:1px solid var(--border-color,rgba(255,255,255,.08));border-bottom:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
|
|
37
|
-
<span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Side</span>
|
|
38
|
-
<span style="font-weight:600;">← Left</span>
|
|
39
|
-
</div>
|
|
40
|
-
<div class="ts-pos-btn" data-pos="right-center"
|
|
41
|
-
style="display:flex;flex-direction:column;align-items:flex-end;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-bottom:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
|
|
42
|
-
<span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Side</span>
|
|
43
|
-
<span style="font-weight:600;">Right →</span>
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
<div class="ts-pos-btn" data-pos="bottom-left"
|
|
47
|
-
style="display:flex;flex-direction:column;align-items:flex-start;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-right:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
|
|
48
|
-
<span style="font-weight:600;">↙ Left</span>
|
|
49
|
-
<span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Bottom</span>
|
|
50
|
-
</div>
|
|
51
|
-
<div class="ts-pos-btn" data-pos="bottom-right"
|
|
52
|
-
style="display:flex;flex-direction:column;align-items:flex-end;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
|
|
53
|
-
<span style="font-weight:600;">Right ↘</span>
|
|
54
|
-
<span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Bottom</span>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
</div>
|
|
58
|
-
<input type="hidden" id="field-position" value="right-center">
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
<!-- Offset -->
|
|
62
|
-
<div class="col-3">
|
|
63
|
-
<label class="form-label">Edge offset</label>
|
|
64
|
-
<div style="display:flex;flex-direction:column;gap:.55rem;">
|
|
65
|
-
<div style="display:flex;align-items:center;gap:.4rem;">
|
|
66
|
-
<span style="font-size:.75rem;color:var(--text-muted,#888);width:22px;">H</span>
|
|
67
|
-
<input id="field-offset-x" type="number" class="form-input" min="0" max="500"
|
|
68
|
-
placeholder="32" style="max-width:80px;">
|
|
69
|
-
<span class="text-muted" style="font-size:.8rem;">px</span>
|
|
70
|
-
</div>
|
|
71
|
-
<div style="display:flex;align-items:center;gap:.4rem;">
|
|
72
|
-
<span style="font-size:.75rem;color:var(--text-muted,#888);width:22px;">V</span>
|
|
73
|
-
<input id="field-offset-y" type="number" class="form-input" min="0" max="500"
|
|
74
|
-
placeholder="32" style="max-width:80px;">
|
|
75
|
-
<span class="text-muted" style="font-size:.8rem;">px</span>
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
<p class="form-hint text-muted" style="margin-top:.35rem;font-size:.75rem;">Distance from the
|
|
79
|
-
screen edge.</p>
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Theme Switcher - Admin Settings View
|
|
3
|
-
*/
|
|
4
|
-
import {apiRequest} from '/admin/js/api.js';
|
|
5
|
-
|
|
6
|
-
export const themeSwitcherView = {
|
|
7
|
-
templateUrl: '/plugins/theme-switcher/admin/templates/theme-switcher.html',
|
|
8
|
-
|
|
9
|
-
async onMount($container) {
|
|
10
|
-
let settings = {};
|
|
11
|
-
try {
|
|
12
|
-
settings = await apiRequest('/plugins/theme-switcher/settings');
|
|
13
|
-
} catch {
|
|
14
|
-
E.toast('Could not load settings.', {type: 'error'});
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const fieldPos = document.getElementById('field-position');
|
|
18
|
-
const fieldOffX = document.getElementById('field-offset-x');
|
|
19
|
-
const fieldOffY = document.getElementById('field-offset-y');
|
|
20
|
-
const saveBtn = document.getElementById('save-settings-btn');
|
|
21
|
-
const grid = document.getElementById('ts-pos-grid');
|
|
22
|
-
|
|
23
|
-
if (fieldPos) fieldPos.value = settings.position || 'right-center';
|
|
24
|
-
if (fieldOffX) fieldOffX.value = settings.offsetX ?? 32;
|
|
25
|
-
if (fieldOffY) fieldOffY.value = settings.offsetY ?? 32;
|
|
26
|
-
|
|
27
|
-
function updatePosBtns(pos) {
|
|
28
|
-
document.querySelectorAll('.ts-pos-btn').forEach(function (btn) {
|
|
29
|
-
const active = btn.getAttribute('data-pos') === pos;
|
|
30
|
-
btn.style.background = active ? 'var(--dm-primary, #5b8cff)' : '';
|
|
31
|
-
btn.style.color = active ? '#fff' : '';
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
updatePosBtns(settings.position || 'right-center');
|
|
36
|
-
|
|
37
|
-
if (grid) {
|
|
38
|
-
grid.addEventListener('click', function (e) {
|
|
39
|
-
const btn = e.target.closest('[data-pos]');
|
|
40
|
-
if (!btn) return;
|
|
41
|
-
const pos = btn.getAttribute('data-pos');
|
|
42
|
-
if (fieldPos) fieldPos.value = pos;
|
|
43
|
-
updatePosBtns(pos);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (saveBtn) {
|
|
48
|
-
saveBtn.addEventListener('click', async function () {
|
|
49
|
-
const data = {
|
|
50
|
-
position: fieldPos ? fieldPos.value : 'right-center',
|
|
51
|
-
offsetX: fieldOffX ? (parseInt(fieldOffX.value, 10) || 32) : 32,
|
|
52
|
-
offsetY: fieldOffY ? (parseInt(fieldOffY.value, 10) || 32) : 32,
|
|
53
|
-
};
|
|
54
|
-
try {
|
|
55
|
-
await apiRequest('/plugins/theme-switcher/settings', {method: 'PUT', body: JSON.stringify(data)});
|
|
56
|
-
E.toast('Settings saved.', {type: 'success'});
|
|
57
|
-
} catch {
|
|
58
|
-
E.toast('Failed to save settings.', {type: 'error'});
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
Domma.icons.scan();
|
|
64
|
-
}
|
|
65
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
themes: [
|
|
3
|
-
'ocean', 'forest', 'sunset', 'royal', 'lemon', 'silver',
|
|
4
|
-
'charcoal', 'christmas', 'unicorn', 'dreamy', 'grayve', 'mint', 'wedding'
|
|
5
|
-
],
|
|
6
|
-
// position: 'right-center' | 'left-center' | 'bottom-right' | 'bottom-left' | 'top-left' | 'top-right'
|
|
7
|
-
position: 'right-center',
|
|
8
|
-
offsetX: 32,
|
|
9
|
-
offsetY: 32,
|
|
10
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Theme Switcher Plugin
|
|
3
|
-
*
|
|
4
|
-
* Exposes a public GET /settings endpoint so the injected client script
|
|
5
|
-
* can read position/offset without requiring auth.
|
|
6
|
-
*/
|
|
7
|
-
import {getPluginSettings, savePluginState} from '../../server/services/plugins.js';
|
|
8
|
-
|
|
9
|
-
export default async function themeSwitcherPlugin(fastify, options) {
|
|
10
|
-
const {authenticate, requireAdmin} = options.auth;
|
|
11
|
-
|
|
12
|
-
fastify.get('/settings', async () => {
|
|
13
|
-
const s = await getPluginSettings('theme-switcher');
|
|
14
|
-
return {
|
|
15
|
-
position: s.position || 'right-center',
|
|
16
|
-
offsetX: s.offsetX ?? 32,
|
|
17
|
-
offsetY: s.offsetY ?? 32,
|
|
18
|
-
};
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
fastify.put('/settings', {preHandler: [authenticate, requireAdmin]}, async (request) => {
|
|
22
|
-
const body = request.body || {};
|
|
23
|
-
savePluginState('theme-switcher', {settings: body});
|
|
24
|
-
return {ok: true};
|
|
25
|
-
});
|
|
26
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "theme-switcher",
|
|
3
|
-
"displayName": "Theme Switcher",
|
|
4
|
-
"version": "1.1.0",
|
|
5
|
-
"description": "Floating disc icon with colour theme dots for switching between all Domma themes",
|
|
6
|
-
"author": "Domma",
|
|
7
|
-
"date": "2026-03-31",
|
|
8
|
-
"icon": "sun",
|
|
9
|
-
"inject": {
|
|
10
|
-
"head": "public/inject-head.html",
|
|
11
|
-
"bodyEnd": "public/inject-body.html"
|
|
12
|
-
},
|
|
13
|
-
"admin": {
|
|
14
|
-
"sidebar": [
|
|
15
|
-
{
|
|
16
|
-
"id": "plugin-theme-switcher",
|
|
17
|
-
"text": "Theme Switcher",
|
|
18
|
-
"icon": "sun",
|
|
19
|
-
"url": "#/plugins/theme-switcher",
|
|
20
|
-
"section": "#/plugins/theme-switcher",
|
|
21
|
-
"permissions": [
|
|
22
|
-
"plugins"
|
|
23
|
-
]
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
|
-
"routes": [
|
|
27
|
-
{
|
|
28
|
-
"path": "/plugins/theme-switcher",
|
|
29
|
-
"view": "pluginThemeSwitcher",
|
|
30
|
-
"title": "Theme Switcher - Domma CMS"
|
|
31
|
-
}
|
|
32
|
-
],
|
|
33
|
-
"views": {
|
|
34
|
-
"pluginThemeSwitcher": {
|
|
35
|
-
"entry": "theme-switcher/admin/views/theme-switcher.js?v=c906bed1",
|
|
36
|
-
"exportName": "themeSwitcherView"
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
"settingsSchema": [
|
|
41
|
-
{
|
|
42
|
-
"key": "position",
|
|
43
|
-
"type": "select",
|
|
44
|
-
"label": "Position",
|
|
45
|
-
"default": "right-center",
|
|
46
|
-
"options": [
|
|
47
|
-
{
|
|
48
|
-
"value": "right-center",
|
|
49
|
-
"label": "Right - Centre"
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"value": "left-center",
|
|
53
|
-
"label": "Left - Centre"
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"value": "bottom-right",
|
|
57
|
-
"label": "Bottom Right"
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"value": "bottom-left",
|
|
61
|
-
"label": "Bottom Left"
|
|
62
|
-
}
|
|
63
|
-
]
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
"key": "offset",
|
|
67
|
-
"type": "number",
|
|
68
|
-
"label": "Offset (px)",
|
|
69
|
-
"default": 32,
|
|
70
|
-
"min": 0,
|
|
71
|
-
"max": 200
|
|
72
|
-
}
|
|
73
|
-
]
|
|
74
|
-
}
|