domma-cms 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/admin/css/admin.css +78 -1
  2. package/admin/js/api.js +32 -0
  3. package/admin/js/app.js +24 -7
  4. package/admin/js/config/sidebar-config.js +8 -0
  5. package/admin/js/templates/collection-editor.html +80 -0
  6. package/admin/js/templates/collection-entries.html +36 -0
  7. package/admin/js/templates/collections.html +12 -0
  8. package/admin/js/templates/documentation.html +136 -0
  9. package/admin/js/templates/navigation.html +26 -4
  10. package/admin/js/templates/page-editor.html +91 -85
  11. package/admin/js/templates/settings.html +433 -172
  12. package/admin/js/views/collection-editor.js +487 -0
  13. package/admin/js/views/collection-entries.js +484 -0
  14. package/admin/js/views/collections.js +153 -0
  15. package/admin/js/views/dashboard.js +14 -6
  16. package/admin/js/views/index.js +9 -3
  17. package/admin/js/views/login.js +3 -2
  18. package/admin/js/views/navigation.js +77 -11
  19. package/admin/js/views/page-editor.js +207 -25
  20. package/admin/js/views/pages.js +14 -6
  21. package/admin/js/views/settings.js +137 -2
  22. package/admin/js/views/users.js +10 -7
  23. package/bin/cli.js +37 -10
  24. package/config/auth.json +2 -1
  25. package/config/content.json +1 -0
  26. package/config/navigation.json +14 -4
  27. package/config/plugins.json +0 -18
  28. package/config/presets.json +4 -8
  29. package/config/site.json +44 -3
  30. package/package.json +6 -2
  31. package/plugins/domma-effects/admin/templates/domma-effects.html +92 -3
  32. package/plugins/domma-effects/plugin.js +125 -0
  33. package/plugins/domma-effects/public/inject-body.html +19 -0
  34. package/plugins/example-analytics/admin/views/analytics.js +2 -2
  35. package/plugins/example-analytics/plugin.json +8 -0
  36. package/plugins/example-analytics/stats.json +15 -1
  37. package/plugins/form-builder/admin/templates/form-editor.html +19 -6
  38. package/plugins/form-builder/admin/views/form-editor.js +634 -9
  39. package/plugins/form-builder/admin/views/form-submissions.js +4 -4
  40. package/plugins/form-builder/admin/views/forms-list.js +5 -5
  41. package/plugins/form-builder/data/forms/consent.json +104 -0
  42. package/plugins/form-builder/data/forms/contacts.json +66 -0
  43. package/plugins/form-builder/data/submissions/consent.json +13 -0
  44. package/plugins/form-builder/data/submissions/contacts.json +26 -0
  45. package/plugins/form-builder/plugin.js +62 -11
  46. package/plugins/form-builder/plugin.json +12 -16
  47. package/plugins/form-builder/public/form-logic-engine.js +568 -0
  48. package/plugins/form-builder/public/inject-body.html +88 -6
  49. package/plugins/form-builder/public/inject-head.html +16 -0
  50. package/plugins/form-builder/public/package.json +1 -0
  51. package/public/css/site.css +113 -0
  52. package/public/js/btt.js +90 -0
  53. package/public/js/cookie-consent.js +61 -0
  54. package/public/js/site.js +129 -34
  55. package/scripts/build.js +129 -0
  56. package/scripts/seed.js +517 -7
  57. package/server/routes/api/collections.js +301 -0
  58. package/server/routes/api/settings.js +66 -2
  59. package/server/server.js +19 -15
  60. package/server/services/collections.js +430 -0
  61. package/server/services/content.js +11 -2
  62. package/server/services/hooks.js +109 -0
  63. package/server/services/markdown.js +500 -149
  64. package/server/services/plugins.js +6 -1
  65. package/server/services/renderer.js +73 -7
  66. package/server/templates/page.html +38 -3
  67. package/plugins/back-to-top/admin/templates/back-to-top-settings.html +0 -55
  68. package/plugins/back-to-top/admin/views/back-to-top-settings.js +0 -44
  69. package/plugins/back-to-top/config.js +0 -10
  70. package/plugins/back-to-top/plugin.js +0 -24
  71. package/plugins/back-to-top/plugin.json +0 -36
  72. package/plugins/back-to-top/public/inject-body.html +0 -105
  73. package/plugins/cookie-consent/admin/templates/cookie-consent-settings.html +0 -113
  74. package/plugins/cookie-consent/admin/views/cookie-consent-settings.js +0 -73
  75. package/plugins/cookie-consent/config.js +0 -30
  76. package/plugins/cookie-consent/plugin.js +0 -24
  77. package/plugins/cookie-consent/plugin.json +0 -36
  78. package/plugins/cookie-consent/public/inject-body.html +0 -69
  79. package/plugins/custom-css/admin/templates/custom-css.html +0 -17
  80. package/plugins/custom-css/admin/views/custom-css.js +0 -35
  81. package/plugins/custom-css/config.js +0 -1
  82. package/plugins/custom-css/data/custom.css +0 -0
  83. package/plugins/custom-css/plugin.js +0 -63
  84. package/plugins/custom-css/plugin.json +0 -32
  85. package/plugins/custom-css/public/inject-head.html +0 -1
  86. package/plugins/form-builder/data/forms/contact.json +0 -52
  87. package/plugins/form-builder/data/submissions/contact.json +0 -14
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Domma Effects Plugin — Server
3
3
  *
