domma-cms 1.6.60 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/CHANGELOG.md +4344 -0
  2. package/README.md +1 -1
  3. package/admin/dist/domma/domma-tools.css +3 -3
  4. package/admin/dist/domma/domma-tools.min.js +3 -3
  5. package/admin/js/lib/effect-defs.js +1 -1
  6. package/admin/js/lib/effects-builder.js +2 -2
  7. package/admin/js/lib/page-editor-reference.js +5 -5
  8. package/admin/js/lib/shortcode-tags.js +1 -1
  9. package/admin/js/templates/docs/usage-shortcodes.html +39 -0
  10. package/docs/DommaFeatures.md +3005 -0
  11. package/docs/MultiTenant.md +176 -0
  12. package/docs/api-reference.md +1667 -0
  13. package/docs/cache.md +96 -0
  14. package/docs/configuration.md +1811 -0
  15. package/docs/editions-and-licences.md +190 -0
  16. package/docs/getting-started.md +201 -0
  17. package/docs/guides/form-triggers.md +260 -0
  18. package/docs/guides/mongodb-storage-adapter.md +282 -0
  19. package/docs/guides/multi-page-forms.md +121 -0
  20. package/docs/images/scribe-composer.png +0 -0
  21. package/docs/markdown-shortcodes.md +1738 -0
  22. package/docs/pentest-report-2026-04-03.md +181 -0
  23. package/docs/plans/2026-02-28-admin-topbar-login-forms.md +349 -0
  24. package/docs/plans/2026-02-28-domma-ui-polish.md +286 -0
  25. package/docs/plans/2026-02-28-reset-seed-fresh-commands.md +520 -0
  26. package/docs/plans/2026-03-02-cookie-consent-design.md +86 -0
  27. package/docs/plans/2026-03-02-cookie-consent-plugin.md +453 -0
  28. package/docs/plans/2026-03-02-tooltip-sweep-and-back-to-top-plugin-design.md +126 -0
  29. package/docs/plans/2026-03-02-tooltip-sweep-back-to-top.md +427 -0
  30. package/docs/plans/2026-03-05-cookie-consent-btt-core.md +115 -0
  31. package/docs/plans/2026-03-05-custom-css-migrate-to-core-design.md +35 -0
  32. package/docs/plans/2026-03-05-custom-css-migrate-to-core.md +352 -0
  33. package/docs/plans/2026-03-05-scribe-script-shortcode.md +373 -0
  34. package/docs/plans/2026-03-05-user-types-design.md +139 -0
  35. package/docs/plans/2026-03-05-user-types-implementation.md +1035 -0
  36. package/docs/plans/2026-03-09-page-editor-view-cta-inserts-design.md +78 -0
  37. package/docs/plans/2026-03-09-page-editor-view-cta-inserts.md +388 -0
  38. package/docs/plans/2026-03-09-pro-documentation-design.md +80 -0
  39. package/docs/plans/2026-03-09-pro-documentation.md +1017 -0
  40. package/docs/plans/2026-06-10-core-project-design.md +240 -0
  41. package/docs/plugin-development.md +827 -0
  42. package/docs/recruitment-recipe.md +501 -0
  43. package/docs/scaffolding.md +312 -0
  44. package/docs/screenshots/calculator-floating.png +0 -0
  45. package/docs/screenshots/calculator-standard.png +0 -0
  46. package/docs/screenshots/email-settings.png +0 -0
  47. package/docs/screenshots/messages-count.png +0 -0
  48. package/docs/structure/user-types.md +107 -0
  49. package/docs/superpowers/plans/2026-05-25-admin-sidebar-via-menus.md +1112 -0
  50. package/docs/superpowers/plans/2026-06-08-navbar-mobile-drawer-and-menu-enrichment.md +1161 -0
  51. package/docs/superpowers/plans/2026-06-25-domma-docs-project.md +695 -0
  52. package/docs/superpowers/plans/2026-06-26-surveys-HANDOFF.md +68 -0
  53. package/docs/superpowers/plans/2026-06-26-surveys-pro-plugin.md +1902 -0
  54. package/docs/superpowers/plans/2026-08-19-collection-context-sort-export.md +1577 -0
  55. package/docs/superpowers/plans/2026-10-09-content-api.md +1192 -0
  56. package/docs/superpowers/specs/2026-03-12-form-followup-actions-design.md +152 -0
  57. package/docs/superpowers/specs/2026-03-12-page-editor-form-insert-and-per-page-theme-design.md +115 -0
  58. package/docs/superpowers/specs/2026-03-17-nav-hidden-items-design.md +85 -0
  59. package/docs/superpowers/specs/2026-03-19-accordion-insert-design.md +175 -0
  60. package/docs/superpowers/specs/2026-03-25-static-block-shortcode-design.md +153 -0
  61. package/docs/superpowers/specs/2026-03-25-text-shortcode-design.md +168 -0
  62. package/docs/superpowers/specs/2026-04-05-card-builder-design.md +315 -0
  63. package/docs/superpowers/specs/2026-04-06-insert-builders-design.md +278 -0
  64. package/docs/superpowers/specs/2026-04-06-layouts-design.md +226 -0
  65. package/docs/superpowers/specs/2026-04-07-blog-plugin-design.md +300 -0
  66. package/docs/superpowers/specs/2026-04-07-calendar-plugin-design.md +538 -0
  67. package/docs/superpowers/specs/2026-04-17-components-area-design.md +368 -0
  68. package/docs/superpowers/specs/2026-05-02-effects-slideover-overhaul-design.md +193 -0
  69. package/docs/superpowers/specs/2026-05-06-dashboard-rebuild-design.md +257 -0
  70. package/docs/superpowers/specs/2026-05-24-menus-design.md +466 -0
  71. package/docs/superpowers/specs/2026-05-25-admin-sidebar-via-menus-design.md +358 -0
  72. package/docs/superpowers/specs/2026-05-25-projects-design.md +380 -0
  73. package/docs/superpowers/specs/2026-06-08-navbar-mobile-drawer-and-menu-enrichment-design.md +292 -0
  74. package/docs/superpowers/specs/2026-06-08-sidebar-tools-plugins-folders-design.md +59 -0
  75. package/docs/superpowers/specs/2026-06-25-domma-docs-project-design.md +192 -0
  76. package/docs/superpowers/specs/2026-06-26-domma-charts-upstream-proposal.md +44 -0
  77. package/docs/superpowers/specs/2026-06-26-manager-scheduler-upstream-proposal.md +46 -0
  78. package/docs/superpowers/specs/2026-06-26-surveys-pro-plugin-design.md +206 -0
  79. package/docs/superpowers/specs/2026-07-02-shortcode-context-menu-design.md +96 -0
  80. package/docs/superpowers/specs/2026-08-19-collection-context-sort-export-design.md +330 -0
  81. package/docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md +1150 -0
  82. package/docs/superpowers/specs/2026-10-09-content-api-design.md +325 -0
  83. package/docs/superpowers/specs/plans/2026-03-12-form-followup-actions.md +624 -0
  84. package/docs/superpowers/specs/plans/2026-03-12-page-editor-form-insert-and-per-page-theme.md +125 -0
  85. package/docs/superpowers/specs/plans/2026-03-17-nav-hidden-items.md +578 -0
  86. package/docs/superpowers/specs/plans/2026-03-19-accordion-insert.md +635 -0
  87. package/docs/superpowers/specs/plans/2026-03-25-text-shortcode.md +545 -0
  88. package/docs/superpowers/specs/plans/2026-04-05-card-builder.md +1970 -0
  89. package/docs/superpowers/specs/plans/2026-04-06-insert-builders.md +1049 -0
  90. package/docs/superpowers/specs/plans/2026-04-06-layouts.md +1015 -0
  91. package/docs/superpowers/specs/plans/2026-04-07-calendar-plugin.md +1526 -0
  92. package/docs/superpowers/specs/plans/2026-04-08-plugin-monetisation-split.md +242 -0
  93. package/docs/superpowers/specs/plans/2026-04-17-components-area.md +2038 -0
  94. package/docs/superpowers/specs/plans/2026-05-02-effects-slideover-overhaul.md +2335 -0
  95. package/docs/superpowers/specs/plans/2026-05-06-dashboard-rebuild.md +1999 -0
  96. package/docs/superpowers/specs/plans/2026-05-17-cache-system.md +1436 -0
  97. package/docs/superpowers/specs/plans/2026-05-24-menus.md +3096 -0
  98. package/docs/superpowers/specs/plans/2026-05-25-projects.md +2869 -0
  99. package/docs/superpowers/specs/plans/2026-09-22-marketplace-step2-consolidation.md +505 -0
  100. package/docs/superpowers/specs/plans/2026-09-22-marketplace-step3-catalogue-and-key.md +154 -0
  101. package/docs/superpowers/specs/plans/2026-09-22-tools-promotion.md +260 -0
  102. package/docs/theming.md +320 -0
  103. package/package.json +2 -2
  104. package/public/js/celebrations/core/canvas.js +1 -1
  105. package/public/js/celebrations/index.js +1 -1
  106. package/public/js/celebrations/licenses/ABeeZee-Regular-OFL.txt +93 -0
  107. package/public/js/celebrations/licenses/Allura-Regular-OFL.txt +93 -0
  108. package/public/js/celebrations/message-attrs.mjs +1 -0
  109. package/public/js/celebrations/themes/anniversary.js +1 -0
  110. package/public/js/celebrations/themes/autumn.js +1 -0
  111. package/public/js/celebrations/themes/birthday.js +1 -0
  112. package/public/js/celebrations/themes/butterfly-flight.js +1 -0
  113. package/public/js/celebrations/themes/calendar.js +1 -0
  114. package/public/js/celebrations/themes/chinese-new-year-dates.js +1 -0
  115. package/public/js/celebrations/themes/chinese-new-year.js +1 -0
  116. package/public/js/celebrations/themes/christmas.js +1 -1
  117. package/public/js/celebrations/themes/easter.js +1 -0
  118. package/public/js/celebrations/themes/garden-drawing.js +1 -0
  119. package/public/js/celebrations/themes/garden.js +1 -0
  120. package/public/js/celebrations/themes/graduation.js +1 -0
  121. package/public/js/celebrations/themes/greeting.js +1 -0
  122. package/public/js/celebrations/themes/make-it-rain.js +1 -0
  123. package/public/js/celebrations/themes/money-rain.js +1 -0
  124. package/public/js/celebrations/themes/new-year.js +1 -0
  125. package/public/js/celebrations/themes/occasion-drawing.js +1 -0
  126. package/public/js/celebrations/themes/occasions.js +1 -0
  127. package/public/js/celebrations/themes/rain-control.js +1 -0
  128. package/public/js/celebrations/themes/sparkler-font-data.js +1 -0
  129. package/public/js/celebrations/themes/sparkler-font.js +1 -0
  130. package/public/js/celebrations/themes/sparkler-writing.js +1 -0
  131. package/public/js/celebrations/themes/summer-garden.js +1 -0
  132. package/public/js/celebrations/themes/winter.js +1 -0
  133. package/public/js/celebrations/trait-data.mjs +1 -1
  134. package/public/js/effects.js +1 -1
  135. package/scripts/build.js +4 -0
  136. package/server/services/markdown.js +5 -2
  137. package/server/templates/page.html +1 -1
