domma-cms 0.1.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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +469 -0
  3. package/admin/css/admin.css +1123 -0
  4. package/admin/index.html +72 -0
  5. package/admin/js/api.js +210 -0
  6. package/admin/js/app.js +270 -0
  7. package/admin/js/config/sidebar-config.js +107 -0
  8. package/admin/js/lib/card.js +63 -0
  9. package/admin/js/lib/image-editor.js +869 -0
  10. package/admin/js/lib/markdown-toolbar.js +421 -0
  11. package/admin/js/templates/dashboard.html +50 -0
  12. package/admin/js/templates/documentation.html +237 -0
  13. package/admin/js/templates/layouts.html +11 -0
  14. package/admin/js/templates/login.html +58 -0
  15. package/admin/js/templates/media.html +16 -0
  16. package/admin/js/templates/navigation.html +50 -0
  17. package/admin/js/templates/page-editor.html +126 -0
  18. package/admin/js/templates/pages.html +18 -0
  19. package/admin/js/templates/plugins.html +12 -0
  20. package/admin/js/templates/settings.html +190 -0
  21. package/admin/js/templates/tutorials.html +233 -0
  22. package/admin/js/templates/user-editor.html +12 -0
  23. package/admin/js/templates/users.html +10 -0
  24. package/admin/js/views/dashboard.js +48 -0
  25. package/admin/js/views/documentation.js +12 -0
  26. package/admin/js/views/index.js +33 -0
  27. package/admin/js/views/layouts.js +49 -0
  28. package/admin/js/views/login.js +254 -0
  29. package/admin/js/views/media.js +240 -0
  30. package/admin/js/views/navigation.js +152 -0
  31. package/admin/js/views/page-editor.js +479 -0
  32. package/admin/js/views/pages.js +64 -0
  33. package/admin/js/views/plugins.js +100 -0
  34. package/admin/js/views/settings.js +64 -0
  35. package/admin/js/views/tutorials.js +12 -0
  36. package/admin/js/views/user-editor.js +88 -0
  37. package/admin/js/views/users.js +73 -0
  38. package/bin/cli.js +334 -0
  39. package/config/auth.json +20 -0
  40. package/config/content.json +10 -0
  41. package/config/navigation.json +63 -0
  42. package/config/plugins.json +47 -0
  43. package/config/presets.json +34 -0
  44. package/config/server.json +6 -0
  45. package/config/site.json +33 -0
  46. package/package.json +67 -0
  47. package/plugins/back-to-top/admin/templates/back-to-top-settings.html +55 -0
  48. package/plugins/back-to-top/admin/views/back-to-top-settings.js +44 -0
  49. package/plugins/back-to-top/config.js +10 -0
  50. package/plugins/back-to-top/plugin.js +24 -0
  51. package/plugins/back-to-top/plugin.json +36 -0
  52. package/plugins/back-to-top/public/inject-body.html +105 -0
  53. package/plugins/cookie-consent/admin/templates/cookie-consent-settings.html +113 -0
  54. package/plugins/cookie-consent/admin/views/cookie-consent-settings.js +73 -0
  55. package/plugins/cookie-consent/config.js +30 -0
  56. package/plugins/cookie-consent/plugin.js +24 -0
  57. package/plugins/cookie-consent/plugin.json +36 -0
  58. package/plugins/cookie-consent/public/inject-body.html +69 -0
  59. package/plugins/custom-css/admin/templates/custom-css.html +17 -0
  60. package/plugins/custom-css/admin/views/custom-css.js +35 -0
  61. package/plugins/custom-css/config.js +1 -0
  62. package/plugins/custom-css/data/custom.css +0 -0
  63. package/plugins/custom-css/plugin.js +63 -0
  64. package/plugins/custom-css/plugin.json +32 -0
  65. package/plugins/custom-css/public/inject-head.html +1 -0
  66. package/plugins/domma-effects/admin/templates/domma-effects.html +488 -0
  67. package/plugins/domma-effects/admin/views/domma-effects.js +56 -0
  68. package/plugins/domma-effects/config.js +9 -0
  69. package/plugins/domma-effects/plugin.js +22 -0
  70. package/plugins/domma-effects/plugin.json +36 -0
  71. package/plugins/domma-effects/public/celebrations/core/canvas.js +111 -0
  72. package/plugins/domma-effects/public/celebrations/core/particles.js +144 -0
  73. package/plugins/domma-effects/public/celebrations/core/physics.js +166 -0
  74. package/plugins/domma-effects/public/celebrations/index.js +535 -0
  75. package/plugins/domma-effects/public/celebrations/themes/christmas.js +1805 -0
  76. package/plugins/domma-effects/public/celebrations/themes/guy-fawkes.js +1477 -0
  77. package/plugins/domma-effects/public/celebrations/themes/halloween.js +1837 -0
  78. package/plugins/domma-effects/public/celebrations/themes/st-andrews.js +1175 -0
  79. package/plugins/domma-effects/public/celebrations/themes/st-davids.js +1258 -0
  80. package/plugins/domma-effects/public/celebrations/themes/st-georges.js +1754 -0
  81. package/plugins/domma-effects/public/celebrations/themes/st-patricks.js +1290 -0
  82. package/plugins/domma-effects/public/celebrations/themes/valentines.js +1361 -0
  83. package/plugins/domma-effects/public/inject-body.html +268 -0
  84. package/plugins/example-analytics/admin/templates/analytics.html +10 -0
  85. package/plugins/example-analytics/admin/views/analytics.js +51 -0
  86. package/plugins/example-analytics/config.js +6 -0
  87. package/plugins/example-analytics/plugin.js +58 -0
  88. package/plugins/example-analytics/plugin.json +27 -0
  89. package/plugins/example-analytics/public/inject-body.html +13 -0
  90. package/plugins/example-analytics/public/inject-head.html +1 -0
  91. package/plugins/example-analytics/stats.json +1 -0
  92. package/plugins/form-builder/admin/templates/form-editor.html +158 -0
  93. package/plugins/form-builder/admin/templates/form-settings.html +29 -0
  94. package/plugins/form-builder/admin/templates/form-submissions.html +30 -0
  95. package/plugins/form-builder/admin/templates/forms-list.html +17 -0
  96. package/plugins/form-builder/admin/views/form-editor.js +817 -0
  97. package/plugins/form-builder/admin/views/form-settings.js +38 -0
  98. package/plugins/form-builder/admin/views/form-submissions.js +295 -0
  99. package/plugins/form-builder/admin/views/forms-list.js +164 -0
  100. package/plugins/form-builder/config.js +9 -0
  101. package/plugins/form-builder/data/forms/contact-details.json +63 -0
  102. package/plugins/form-builder/data/forms/contact.json +52 -0
  103. package/plugins/form-builder/data/submissions/contact-details.json +1 -0
  104. package/plugins/form-builder/data/submissions/contact.json +14 -0
  105. package/plugins/form-builder/email.js +103 -0
  106. package/plugins/form-builder/plugin.js +454 -0
  107. package/plugins/form-builder/plugin.json +56 -0
  108. package/plugins/form-builder/public/inject-body.html +270 -0
  109. package/plugins/form-builder/public/inject-head.html +42 -0
  110. package/public/css/site.css +189 -0
  111. package/public/js/site.js +109 -0
  112. package/scripts/copy-domma.js +48 -0
  113. package/scripts/fresh.js +41 -0
  114. package/scripts/reset.js +124 -0
  115. package/scripts/seed.js +666 -0
  116. package/scripts/setup.js +263 -0
  117. package/server/config.js +56 -0
  118. package/server/middleware/auth.js +97 -0
  119. package/server/routes/api/auth.js +116 -0
  120. package/server/routes/api/layouts.js +25 -0
  121. package/server/routes/api/media.js +93 -0
  122. package/server/routes/api/navigation.js +37 -0
  123. package/server/routes/api/pages.js +118 -0
  124. package/server/routes/api/plugins.js +46 -0
  125. package/server/routes/api/settings.js +25 -0
  126. package/server/routes/api/users.js +110 -0
  127. package/server/routes/public.js +108 -0
  128. package/server/server.js +169 -0
  129. package/server/services/content.js +298 -0
  130. package/server/services/images.js +334 -0
  131. package/server/services/markdown.js +297 -0
  132. package/server/services/plugins.js +246 -0
  133. package/server/services/renderer.js +80 -0
  134. package/server/services/users.js +212 -0
  135. package/server/templates/page.html +78 -0