4
+ * Registers all [effect] shortcodes via the hooks API at startup.
5
+ * Shortcodes are only active when this plugin is enabled — disabling the plugin
6
+ * means [reveal], [counter], etc. pass through as plain text.
7
+ *
4
8
  * Endpoints (prefix: /api/plugins/domma-effects):
5
9
  * GET /settings — public (called from public site IIFE)
6
10
  * PUT /settings — admin-auth — saves user overrides
@@ -9,6 +13,127 @@ import {getPluginSettings, savePluginState} from '../../server/services/plugins.
9
13
 
10
14
  export default async function dommaEffectsPlugin(fastify, options) {
11
15
  const {authenticate, requireAdmin} = options.auth;
16
+ const {registerShortcode} = options.hooks;
17
+
18
+ // -------------------------------------------------------------------------
19
+ // Shortcode registrations
20
+ // -------------------------------------------------------------------------
21
+
22
+ // Self-closing: [counter to="100" from="0" prefix="$" suffix="+" duration="2000" /]
23
+ registerShortcode('counter', (attrStr, _body, ctx) => {
24
+ const attrs = ctx.parseShortcodeAttrs(attrStr);
25
+ const to = attrs.to || '0';
26
+ const prefix = attrs.prefix || '';
27
+ const suffix = attrs.suffix || '';
28
+ const dataAttrs = Object.entries(attrs).map(([k, v]) => ` data-fx-${k}="${v}"`).join('');
29
+ return `<span class="dm-fx-counter"${dataAttrs}>${prefix}${to}${suffix}</span>`;
30
+ }, {priority: 5});
31
+
32
+ // Self-closing: [celebrate theme="christmas" intensity="medium" /]
33
+ registerShortcode('celebrate', (attrStr, _body, ctx) => {
34
+ const attrs = ctx.parseShortcodeAttrs(attrStr);
35
+ const dataAttrs = Object.entries(attrs).map(([k, v]) => ` data-fx-${k}="${v}"`).join('');
36
+ return `<div class="dm-fx-celebrate"${dataAttrs}></div>`;
37
+ }, {priority: 5});
38
+
39
+ // [firework] — self-closing and wrapping forms.
40
+ // Self-closing: [firework type="burst" colour="rainbow" size="lg" continuous="true" /]
41
+ // Wrapping: [firework type="sparkle" colour="info"]Click me[/firework]
42
+ // Note: [fireworks] (plural) is registered separately below.
43
+ registerShortcode('firework', (attrStr, body, ctx) => {
44
+ const attrs = ctx.parseShortcodeAttrs(attrStr);
45
+ const classes = ['firework'];
46
+ if (attrs.type) classes.push(`firework-${attrs.type}`);
47
+ if (attrs.colour) classes.push(`firework-${attrs.colour}`);
48
+ if (attrs.size) classes.push(`firework-${attrs.size}`);
49
+ if (attrs.continuous === 'true') classes.push('firework-continuous');
50
+ if (attrs.hover === 'true') classes.push('firework-on-hover');
51
+ if (body === null) {
52
+ return `<div class="${classes.join(' ')}"></div>`;
53
+ }
54
+ const innerHtml = ctx.marked.parse(ctx.processCardBlocks(ctx.processGridBlocks(body.trim())));
55
+ return `<div class="${classes.join(' ')}">${innerHtml}</div>\n`;
56
+ }, {priority: 5});
57
+
58
+ // Wrapping container: [fireworks]...[/fireworks]
59
+ // Body contains already-processed firework HTML from the firework handler above.
60
+ registerShortcode('fireworks', (attrStr, body, _ctx) => {
61
+ if (body === null) return '';
62
+ return `<div class="fireworks-container">${body.trim()}</div>\n`;
63
+ }, {priority: 10});
64
+
65
+ // Dedicated scribe handler — supports both simple wrapping and action-script syntax.
66
+ // Simple: [scribe speed="fast"]Hello world[/scribe]
67
+ // Script: [scribe][render effect="fade"]Hello[/render][wait]1s[/wait][undo /][/scribe]
68
+ registerShortcode('scribe', (attrStr, body, ctx) => {
69
+ if (body === null) return '';
70
+ const attrs = ctx.parseShortcodeAttrs(attrStr);
71
+ const dataAttrs = Object.entries(attrs).map(([k, v]) => ` data-fx-${k}="${ctx.escapeAttr(v)}"`).join('');
72
+ const hasActions = /\[\s*(render|wait|undo)\b/.test(body);
73
+ if (!hasActions) {
74
+ const innerHtml = ctx.marked.parse(ctx.processCardBlocks(ctx.processGridBlocks(body.trim())));
75
+ return `<div class="dm-fx-scribe"${dataAttrs}>${innerHtml}</div>\n`;
76
+ }
77
+ const actions = [];
78
+ const actionRe = /\[render([^\]]*)\]([\s\S]*?)\[\/render\]|\[wait\]([\s\S]*?)\[\/wait\]|\[undo([^\]]*?)\/\]/gi;
79
+ for (const match of body.matchAll(actionRe)) {
80
+ if (match[0].startsWith('[render')) {
81
+ const rAttrs = ctx.parseShortcodeAttrs(match[1] || '');
82
+ const action = {render: match[2].trim()};
83
+ if (rAttrs.effect) action.effect = rAttrs.effect;
84
+ actions.push(action);
85
+ } else if (match[0].startsWith('[wait')) {
86
+ const val = match[3].trim();
87
+ actions.push({wait: isNaN(val) ? val : parseInt(val, 10)});
88
+ } else {
89
+ const uAttrs = ctx.parseShortcodeAttrs(match[4] || '');
90
+ if (uAttrs.all === 'true') actions.push({undoRender: 'all'});
91
+ else if (uAttrs.count) actions.push({undoRender: parseInt(uAttrs.count, 10)});
92
+ else actions.push({undoRender: true});
93
+ }
94
+ }
95
+ if (!actions.length) return '';
96
+ const actionsJson = ctx.escapeAttr(JSON.stringify(actions));
97
+ return `<span class="dm-fx-scribe"${dataAttrs} data-fx-actions="${actionsJson}"></span>\n`;
98
+ }, {priority: 5});
99
+
100
+ // Wrapping animation effects — all follow the same dm-fx-{name} pattern.
101
+ for (const name of ['reveal', 'breathe', 'pulse', 'shake', 'scramble', 'ripple', 'twinkle']) {
102
+ registerShortcode(name, (attrStr, body, ctx) => {
103
+ if (body === null) return '';
104
+ const attrs = ctx.parseShortcodeAttrs(attrStr);
105
+ const dataAttrs = Object.entries(attrs).map(([k, v]) => ` data-fx-${k}="${v}"`).join('');
106
+ const innerHtml = ctx.marked.parse(ctx.processCardBlocks(ctx.processGridBlocks(body.trim())));
107
+ return `<div class="dm-fx-${name}"${dataAttrs}>${innerHtml}</div>\n`;
108
+ });
109
+ }
110
+
111
+ // CSS-only: [animate type="fade-in-up" duration="slow" delay="200ms" repeat="infinite"]...[/animate]
112
+ registerShortcode('animate', (attrStr, body, ctx) => {
113
+ if (body === null) return '';
114
+ const attrs = ctx.parseShortcodeAttrs(attrStr);
115
+ const classes = [];
116
+ if (attrs.type) classes.push(`animate-${attrs.type}`);
117
+ if (attrs.duration) classes.push(`animate-duration-${attrs.duration}`);
118
+ if (attrs.delay) classes.push(`animate-delay-${attrs.delay}`);
119
+ if (attrs.repeat) classes.push(`animate-${attrs.repeat}`);
120
+ return `<div class="${classes.join(' ')}">${ctx.marked.parse(ctx.processCardBlocks(ctx.processGridBlocks(body.trim())))}</div>\n`;
121
+ });
122
+
123
+ // CSS-only: [ambient type="float-blobs" speed="slow" intensity="subtle"]...[/ambient]
124
+ registerShortcode('ambient', (attrStr, body, ctx) => {
125
+ if (body === null) return '';
126
+ const attrs = ctx.parseShortcodeAttrs(attrStr);
127
+ const classes = [];
128
+ if (attrs.type) classes.push(`bg-ambient-${attrs.type}`);
129
+ if (attrs.speed) classes.push(`bg-ambient-${attrs.speed}`);
130
+ if (attrs.intensity) classes.push(`bg-ambient-${attrs.intensity}`);
131
+ return `<div class="${classes.join(' ')}">${ctx.marked.parse(ctx.processCardBlocks(ctx.processGridBlocks(body.trim())))}</div>\n`;
132
+ });
133
+
134
+ // -------------------------------------------------------------------------
135
+ // Settings routes
136
+ // -------------------------------------------------------------------------
12
137
 