@@ -0,0 +1,152 @@
1
+ # Form Follow-Up: Success States & Action Triggers
2
+
3
+ **Date:** 2026-03-12
4
+ **Status:** Approved
5
+
6
+ ---
7
+
8
+ ## Problem
9
+
10
+ The `[form]` shortcode stores submissions but provides no visible follow-up to the visitor and no automatic way to trigger a CMS Action after a submission. Admins know forms have built-in email/webhook support, but there is no bridge to the Actions pipeline and no client-side success UX.
11
+
12
+ ---
13
+
14
+ ## Solution Overview
15
+
16
+ Three connected improvements:
17
+
18
+ 1. **Action auto-trigger on submission** - add an optional `actionSlug` field to form settings; the submission pipeline executes the named Action server-side after storing the entry.
19
+ 2. **Configurable visitor success UX** - add an optional `successRedirect` URL field; client redirects if set, otherwise fades the form out and shows `successMessage` inline.
20
+ 3. **Example page + tutorial** - a working public example and admin tutorial covering the full flow.
21
+
22
+ ---
23
+
24
+ ## Data Flow
25
+
26
+ ```
27
+ [form] shortcode (public page)
28
+ ↓ visitor submits
29
+ ↓ site.js intercepts submit, POST /api/forms/submit/:slug
30
+ ↓ validate fields
31
+ ↓ store entry to collection
32
+ ↓ fire email action (if configured)
33
+ ↓ fire webhook action (if configured)
34
+ ↓ executeAction(actionSlug, entry.id) ← NEW - service-to-service, no auth needed
35
+ ↓ return { success, message, redirect } ← redirect field is NEW
36
+ Client:
37
+ if redirect → window.location.href = redirect
38
+ else → fade form out, show successMessage inline
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Form Settings Changes
44
+
45
+ Two new optional fields in each form's JSON (`content/forms/{slug}.json`):
46
+
47
+ | Field | Type | Default | Purpose |
48
+ |---|---|---|---|
49
+ | `actionSlug` | `string \| null` | `null` | CMS Action slug to execute after submission |
50
+ | `successRedirect` | `string \| null` | `null` | URL to redirect visitor on success |
51
+
52
+ **Priority rule:** `successRedirect` takes precedence over `successMessage` if both are set.
53
+
54
+ Existing fields unchanged: `successMessage`, `submitText`, `layout`, `honeypot`, `rateLimitPerMinute`, `actions.email`, `actions.webhook`.
55
+
56
+ ---
57
+
58
+ ## Admin UI Changes
59
+
60
+ **Form editor - Settings tab:** two new fields added after the existing `successMessage` field:
61
+
62
+ - **"Action on Submit"** - `<select>` populated from `GET /api/actions`. Options: blank (none) + each action's label and slug.
63
+ - **"Success Redirect URL"** - text input (e.g. `/thank-you`). Helper text: *"Takes priority over the success message if set."*
64
+
65
+ ---
66
+
67
+ ## Server Changes
68
+
69
+ **`server/routes/api/forms.js` - submission handler:**
70
+
71
+ After the existing webhook step, add:
72
+
73
+ ```js
74
+ // Execute CMS Action if configured
75
+ if (formDef.actionSlug) {
76
+ try {
77
+ await executeAction(formDef.actionSlug, entry.id, { user: null });
78
+ } catch (err) {
79
+ console.warn(`[forms] Action "${formDef.actionSlug}" failed for form "${slug}":`, err.message);
80
+ // Non-fatal - submission already stored
81
+ }
82
+ }
83
+ ```
84
+
85
+ Return value gains a `redirect` field:
86
+
87
+ ```js
88
+ return {
89
+ success: true,
90
+ message: formDef.settings?.successMessage || 'Thank you.',
91
+ redirect: formDef.settings?.successRedirect || null
92
+ };
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Client Changes
98
+
99
+ **`public/js/site.js` - form submission handler:**
100
+
101
+ Intercept `submit` on elements rendered by the `[form]` shortcode. On success response:
102
+
103
+ - If `redirect` is set → `window.location.href = redirect`
104
+ - Else → hide the form element, show `successMessage` in its place (simple fade, no library needed)
105
+
106
+ On error → display validation/server errors inline beneath the relevant fields.
107
+
108
+ ---
109
+
110
+ ## Example Page
111
+
112
+ **`content/pages/contact.md`** - updated to use:
113
+ - `[form slug="enquiries"]` shortcode
114
+ - The enquiries form configured with:
115
+ - Email notification to admin
116
+ - `actionSlug: "new-enquiry"`
117
+ - `successMessage: "Thanks - we'll be in touch shortly."`
118
+
119
+ **Sample Action** - `new-enquiry` action with a single webhook step pointing to a placeholder URL, demonstrating the pattern.
120
+
121
+ ---
122
+
123
+ ## Tutorial (admin/js/templates/tutorials.html)
124
+
125
+ New collapsible card: **"Form Follow-Up: Notifications & Actions"**
126
+
127
+ Sections:
128
+ 1. Configuring email notification on a form
129
+ 2. Creating an Action with one or more steps
130
+ 3. Wiring the Action to the form via "Action on Submit"
131
+ 4. Choosing between inline success message and redirect
132
+ 5. Execution order: store → email → webhook → action
133
+
134
+ ---
135
+
136
+ ## Documentation (admin/js/templates/documentation.html)
137
+
138
+ Short addition to the Forms section referencing the two new fields and pointing to the Actions section for step configuration.
139
+
140
+ ---
141
+
142
+ ## Files Modified
143
+
144
+ | File | Change |
145
+ |---|---|
146
+ | `server/routes/api/forms.js` | Add action execution + redirect to response |
147
+ | `admin/js/views/formEditor.js` | Add actionSlug + successRedirect fields to settings tab |
148
+ | `public/js/site.js` | Handle form submit, success/redirect/error states |
149
+ | `content/pages/contact.md` | Updated example using form + action |
150
+ | `content/collections/actions/data.json` | Seed `new-enquiry` sample action |
151
+ | `admin/js/templates/tutorials.html` | New tutorial card |
152
+ | `admin/js/templates/documentation.html` | New docs entry under Forms |
@@ -0,0 +1,115 @@
1
+ # Page Editor: Form Insert & Per-Page Theme - Design
2
+
3
+ **Date:** 2026-03-12
4
+
5
+ ## Overview
6
+
7
+ Two additions to the page editor:
8
+
9
+ 1. **Insert Form** - a new entry in the Insert dropdown that embeds a form into page content via a server-side shortcode
10
+ 2. **Per-page theme** - an optional `theme` frontmatter field that overrides the global site theme for a single page
11
+
12
+ ---
13
+
14
+ ## Feature 1: Insert Form
15
+
16
+ ### Shortcode
17
+
18
+ ```
19
+ [form slug="to-do"]
20
+ [form slug="contact" title="Get in Touch"]
21
+ ```
22
+
23
+ Optional `title` attribute overrides the form's own display title. Slug is required.
24
+
25
+ ### Server-side processing
26
+
27
+ **File:** `server/services/markdown.js`
28
+
29
+ - New `processFormBlocks()` function
30
+ - Runs in the existing shortcode pipeline before `marked.parse()`, after `processDConfigBlocks()`
31
+ - Reads `content/forms/{slug}.json`
32
+ - If the form is not found, renders a visible error placeholder - no crash
33
+ - Base64-encodes the form JSON and outputs:
34
+
35
+ ```html
36
+ <div class="cms-form-embed" data-form-inline="BASE64"></div>
37
+ ```
38
+
39
+ - If `title` attribute is provided, it overrides `form.title` in the encoded schema before encoding
40
+
41
+ ### Client-side hydration
42
+
43
+ **File:** `public/js/site.js`
44
+
45
+ - On DOM ready, find all `[data-form-inline]` elements
46
+ - Decode the base64 schema
47
+ - Call `F.render(el, blueprint, {}, settings)` - same call used by the admin preview
48
+
49
+ ### Admin insert UI
50
+
51
+ **File:** `admin/js/lib/markdown-toolbar.js`
52
+
53
+ - New "Form" entry in the Insert dropdown (alongside Collection, View, etc.)
54
+ - Clicking opens a picker modal:
55
+ - Fetches available forms from `GET /api/forms`
56
+ - Lists form titles with their slugs
57
+ - Selecting a form inserts `[form slug="selected-slug"]` at the cursor position
58
+ - No additional options at insert time (title override is manual if needed)
59
+
60
+ ---
61
+
62
+ ## Feature 2: Per-page Theme
63
+
64
+ ### Data model
65
+
66
+ New optional field in page YAML frontmatter:
67
+
68
+ ```yaml
69
+ theme: "ocean-dark"
70
+ ```
71
+
72
+ Omitted or blank = use global `site.json` theme. No migration needed - absence is the default.
73
+
74
+ ### Renderer
75
+
76
+ **File:** `server/services/renderer.js`
77
+
78
+ - After reading page frontmatter, check `page.theme`
79
+ - If set, substitute it in place of `site.theme` for `activeTheme` / `dommaTheme` / `customThemeClass` computation
80
+ - Applies to the whole page (navbar, footer, content area)
81
+ - No structural change - just a different source value for the existing theme variables
82
+
83
+ ### Admin UI
84
+
85
+ **File:** `admin/js/templates/page-editor.html`
86
+
87
+ - Add a "Theme" `<select>` in the Page Details tab
88
+ - Options: blank "- Use site default -" plus all Domma themes:
89
+ - charcoal-dark, charcoal-light, ocean-dark, ocean-light, forest-dark, forest-light, sunset-dark, sunset-light, royal-dark, royal-light, lemon-dark, lemon-light, silver-dark, silver-light, grayve-dark, grayve-light
90
+
91
+ **File:** `admin/js/views/page-editor.js` (or equivalent)
92
+
93
+ - Read `page.theme` on load, set the select value (blank if absent)
94
+ - Write `theme` field on save (omit the key entirely if blank, to keep frontmatter clean)
95
+
96
+ ---
97
+
98
+ ## Files Changed
99
+
100
+ | File | Change |
101
+ |------|--------|
102
+ | `server/services/markdown.js` | Add `processFormBlocks()`, call in pipeline |
103
+ | `public/js/site.js` | Hydrate `[data-form-inline]` embeds on DOM ready |
104
+ | `admin/js/lib/markdown-toolbar.js` | Add Form entry to Insert dropdown + picker modal |
105
+ | `server/services/renderer.js` | Check `page.theme` before falling back to `site.theme` |
106
+ | `admin/js/templates/page-editor.html` | Add Theme select in Page Details tab |
107
+ | `admin/js/views/page-editor.js` | Read/write `theme` field on load/save |
108
+
109
+ ---
110
+
111
+ ## Out of Scope
112
+
113
+ - Form SSR (full `<form>` HTML generation) - client hydration from inline schema is sufficient
114
+ - Per-page overrides beyond theme - YAGNI
115
+ - Theme preview in admin - out of scope for now
@@ -0,0 +1,85 @@
1
+ # Nav Hidden Items - Design Spec
2
+
3
+ **Date:** 2026-03-17
4
+ **Status:** Approved
5
+
6
+ ## Overview
7
+
8
+ Add a "hidden" toggle to the navigation editor so menu items and footer links can be suppressed from the public site without being deleted. Hidden items are preserved in `navigation.json` and `site.json` for easy re-enabling, but are stripped server-side before the public page receives them.
9
+
10
+ ## Scope
11
+
12
+ - Main menu items (including children)
13
+ - Footer links (`site.footer.links`)
14
+
15
+ ## Data Model
16
+
17
+ Both `navigation.json` items and `site.json` footer links gain an optional `"hidden": true` flag:
18
+
19
+ ```json
20
+ { "text": "About", "url": "/about", "icon": "info", "hidden": true }
21
+ ```
22
+
23
+ - Absent key = visible (backwards compatible, no migration needed)
24
+ - Children can be hidden independently of their parent
25
+ - If a parent item is hidden, the **entire entry is stripped** from the public output, including all its children - child `hidden` flags are irrelevant when the parent is hidden
26
+ - The admin GET/PUT API is untouched - it always works with raw data, hidden flags included
27
+
28
+ ## Admin Editor UI
29
+
30
+ ### Saving behaviour
31
+
32
+ The hidden toggle is **UI-state only** - it updates the in-memory `flatItems` array but does not auto-save. The user must press the existing Save button to persist. On save failure, the existing error toast fires; no optimistic revert is needed because nothing has been written to disk.
33
+
34
+ ### Per-row toggle button (menu items)
35
+
36
+ Each `nav-item-row` gains a second action button using the `eye-off` icon (already registered in the Domma icon set), placed **to the left** of the existing trash button. Clicking it:
37
+
38
+ 1. Flips the item's `hidden` state in the `flatItems` array
39
+ 2. Calls `renderItems()` to re-render the full list
40
+
41
+ `renderItems()` writes a `data-hidden="true|false"` attribute on the toggle button from the model. `syncFromDom()` reads `$(this).find('.btn-toggle-hidden').data('hidden') === 'true'` (string comparison - DOM data attributes are strings) to populate `hidden` on each flat item before a save.
42
+
43
+ ### Per-row toggle button (footer links)
44
+
45
+ Footer link rows receive identical treatment: an `eye-off` toggle button to the left of the trash button. `renderFooterLinks()` writes `data-hidden` from the model. `syncFooterFromDom()` reads it to populate `hidden` on each footer link before a save.
46
+
47
+ ### Visual treatment
48
+
49
+ Hidden rows receive a `.nav-item-row--hidden` CSS class (in `admin/css/admin.css`):
50
+
51
+ - Row is dimmed (e.g. `opacity: 0.45`)
52
+ - Label input text has a strikethrough (e.g. `text-decoration: line-through`)
53
+
54
+ The eye-off button itself gets an `.active` class when the item is hidden, so it appears pressed/highlighted.
55
+
56
+ ### Column header
57
+
58
+ The `nav-items-header` in `navigation.html` gains a narrow column label (e.g. "Vis.") above the new toggle button column to keep the layout aligned.
59
+
60
+ ## Server-Side Filtering
61
+
62
+ In `server/services/renderer.js`, before serialising `__CMS_NAV__` into the page:
63
+
64
+ 1. Filter `nav.items` - remove entries where `hidden === true` (this removes the parent and all its children)
65
+ 2. For each remaining parent item, filter its `items` children - remove children where `hidden === true`
66
+ 3. Filter `site.footer.links` - remove entries where `hidden === true`
67
+
68
+ No changes to any API route. Filtering happens at render time only.
69
+
70
+ **Note:** The `[footer]` tag in Markdown is a card sub-tag only (it renders a `<div class="card-footer">`). It has no connection to `site.footer.links` and is unaffected by this change.
71
+
72
+ ## Files Affected
73
+
74
+ | File | Change |
75
+ |------|--------|
76
+ | `admin/js/views/navigation.js` | `flatItems` gains `hidden` field; `syncFromDom`, `nestItems`, `renderItems` updated; footer equivalents updated |
77
+ | `admin/js/templates/navigation.html` | Column header updated for new button column |
78
+ | `admin/css/admin.css` | `.nav-item-row--hidden` style added |
79
+ | `server/services/renderer.js` | Filter hidden items/links before injecting `__CMS_NAV__` |
80
+
81
+ ## Out of Scope
82
+
83
+ - Hiding items per-role or per-user
84
+ - Scheduled visibility (show/hide on a date)
85
+ - Any `[footer]` or `[header]` shortcode behaviour (unrelated to `site.footer.links`)
@@ -0,0 +1,175 @@
1
+ # Accordion Insert - Design Spec
2
+
3
+ **Date:** 2026-03-19
4
+ **Status:** Approved
5
+
6
+ ## Overview
7
+
8
+ Add an "Accordion" entry to the Page Editor Insert dropdown. The insert dialog supports two modes:
9
+ - **Static** - user defines items manually (titles + placeholder body content)
10
+ - **Collection** - bind to a CMS collection; entries are rendered as accordion items using nominated fields
11
+
12
+ ## Files Changed
13
+
14
+ | File | Change |
15
+ |------|--------|
16
+ | `server/services/markdown.js` | Add `display="accordion"` to `processCollectionBlocks()` |
17
+ | `admin/js/lib/markdown-toolbar.js` | Register icon, add action, export `onAccordion` |
18
+ | `admin/js/views/page-editor.js` | Implement `toolbar.onAccordion(...)` dialog |
19
+
20
+ ---
21
+
22
+ ## Section 1 - Server-side (`markdown.js`)
23
+
24
+ ### New helper: `renderCollectionAccordion`
25
+
26
+ ```
27
+ renderCollectionAccordion(entries, titleField, bodyField, multiple, emptyMsg)
28
+ ```
29
+
30
+ - Produces identical HTML to `processAccordionBlocks()` so existing accordion CSS/JS handles it with no extra work
31
+ - Wraps output in `<div class="accordion"[ data-multi="true"]>`
32
+ - Each entry → `<div class="accordion-item">`:
33
+ - Header: `<h3 class="accordion-header"><button class="accordion-button" type="button">{title}<span class="accordion-icon" data-icon="chevron-down"></span></button></h3>`
34
+ - Body: `<div class="accordion-body"><div class="accordion-content">{bodyHtml}</div></div>`
35
+ - `titleField` - entry field used as header text; falls back to entry `id` if field is absent
36
+ - `bodyField` - entry field rendered as body content (escaped text); falls back to empty string
37
+ - `multiple` - `true` adds `data-multi="true"` on wrapper
38
+ - Empty state: same `dm-collection-display dm-collection-empty` pattern as other display modes
39
+
40
+ ### Change to `processCollectionBlocks`
41
+
42
+ Add a new branch alongside `cards`, `list`, `block`:
43
+
44
+ ```js
45
+ } else if (display === 'accordion') {
46
+ const titleField = attrs['title-field'] || 'title';
47
+ const bodyField = attrs['body-field'] || 'description';
48
+ const multiple = attrs.multiple === 'true';
49
+ replacement = renderCollectionAccordion(entries, titleField, bodyField, multiple, emptyMsg);
50
+ }
51
+ ```
52
+
53
+ ### Shortcode signature
54
+
55
+ ```
56
+ [collection slug="faq" display="accordion" title-field="title" body-field="description" limit="10" multiple="true" /]
57
+ ```
58
+
59
+ All attributes except `slug` and `display` are optional.
60
+
61
+ ---
62
+
63
+ ## Section 2 - Toolbar (`markdown-toolbar.js`)
64
+
65
+ ### New icon: `accordion-insert`
66
+
67
+ Register a stacked-chevrons SVG icon (visually distinct from the `layout-list` tabs icon).
68
+
69
+ ### Changes to `executeAction`
70
+
71
+ Add `else if (action === 'accordion')` branch:
72
+ - If `handlers.accordion` is set, delegate to it
73
+ - Fallback: insert a 2-item static snippet directly
74
+
75
+ ```
76
+ [accordion]
77
+ [item title="Item 1"]Content here.[/item]
78
+ [item title="Item 2"]Content here.[/item]
79
+ [/accordion]
80
+ ```
81
+
82
+ ### Changes to `buttons` array (Insert dropdown)
83
+
84
+ Add after the Tabs entry:
85
+ ```js
86
+ {label: 'Accordion', icon: 'accordion-insert', action: 'accordion'},
87
+ ```
88
+
89
+ ### Changes to `handlers` object and `createToolbar` return
90
+
91
+ - Add `accordion: null` to handlers
92
+ - Export `onAccordion(cb) { handlers.accordion = cb; }` from the returned object
93
+
94
+ ---
95
+
96
+ ## Section 3 - Dialog (`page-editor.js`)
97
+
98
+ ### Handler: `toolbar.onAccordion(async (ta) => { ... })`
99
+
100
+ Follows the same light-DOM modal pattern as `onTabs` and `onCollection`.
101
+
102
+ ### Dialog structure
103
+
104
+ ```
105
+ ┌─ Insert Accordion ────────────────────┐
106
+ │ [Static] [Collection] │ ← segmented toggle
107
+ │ │
108
+ │ ── Static panel (default) ────────── │
109
+ │ Item 1: [___________________] │
110
+ │ Item 2: [___________________] │
111
+ │ [+ Add Item] │
112
+ │ □ Allow multiple open │
113
+ │ │
114
+ │ ── Collection panel (hidden) ─────── │
115
+ │ Collection: [select ▾] │
116
+ │ Title field: [select ▾] │
117
+ │ Body field: [select ▾] │
118
+ │ Limit: [ ] (optional) │
119
+ │ □ Allow multiple open │
120
+ │ │
121
+ │ [Insert Accordion] │
122
+ └───────────────────────────────────────┘
123
+ ```
124
+
125
+ ### Mode toggle behaviour
126
+
127
+ - Two buttons styled as a segmented control (`btn-group` or inline toggle)
128
+ - Clicking "Collection" hides the static panel and shows the collection panel, and vice versa
129
+ - Active mode state is a local variable used by the Insert button
130
+
131
+ ### Static panel behaviour
132
+
133
+ - Starts with two item rows, each containing a single text input (placeholder: `Item title`)
134
+ - "Add Item" appends a new row (no maximum enforced)
135
+ - "Allow multiple open" checkbox, unchecked by default
136
+
137
+ ### Collection panel behaviour
138
+
139
+ - Collection `<select>` populated from `api.collections.list()` (fetched once on dialog open)
140
+ - On collection change: call `api.collections.get(slug)`, extract `schema.fields[]`, populate both field selects
141
+ - Title field: pre-select field named `title` if present; otherwise first field; label shows field name
142
+ - Body field: pre-select field named `description` if present; otherwise second field (or `- none -`)
143
+ - Limit `<input type="number">`, optional, placeholder "All"
144
+ - "Allow multiple open" checkbox, unchecked by default
145
+
146
+ ### Snippet generation (Insert button)
147
+
148
+ **Static mode:**
149
+ ```
150
+ [accordion multiple="true"]
151
+ [item title="Item 1"]Content here.[/item]
152
+ [item title="Item 2"]Content here.[/item]
153
+ [/accordion]
154
+ ```
155
+ - Skips blank-title rows
156
+ - Falls back to "Item N" if title input is empty
157
+
158
+ **Collection mode:**
159
+ ```
160
+ [collection slug="faq" display="accordion" title-field="title" body-field="description" /]
161
+ ```
162
+ - Appends `limit="N"` if limit input is non-empty
163
+ - Appends `multiple="true"` if checkbox is checked
164
+
165
+ ---
166
+
167
+ ## Edge Cases
168
+
169
+ | Scenario | Behaviour |
170
+ |----------|-----------|
171
+ | No collections exist | Collection select shows "No collections found"; Insert disabled |
172
+ | Schema fetch fails | Field selects show single option "- unknown -"; Insert still enabled |
173
+ | Static mode, all titles blank | Inserts snippet with "Item 1", "Item 2" fallback labels |
174
+ | Collection with no fields | Field selects populated with `- none -`; shortcode emitted without `title-field`/`body-field` |
175
+ | `body-field` on server but field missing on entry | Renders empty body gracefully (no crash) |
@@ -0,0 +1,153 @@
1
+ # Static Block Shortcode - Design Spec
2
+
3
+ ## Context
4
+
5
+ Blocks in Domma CMS are reusable HTML templates (`content/blocks/*.html`) with `{{placeholder}}` substitution. Today,
6
+ blocks can only be used via `[collection slug="..." display="block" block="post-card" /]`, which fetches entries from a
7
+ Collection at render time.
8
+
9
+ **Problem**: For one-off content - a single featured post card, a contact highlight, a testimonial - creating an entire
10
+ Collection with one entry is overkill. Users need a way to use block templates with static, inline data entered directly
11
+ in the page editor.
12
+
13
+ **Solution**: A new `[block]` shortcode that takes a block template name and inline data as attributes, rendered
14
+ server-side using the existing block template infrastructure. A new "Block" item in the page editor's Insert menu
15
+ provides a guided modal with live preview.
16
+
17
+ ---
18
+
19
+ ## Shortcode Syntax
20
+
21
+ ```
22
+ [block template="post-card" title="My Post" author="Jane Doe" excerpt="A quick summary" /]
23
+ ```
24
+
25
+ - **Self-closing** (like `[collection ... /]`)
26
+ - `template` is the only reserved attribute - everything else is data for `{{placeholder}}` substitution
27
+ - Attribute values are HTML-escaped during rendering (reuses existing `escapeHtmlText`)
28
+ - Missing placeholders render as empty strings (consistent with collection block behaviour)
29
+
30
+ ---
31
+
32
+ ## Server-Side Processing
33
+
34
+ ### New function: `processStaticBlocks(content)`
35
+
36
+ **File**: `server/services/markdown.js`
37
+
38
+ **Regex**: Matches `[block template="..." key="value" ... /]` - same multi-line attribute parsing approach used by
39
+ `processTextBlocks`.
40
+
41
+ **Logic**:
42
+
43
+ 1. Extract `template` attribute (required - skip if missing)
44
+ 2. Collect all other attributes as key-value data pairs
45
+ 3. Load block template via existing `loadBlockTemplate(templateName)` (line 52)
46
+ 4. Run `{{placeholder}}` replacement: `template.replace(/\{\{([\w_]+)\}\}/g, ...)` using the attribute data (same regex
47
+ as `renderCollectionBlocks` line 73)
48
+ 5. HTML-escape all values via existing `escapeHtmlText()`
49
+ 6. Wrap output in `<div class="dm-static-block">` (distinct from `dm-collection-blocks` since this is a single item, not
50
+ a collection grid)
51
+ 7. On error (template not found), output a visible error comment in dev, empty string in production
52
+
53
+ **Processing order**: Runs after `processCollectionBlocks` and `processViewBlocks`, before `processDConfigBlocks`.
54
+ Insert at line ~1621 in the pipeline.
55
+
56
+ ### Reused functions (no changes needed)
57
+
58
+ - `loadBlockTemplate(name)` - `markdown.js:52`
59
+ - `escapeHtmlText(str)` - already used throughout
60
+ - `escapeAttr(str)` - for attribute parsing
61
+
62
+ ---
63
+
64
+ ## Insert Menu Integration
65
+
66
+ ### Toolbar Registration
67
+
68
+ **File**: `admin/js/lib/markdown-toolbar.js`
69
+
70
+ 1. Add new item to the Insert dropdown (line 937-955), between "Badge" and "Button" (alphabetical):
71
+ ```js
72
+ {label: 'Block', icon: 'layout', action: 'block'},
73
+ ```
74
+
75
+ 2. Add handler registration method alongside existing ones (near line 1135):
76
+ ```js
77
+ onBlock(cb) { handlers.block = cb; },
78
+ ```
79
+
80
+ 3. Add `'block'` case to `executeAction()` that calls the registered handler.
81
+
82
+ ### Modal Handler
83
+
84
+ **File**: `admin/js/views/page-editor.js`
85
+
86
+ Register via `toolbar.onBlock(async (ta) => { ... })`.
87
+
88
+ **Modal layout**: Two-pane - inputs on left, live preview on right.
89
+
90
+ **Flow**:
91
+
92
+ 1. Fetch block list via `api.blocks.list()`
93
+ 2. Show template picker dropdown
94
+ 3. On template selection:
95
+ - Fetch template HTML via `api.blocks.get(name)`
96
+ - Extract placeholders using same regex as block-editor: `/\{\{([\w_]+)\}\}/g`
97
+ - Filter out meta placeholders (`_id`, `_createdAt`, `_updatedAt`) - not relevant for static data
98
+ - Generate labelled input fields for each placeholder
99
+ 4. Live preview panel:
100
+ - On input change, run `{{placeholder}}` replacement client-side with current field values
101
+ - Display rendered HTML preview (same approach as `block-editor.js` line 202-245)
102
+ 5. **Insert** button:
103
+ - Build shortcode string: `[block template="name" field1="val1" field2="val2" /]`
104
+ - Escape double quotes in values (replace `"` with `&quot;` in attribute values)
105
+ - Call `insertAtCursor(ta, shortcode)`
106
+ - Close modal, dispatch `input` event
107
+
108
+ **Placeholder-to-label conversion**: `featured_image` → `Featured Image` (split on `_`, capitalise each word) - same
109
+ pattern as block-editor.js `refreshSampleForm`.
110
+
111
+ ---
112
+
113
+ ## CSS
114
+
115
+ No new CSS required. The `<div class="dm-static-block">` wrapper is intentionally minimal - the block template itself
116
+ contains all styling (using Domma card classes, etc.). This is consistent with how collection blocks work.
117
+
118
+ ---
119
+
120
+ ## Documentation
121
+
122
+ **File**: `docs/markdown-shortcodes.md`
123
+
124
+ Add a new section for the `[block]` shortcode with syntax, attributes, and examples. Place it near the `[collection]`
125
+ documentation since they share the block template concept.
126
+
127
+ ---
128
+
129
+ ## Files Modified
130
+
131
+ | File | Change |
132
+ |------------------------------------|--------------------------------------------------------------------------------------|
133
+ | `server/services/markdown.js` | Add `processStaticBlocks()`, add to pipeline |
134
+ | `admin/js/lib/markdown-toolbar.js` | Add "Block" to Insert dropdown, `onBlock` handler registration, `executeAction` case |
135
+ | `admin/js/views/page-editor.js` | Add `toolbar.onBlock()` modal handler with live preview |
136
+ | `docs/markdown-shortcodes.md` | Document `[block]` shortcode |
137
+
138
+ **No new files created. No new APIs. No new CSS files.**
139
+
140
+ ---
141
+
142
+ ## Verification
143
+
144
+ 1. **Server rendering**: Create a test page with
145
+ `[block template="contact-card" full_name="Jane Doe" phone_number="555-1234" email_address="jane@example.com" /]` and
146
+ verify it renders the contact-card template with substituted values
147
+ 2. **Missing template**: Use a non-existent template name and verify graceful handling (no crash)
148
+ 3. **Missing placeholders**: Omit some fields and verify they render as empty strings
149
+ 4. **XSS prevention**: Include `<script>alert('xss')</script>` in a field value and verify it's escaped
150
+ 5. **Insert modal**: Open page editor → Insert → Block → select a template → verify fields appear → fill in data →
151
+ verify live preview → click Insert → verify shortcode inserted in editor
152
+ 6. **Live preview**: Confirm the editor's split-pane preview renders the block correctly after insertion
153
+ 7. **Existing blocks unaffected**: Verify `[collection ... display="block" ...]` still works as before