@@ -0,0 +1,47 @@
1
+ {
2
+ "example-analytics": {
3
+ "enabled": true,
4
+ "settings": {}
5
+ },
6
+ "form-builder": {
7
+ "enabled": true,
8
+ "settings": {
9
+ "smtp": {
10
+ "host": "localhost",
11
+ "port": 1025,
12
+ "secure": false,
13
+ "user": "",
14
+ "pass": ""
15
+ },
16
+ "fromAddress": "noreply@example.com",
17
+ "fromName": "Website Forms"
18
+ }
19
+ },
20
+ "back-to-top": {
21
+ "enabled": true,
22
+ "settings": {
23
+ "scrollThreshold": 150,
24
+ "position": "bottom-right",
25
+ "label": "",
26
+ "smooth": true,
27
+ "offset": 32
28
+ }
29
+ },
30
+ "cookie-consent": {
31
+ "enabled": true,
32
+ "settings": {}
33
+ },
34
+ "custom-css": {
35
+ "enabled": true,
36
+ "settings": {}
37
+ },
38
+ "domma-effects": {
39
+ "enabled": true,
40
+ "settings": {
41
+ "respectMotion": false,
42
+ "defaultDuration": 600,
43
+ "defaultAnimation": "fade",
44
+ "defaultThreshold": 0.1
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "default": {
3
+ "label": "Default",
4
+ "description": "Standard page with navbar and footer",
5
+ "navbar": true,
6
+ "footer": true,
7
+ "sidebar": false,
8
+ "features": ["back-to-top", "icon-scan"]
9
+ },
10
+ "with-sidebar": {
11
+ "label": "With Sidebar",
12
+ "description": "Page with navbar, sidebar, and footer",
13
+ "navbar": true,
14
+ "footer": true,
15
+ "sidebar": true,
16
+ "features": ["back-to-top", "icon-scan", "scroll-spy"]
17
+ },
18
+ "minimal": {
19
+ "label": "Minimal",
20
+ "description": "Clean page with no navbar or footer",
21
+ "navbar": false,
22
+ "footer": false,
23
+ "sidebar": false,
24
+ "features": []
25
+ },
26
+ "landing": {
27
+ "label": "Landing Page",
28
+ "description": "Full-width landing page layout",
29
+ "navbar": true,
30
+ "footer": true,
31
+ "sidebar": false,
32
+ "features": ["back-to-top", "icon-scan"]
33
+ }
34
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "port": 80,
3
+ "host": "0.0.0.0",
4
+ "cors": { "origin": true },
5
+ "uploads": { "maxFileSize": 10485760 }
6
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "title": "My Boss Site",
3
+ "tagline": "My Dead Good Boss Site",
4
+ "theme": "silver-dark",
5
+ "adminTheme": "charcoal-dark",
6
+ "seo": {
7
+ "defaultTitle": "My Boss Site",
8
+ "titleSeparator": " | ",
9
+ "defaultDescription": "A site built with Domma CMS"
10
+ },
11
+ "footer": {
12
+ "copyright": "© 2026 DCBW Consulting Ltd.",
13
+ "links": [
14
+ {
15
+ "text": "Privacy Policy",
16
+ "url": "/privacy"
17
+ },
18
+ {
19
+ "text": "Contact",
20
+ "url": "/contact"
21
+ }
22
+ ]
23
+ },
24
+ "smtp": {
25
+ "host": "localhost",
26
+ "port": 1025,
27
+ "user": "",
28
+ "pass": "",
29
+ "secure": false,
30
+ "fromAddress": "",
31
+ "fromName": ""
32
+ }
33
+ }
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "domma-cms",
3
+ "version": "0.1.0",
4
+ "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
+ "type": "module",
6
+ "main": "server/server.js",
7
+ "bin": {
8
+ "domma-cms": "bin/cli.js"
9
+ },
10
+ "files": [
11
+ "bin/",
12
+ "server/",
13
+ "admin/",
14
+ "public/",
15
+ "config/",
16
+ "plugins/",
17
+ "scripts/"
18
+ ],
19
+ "scripts": {
20
+ "start": "pm2 start server/server.js -i max --name domma-cms",
21
+ "dev": "PORT=3050 node --watch server/server.js",
22
+ "prod": "node server/server.js",
23
+ "setup": "node scripts/setup.js",
24
+ "reset": "node scripts/reset.js",
25
+ "seed": "node scripts/seed.js",
26
+ "fresh": "node scripts/fresh.js",
27
+ "copy-domma": "node scripts/copy-domma.js"
28
+ },
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "keywords": [
33
+ "cms",
34
+ "file-based",
35
+ "markdown",
36
+ "fastify",
37
+ "domma",
38
+ "flat-file",
39
+ "no-database"
40
+ ],
41
+ "author": "Darryl Waterhouse",
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/pinpointzero73/domma-cms.git"
46
+ },
47
+ "homepage": "https://github.com/pinpointzero73/domma-cms#readme",
48
+ "bugs": {
49
+ "url": "https://github.com/pinpointzero73/domma-cms/issues"
50
+ },
51
+ "dependencies": {
52
+ "@fastify/cors": "^11.2.0",
53
+ "@fastify/jwt": "^10.0.0",
54
+ "@fastify/multipart": "^9.3.0",
55
+ "@fastify/static": "^8.1.0",
56
+ "bcryptjs": "^3.0.3",
57
+ "domma-js": "^0.18.2",
58
+ "dotenv": "^17.2.3",
59
+ "fastify": "^5.7.3",
60
+ "gray-matter": "^4.0.3",
61
+ "marked": "^15.0.0",
62
+ "nodemailer": "^8.0.1",
63
+ "sanitize-html": "^2.17.0",
64
+ "sharp": "^0.34.5",
65
+ "uuid": "^13.0.0"
66
+ }
67
+ }
@@ -0,0 +1,55 @@
1
+ <div class="view-header">
2
+ <h1><span data-icon="arrow-up"></span> Back to Top</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="card mb-4">
11
+ <div class="card-header"><h2>Appearance</h2></div>
12
+ <div class="card-body">
13
+ <div class="row mb-3">
14
+ <div class="col-6">
15
+ <label class="form-label">Position</label>
16
+ <select id="field-position" class="form-select">
17
+ <option value="bottom-right">Bottom right</option>
18
+ <option value="bottom-left">Bottom left</option>
19
+ </select>
20
+ </div>
21
+ <div class="col-6">
22
+ <label class="form-label">Edge offset (px)</label>
23
+ <input id="field-offset" type="number" class="form-input" min="0" max="200" placeholder="32">
24
+ </div>
25
+ </div>
26
+ <div class="row">
27
+ <div class="col">
28
+ <label class="form-label">Button label <span class="text-muted">(optional text beside icon)</span></label>
29
+ <input id="field-label" type="text" class="form-input" placeholder="Leave empty for icon only">
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+
35
+ <div class="card mb-4">
36
+ <div class="card-header"><h2>Behaviour</h2></div>
37
+ <div class="card-body">
38
+ <div class="row mb-3">
39
+ <div class="col-6">
40
+ <label class="form-label">Scroll threshold (px)</label>
41
+ <input id="field-threshold" type="number" class="form-input" min="0" max="9999" placeholder="300">
42
+ <span class="form-hint">Button appears after scrolling this far down.</span>
43
+ </div>
44
+ </div>
45
+ <div class="row">
46
+ <div class="col">
47
+ <label class="form-check-label">
48
+ <input id="field-smooth" type="checkbox">
49
+ Smooth scroll
50
+ </label>
51
+ <span class="form-hint">Animates scroll to top instead of jumping instantly.</span>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Back to Top Plugin — Admin Settings View
3
+ */
4
+ import {apiRequest} from '/admin/js/api.js';
5
+
6
+ export const backToTopSettingsView = {
7
+ templateUrl: '/plugins/back-to-top/admin/templates/back-to-top-settings.html',
8
+
9
+ async onMount($container) {
10
+ let settings = {};
11
+ try {
12
+ settings = await apiRequest('/plugins/back-to-top/settings');
13
+ } catch {
14
+ E.toast('Could not load settings.', {type: 'error'});
15
+ }
16
+
17
+ $container.find('#field-threshold').val(settings.scrollThreshold ?? 300);
18
+ $container.find('#field-position').val(settings.position || 'bottom-right');
19
+ $container.find('#field-label').val(settings.label || '');
20
+ $container.find('#field-smooth').prop('checked', settings.smooth !== false);
21
+ $container.find('#field-offset').val(settings.offset ?? 32);
22
+
23
+ $container.find('#save-settings-btn').off('click').on('click', async () => {
24
+ const data = {
25
+ scrollThreshold: parseInt($container.find('#field-threshold').val(), 10) || 300,
26
+ position: $container.find('#field-position').val(),
27
+ label: $container.find('#field-label').val().trim(),
28
+ smooth: $container.find('#field-smooth').prop('checked'),
29
+ offset: parseInt($container.find('#field-offset').val(), 10) || 32
30
+ };
31
+ try {
32
+ await apiRequest('/plugins/back-to-top/settings', {
33
+ method: 'PUT',
34
+ body: JSON.stringify(data)
35
+ });
36
+ E.toast('Settings saved.', {type: 'success'});
37
+ } catch {
38
+ E.toast('Failed to save settings.', {type: 'error'});
39
+ }
40
+ });
41
+
42
+ Domma.icons.scan();
43
+ }
44
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Back to Top Plugin — Default Configuration
3
+ */
4
+ export default {
5
+ scrollThreshold: 300,
6
+ position: 'bottom-right',
7
+ label: '',
8
+ smooth: true,
9
+ offset: 32
10
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Back to Top Plugin — Server
3
+ *
4
+ * Endpoints (prefix: /api/plugins/back-to-top):
5
+ * GET /settings — public (called from public site IIFE)
6
+ * PUT /settings — admin-auth — saves user overrides
7
+ */
8
+ import {getPluginSettings, savePluginState} from '../../server/services/plugins.js';
9
+
10
+ export default async function backToTopPlugin(fastify, options) {
11
+ const {authenticate, requireAdmin} = options.auth;
12
+
13
+ // GET /settings — no auth, public read (returns non-sensitive display config)
14
+ fastify.get('/settings', async () => {
15
+ return getPluginSettings('back-to-top');
16
+ });
17
+
18
+ // PUT /settings — admin only
19
+ fastify.put('/settings', {preHandler: [authenticate, requireAdmin]}, async (request) => {
20
+ const body = request.body || {};
21
+ savePluginState('back-to-top', {settings: body});
22
+ return {ok: true};
23
+ });
24
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "back-to-top",
3
+ "displayName": "Back to Top",
4
+ "version": "1.0.0",
5
+ "description": "Injects a configurable scroll-to-top button into every public page.",
6
+ "author": "Darryl Waterhouse",
7
+ "date": "2026-03-02",
8
+ "icon": "arrow-up",
9
+ "admin": {
10
+ "sidebar": [
11
+ {
12
+ "id": "back-to-top",
13
+ "text": "Back to Top",
14
+ "icon": "arrow-up",
15
+ "url": "#/plugins/back-to-top",
16
+ "section": "#/plugins/back-to-top"
17
+ }
18
+ ],
19
+ "routes": [
20
+ {
21
+ "path": "/plugins/back-to-top",
22
+ "view": "plugin-back-to-top-settings",
23
+ "title": "Back to Top - Domma CMS"
24
+ }
25
+ ],
26
+ "views": {
27
+ "plugin-back-to-top-settings": {
28
+ "entry": "back-to-top/admin/views/back-to-top-settings.js",
29
+ "exportName": "backToTopSettingsView"
30
+ }
31
+ }
32
+ },
33
+ "inject": {
34
+ "bodyEnd": "public/inject-body.html"
35
+ }
36
+ }
@@ -0,0 +1,105 @@
1
+ <script>
2
+ (function () {
3
+ 'use strict';
4
+
5
+ var BTN_ID = 'back-to-top-btn';
6
+
7
+ fetch('/api/plugins/back-to-top/settings')
8
+ .then(function (r) {
9
+ return r.json();
10
+ })
11
+ .then(function (cfg) {
12
+ var threshold = cfg.scrollThreshold || 300;
13
+ var position = cfg.position || 'bottom-right';
14
+ var label = cfg.label || '';
15
+ var smooth = cfg.smooth !== false;
16
+ var offset = cfg.offset || 32;
17
+
18
+ var btn = document.createElement('button');
19
+ btn.id = BTN_ID;
20
+ btn.type = 'button';
21
+ btn.setAttribute('aria-label', 'Back to top');
22
+ btn.setAttribute('title', 'Back to top');
23
+
24
+ // Inline SVG — arrow-up (Domma icon set path)
25
+ var svgNS = 'http://www.w3.org/2000/svg';
26
+ var svg = document.createElementNS(svgNS, 'svg');
27
+ svg.setAttribute('viewBox', '0 0 24 24');
28
+ svg.setAttribute('width', '20');
29
+ svg.setAttribute('height', '20');
30
+ svg.setAttribute('fill', 'none');
31
+ svg.setAttribute('stroke', 'currentColor');
32
+ svg.setAttribute('stroke-width', '2');
33
+ svg.setAttribute('stroke-linecap', 'round');
34
+ svg.setAttribute('stroke-linejoin', 'round');
35
+ var path = document.createElementNS(svgNS, 'path');
36
+ path.setAttribute('d', 'M12 19V5M5 12l7-7 7 7');
37
+ svg.appendChild(path);
38
+ btn.appendChild(svg);
39
+
40
+ if (label) {
41
+ var span = document.createElement('span');
42
+ span.textContent = label;
43
+ span.style.marginLeft = '6px';
44
+ btn.appendChild(span);
45
+ }
46
+
47
+ // Inline styles — self-contained, no site.css dependency
48
+ var isRight = position !== 'bottom-left';
49
+ Object.assign(btn.style, {
50
+ position: 'fixed',
51
+ bottom: offset + 'px',
52
+ right: isRight ? offset + 'px' : 'auto',
53
+ left: isRight ? 'auto' : offset + 'px',
54
+ zIndex: '9999',
55
+ display: 'flex',
56
+ alignItems: 'center',
57
+ padding: '10px',
58
+ borderRadius: '50%',
59
+ border: 'none',
60
+ cursor: 'pointer',
61
+ background: 'rgba(0,0,0,0.5)',
62
+ color: '#fff',
63
+ backdropFilter: 'blur(4px)',
64
+ opacity: '0',
65
+ transform: 'translateY(12px)',
66
+ transition: 'opacity .25s, transform .25s',
67
+ pointerEvents: 'none'
68
+ });
69
+
70
+ if (label) {
71
+ btn.style.borderRadius = '999px';
72
+ btn.style.padding = '10px 16px';
73
+ }
74
+
75
+ document.body.appendChild(btn);
76
+
77
+ function show() {
78
+ btn.style.opacity = '1';
79
+ btn.style.transform = 'translateY(0)';
80
+ btn.style.pointerEvents = 'auto';
81
+ }
82
+
83
+ function hide() {
84
+ btn.style.opacity = '0';
85
+ btn.style.transform = 'translateY(12px)';
86
+ btn.style.pointerEvents = 'none';
87
+ }
88
+
89
+ window.addEventListener('scroll', function () {
90
+ if (window.scrollY > threshold) {
91
+ show();
92
+ } else {
93
+ hide();
94
+ }
95
+ }, {passive: true});
96
+
97
+ btn.addEventListener('click', function () {
98
+ window.scrollTo({top: 0, behavior: smooth ? 'smooth' : 'auto'});
99
+ });
100
+ })
101
+ .catch(function () {
102
+ // Settings fetch failed — don't inject button
103
+ });
104
+ })();
105
+ </script>
@@ -0,0 +1,113 @@
1
+ <div class="view-header">
2
+ <h1><span data-icon="shield"></span> Cookie Consent</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="card mb-4">
11
+ <div class="card-header"><h2>Banner Wording</h2></div>
12
+ <div class="card-body">
13
+ <div class="row mb-3">
14
+ <div class="col">
15
+ <label class="form-label">Message</label>
16
+ <textarea id="field-message" class="form-input" rows="3"></textarea>
17
+ </div>
18
+ </div>
19
+ <div class="row mb-3">
20
+ <div class="col-6">
21
+ <label class="form-label">Accept All button</label>
22
+ <input id="field-accept-all-text" type="text" class="form-input">
23
+ </div>
24
+ <div class="col-6">
25
+ <label class="form-label">Reject All button</label>
26
+ <input id="field-reject-all-text" type="text" class="form-input">
27
+ </div>
28
+ </div>
29
+ <div class="row mb-3">
30
+ <div class="col-6">
31
+ <label class="form-label">Customize button</label>
32
+ <input id="field-customize-text" type="text" class="form-input">
33
+ </div>
34
+ <div class="col-6">
35
+ <label class="form-label">Save Preferences button</label>
36
+ <input id="field-save-preferences-text" type="text" class="form-input">
37
+ </div>
38
+ </div>
39
+ <div class="row mb-3">
40
+ <div class="col-6">
41
+ <label class="form-label">Privacy Policy link text</label>
42
+ <input id="field-privacy-policy-text" type="text" class="form-input">
43
+ </div>
44
+ <div class="col-6">
45
+ <label class="form-label">Privacy Policy URL</label>
46
+ <input id="field-privacy-policy-url" type="text" class="form-input" placeholder="/privacy-policy">
47
+ </div>
48
+ </div>
49
+ <div class="row">
50
+ <div class="col-6">
51
+ <label class="form-label">Cookie Policy link text</label>
52
+ <input id="field-cookie-policy-text" type="text" class="form-input">
53
+ </div>
54
+ <div class="col-6">
55
+ <label class="form-label">Cookie Policy URL <span class="text-muted">(leave empty to hide)</span></label>
56
+ <input id="field-cookie-policy-url" type="text" class="form-input" placeholder="/cookie-policy">
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="card mb-4">
63
+ <div class="card-header"><h2>Appearance &amp; Categories</h2></div>
64
+ <div class="card-body">
65
+ <div class="row mb-3">
66
+ <div class="col-4">
67
+ <label class="form-label">Position</label>
68
+ <select id="field-position" class="form-select">
69
+ <option value="bottom">Bottom</option>
70
+ <option value="top">Top</option>
71
+ </select>
72
+ </div>
73
+ <div class="col-4">
74
+ <label class="form-label">Layout</label>
75
+ <select id="field-layout" class="form-select">
76
+ <option value="bar">Bar</option>
77
+ <option value="modal">Modal</option>
78
+ </select>
79
+ </div>
80
+ <div class="col-4">
81
+ <label class="form-label">Theme</label>
82
+ <select id="field-theme" class="form-select">
83
+ <option value="dark">Dark</option>
84
+ <option value="light">Light</option>
85
+ </select>
86
+ </div>
87
+ </div>
88
+ <div class="row mb-3">
89
+ <div class="col">
90
+ <label class="form-label">Cookie categories shown in Customize panel</label>
91
+ <span class="form-hint">Necessary cookies are always shown and always required.</span>
92
+ <div style="margin-top:.75rem;display:flex;flex-direction:column;gap:.5rem;">
93
+ <label class="form-check-label">
94
+ <input id="field-show-functional" type="checkbox"> Functional Cookies
95
+ </label>
96
+ <label class="form-check-label">
97
+ <input id="field-show-analytics" type="checkbox"> Analytics Cookies
98
+ </label>
99
+ <label class="form-check-label">
100
+ <input id="field-show-marketing" type="checkbox"> Marketing Cookies
101
+ </label>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ <div class="row">
106
+ <div class="col-4">
107
+ <label class="form-label">Consent version</label>
108
+ <input id="field-consent-version" type="text" class="form-input" placeholder="1.0">
109
+ <span class="form-hint">Increment to re-prompt users who already accepted.</span>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </div>
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Cookie Consent Plugin — Admin Settings View
3
+ */
4
+ import {apiRequest} from '/admin/js/api.js';
5
+
6
+ export const cookieConsentSettingsView = {
7
+ templateUrl: '/plugins/cookie-consent/admin/templates/cookie-consent-settings.html',
8
+
9
+ async onMount($container) {
10
+ let settings = {};
11
+ try {
12
+ settings = await apiRequest('/plugins/cookie-consent/settings');
13
+ } catch {
14
+ E.toast('Could not load settings.', {type: 'error'});
15
+ }
16
+
17
+ // Wording
18
+ $container.find('#field-message').val(settings.message || '');
19
+ $container.find('#field-accept-all-text').val(settings.acceptAllText || '');
20
+ $container.find('#field-reject-all-text').val(settings.rejectAllText || '');
21
+ $container.find('#field-customize-text').val(settings.customizeText || '');
22
+ $container.find('#field-save-preferences-text').val(settings.savePreferencesText || '');
23
+ $container.find('#field-privacy-policy-text').val(settings.privacyPolicyText || '');
24
+ $container.find('#field-privacy-policy-url').val(settings.privacyPolicyUrl || '');
25
+ $container.find('#field-cookie-policy-text').val(settings.cookiePolicyText || '');
26
+ $container.find('#field-cookie-policy-url').val(settings.cookiePolicyUrl || '');
27
+
28
+ // Appearance
29
+ $container.find('#field-position').val(settings.position || 'bottom');
30
+ $container.find('#field-layout').val(settings.layout || 'bar');
31
+ $container.find('#field-theme').val(settings.theme || 'dark');
32
+
33
+ // Categories
34
+ $container.find('#field-show-functional').prop('checked', settings.showFunctional !== false);
35
+ $container.find('#field-show-analytics').prop('checked', settings.showAnalytics !== false);
36
+ $container.find('#field-show-marketing').prop('checked', settings.showMarketing !== false);
37
+
38
+ // Version
39
+ $container.find('#field-consent-version').val(settings.consentVersion || '1.0');
40
+
41
+ $container.find('#save-settings-btn').off('click').on('click', async () => {
42
+ const data = {
43
+ message: $container.find('#field-message').val().trim(),
44
+ acceptAllText: $container.find('#field-accept-all-text').val().trim(),
45
+ rejectAllText: $container.find('#field-reject-all-text').val().trim(),
46
+ customizeText: $container.find('#field-customize-text').val().trim(),
47
+ savePreferencesText: $container.find('#field-save-preferences-text').val().trim(),
48
+ privacyPolicyText: $container.find('#field-privacy-policy-text').val().trim(),
49
+ privacyPolicyUrl: $container.find('#field-privacy-policy-url').val().trim(),
50
+ cookiePolicyText: $container.find('#field-cookie-policy-text').val().trim(),
51
+ cookiePolicyUrl: $container.find('#field-cookie-policy-url').val().trim(),
52
+ position: $container.find('#field-position').val(),
53
+ layout: $container.find('#field-layout').val(),
54
+ theme: $container.find('#field-theme').val(),
55
+ showFunctional: $container.find('#field-show-functional').prop('checked'),
56
+ showAnalytics: $container.find('#field-show-analytics').prop('checked'),
57
+ showMarketing: $container.find('#field-show-marketing').prop('checked'),
58
+ consentVersion: $container.find('#field-consent-version').val().trim() || '1.0'
59
+ };
60
+ try {
61
+ await apiRequest('/plugins/cookie-consent/settings', {
62
+ method: 'PUT',
63
+ body: JSON.stringify(data)
64
+ });
65
+ E.toast('Settings saved.', {type: 'success'});
66
+ } catch {
67
+ E.toast('Failed to save settings.', {type: 'error'});
68
+ }
69
+ });
70
+
71
+ Domma.icons.scan();
72
+ }
73
+ };