13
138
  fastify.get('/settings', async () => {
14
139
  return getPluginSettings('domma-effects');
@@ -218,9 +218,28 @@
218
218
  body.querySelectorAll('.dm-fx-scribe').forEach(function (el) {
219
219
  var opts = {};
220
220
  if (attr(el, 'speed')) opts.speed = numAttr(el, 'speed', 50);
221
+ if (attr(el, 'delete-speed')) opts.deleteSpeed = numAttr(el, 'delete-speed', 30);
221
222
  if (attr(el, 'cursor')) opts.cursor = boolAttr(el, 'cursor', true);
223
+ if (attr(el, 'cursor-char')) opts.cursorChar = attr(el, 'cursor-char');
224
+ if (attr(el, 'cursor-type')) opts.cursorType = attr(el, 'cursor-type');
222
225
  if (attr(el, 'mode')) opts.mode = attr(el, 'mode');
223
226
  if (attr(el, 'loop')) opts.loop = boolAttr(el, 'loop', false);
227
+ if (attr(el, 'loop-delay')) opts.loopDelay = numAttr(el, 'loop-delay', 1000);
228
+ if (attr(el, 'pause-on-hover')) opts.pauseOnHover = boolAttr(el, 'pause-on-hover', false);
229
+ // Script mode: data-fx-actions contains JSON action array
230
+ var actionsRaw = attr(el, 'actions');
231
+ if (actionsRaw) {
232
+ try {
233
+ opts.actions = JSON.parse(actionsRaw);
234
+ } catch (e) {
235
+ return;
236
+ }
237
+ } else {
238
+ var text = el.textContent.trim();
239
+ if (!text) return;
240
+ el.textContent = '';
241
+ opts.actions = [{render: text}];
242
+ }
224
243
  E.scribe(el, opts);
225
244
  });
226
245
 
@@ -43,8 +43,8 @@ async function loadStats($container) {
43
43
  T.create('#analytics-table', {
44
44
  data: stats,
45
45
  columns: [
46
- { key: 'url', label: 'Page URL', render: (val) => `<code>${val}</code>` },
47
- { key: 'hits', label: 'Page views' }
46
+ {key: 'url', title: 'Page URL', render: (val) => `<code>${val}</code>`},
47
+ {key: 'hits', title: 'Page views'}
48
48
  ],
49
49
  emptyMessage: 'No page views recorded yet.'
50
50
  });
@@ -23,5 +23,13 @@
23
23
  "inject": {
24
24
  "head": "public/inject-head.html",
25
25
  "bodyEnd": "public/inject-body.html"
26
+ },
27
+ "scaffold": {
28
+ "reset": [
29
+ {
30
+ "path": "stats.json",
31
+ "content": "{}"
32
+ }
33
+ ]
26
34
  }
27
35
  }
@@ -1 +1,15 @@
1
- {}
1
+ {
2
+ "/": 30,
3
+ "/about": 28,
4
+ "/blog": 11,
5
+ "/contact": 10,
6
+ "/resources/typography": 3,
7
+ "/resources": 2,
8
+ "/resources/shortcodes": 5,
9
+ "/resources/cards": 1,
10
+ "/resources/interactive": 12,
11
+ "/resources/grid": 4,
12
+ "/forms": 14,
13
+ "/resources/effects": 3,
14
+ "/blog/hello-world": 14
15
+ }
@@ -42,13 +42,26 @@
42
42
  <div class="card mb-4">
43
43
  <div class="card-header" style="display:flex;justify-content:space-between;align-items:center;">
44
44
  <h2>Fields</h2>
45
- <div style="display:flex;gap:.4rem;">
46
- <button id="add-field-btn" class="btn btn-ghost btn-sm">
47
- <span data-icon="plus"></span> Add Field
48
- </button>
49
- <button id="add-page-break-btn" class="btn btn-ghost btn-sm" title="Add a wizard step separator">
50
- <span data-icon="minus"></span> Add Page Break
45
+ <div id="add-element-dropdown" style="position:relative;">
46
+ <button id="add-element-btn" class="btn btn-ghost btn-sm" type="button">
47
+ <span data-icon="plus"></span> Add <span
48
+ style="font-size:.65rem;opacity:.6;margin-left:.1rem;">▾</span>
51
49
  </button>
50
+ <div id="add-element-menu"
51
+ style="display:none;position:absolute;right:0;top:calc(100% + .2rem);background:var(--card-bg,#1e1e2e);border:1px solid var(--border-color,#333);border-radius:6px;min-width:140px;z-index:100;box-shadow:0 4px 16px rgba(0,0,0,.3);overflow:hidden;">
52
+ <button id="add-field-btn" type="button"
53
+ style="display:block;width:100%;text-align:left;padding:.5rem .85rem;background:none;border:none;color:inherit;cursor:pointer;font-size:.875rem;border-bottom:1px solid var(--border-color,#333);">
54
+ Field
55
+ </button>
56
+ <button id="add-spacer-btn" type="button"
57
+ style="display:block;width:100%;text-align:left;padding:.5rem .85rem;background:none;border:none;color:inherit;cursor:pointer;font-size:.875rem;border-bottom:1px solid var(--border-color,#333);">
58
+ Spacer
59
+ </button>
60
+ <button id="add-page-break-btn" type="button"
61
+ style="display:block;width:100%;text-align:left;padding:.5rem .85rem;background:none;border:none;color:inherit;cursor:pointer;font-size:.875rem;">
62
+ Page Break
63
+ </button>
64
+ </div>
52
65
  </div>
53
66
  </div>
54
67
  <div class="card-body" style="padding:0;">