domma-cms 1.6.59 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4344 -0
- package/README.md +1 -1
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +2 -2
- package/admin/js/lib/draft-owner.js +1 -0
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +2 -2
- package/admin/js/lib/page-editor-reference.js +5 -5
- package/admin/js/lib/shortcode-tags.js +1 -1
- package/admin/js/templates/docs/usage-shortcodes.html +39 -0
- package/admin/js/views/user-editor.js +1 -1
- package/docs/DommaFeatures.md +3005 -0
- package/docs/MultiTenant.md +176 -0
- package/docs/api-reference.md +1667 -0
- package/docs/cache.md +96 -0
- package/docs/configuration.md +1811 -0
- package/docs/editions-and-licences.md +190 -0
- package/docs/getting-started.md +201 -0
- package/docs/guides/form-triggers.md +260 -0
- package/docs/guides/mongodb-storage-adapter.md +282 -0
- package/docs/guides/multi-page-forms.md +121 -0
- package/docs/images/scribe-composer.png +0 -0
- package/docs/markdown-shortcodes.md +1738 -0
- package/docs/pentest-report-2026-04-03.md +181 -0
- package/docs/plans/2026-02-28-admin-topbar-login-forms.md +349 -0
- package/docs/plans/2026-02-28-domma-ui-polish.md +286 -0
- package/docs/plans/2026-02-28-reset-seed-fresh-commands.md +520 -0
- package/docs/plans/2026-03-02-cookie-consent-design.md +86 -0
- package/docs/plans/2026-03-02-cookie-consent-plugin.md +453 -0
- package/docs/plans/2026-03-02-tooltip-sweep-and-back-to-top-plugin-design.md +126 -0
- package/docs/plans/2026-03-02-tooltip-sweep-back-to-top.md +427 -0
- package/docs/plans/2026-03-05-cookie-consent-btt-core.md +115 -0
- package/docs/plans/2026-03-05-custom-css-migrate-to-core-design.md +35 -0
- package/docs/plans/2026-03-05-custom-css-migrate-to-core.md +352 -0
- package/docs/plans/2026-03-05-scribe-script-shortcode.md +373 -0
- package/docs/plans/2026-03-05-user-types-design.md +139 -0
- package/docs/plans/2026-03-05-user-types-implementation.md +1035 -0
- package/docs/plans/2026-03-09-page-editor-view-cta-inserts-design.md +78 -0
- package/docs/plans/2026-03-09-page-editor-view-cta-inserts.md +388 -0
- package/docs/plans/2026-03-09-pro-documentation-design.md +80 -0
- package/docs/plans/2026-03-09-pro-documentation.md +1017 -0
- package/docs/plans/2026-06-10-core-project-design.md +240 -0
- package/docs/plugin-development.md +827 -0
- package/docs/recruitment-recipe.md +501 -0
- package/docs/scaffolding.md +312 -0
- package/docs/screenshots/calculator-floating.png +0 -0
- package/docs/screenshots/calculator-standard.png +0 -0
- package/docs/screenshots/email-settings.png +0 -0
- package/docs/screenshots/messages-count.png +0 -0
- package/docs/structure/user-types.md +107 -0
- package/docs/superpowers/plans/2026-05-25-admin-sidebar-via-menus.md +1112 -0
- package/docs/superpowers/plans/2026-06-08-navbar-mobile-drawer-and-menu-enrichment.md +1161 -0
- package/docs/superpowers/plans/2026-06-25-domma-docs-project.md +695 -0
- package/docs/superpowers/plans/2026-06-26-surveys-HANDOFF.md +68 -0
- package/docs/superpowers/plans/2026-06-26-surveys-pro-plugin.md +1902 -0
- package/docs/superpowers/plans/2026-08-19-collection-context-sort-export.md +1577 -0
- package/docs/superpowers/plans/2026-10-09-content-api.md +1192 -0
- package/docs/superpowers/specs/2026-03-12-form-followup-actions-design.md +152 -0
- package/docs/superpowers/specs/2026-03-12-page-editor-form-insert-and-per-page-theme-design.md +115 -0
- package/docs/superpowers/specs/2026-03-17-nav-hidden-items-design.md +85 -0
- package/docs/superpowers/specs/2026-03-19-accordion-insert-design.md +175 -0
- package/docs/superpowers/specs/2026-03-25-static-block-shortcode-design.md +153 -0
- package/docs/superpowers/specs/2026-03-25-text-shortcode-design.md +168 -0
- package/docs/superpowers/specs/2026-04-05-card-builder-design.md +315 -0
- package/docs/superpowers/specs/2026-04-06-insert-builders-design.md +278 -0
- package/docs/superpowers/specs/2026-04-06-layouts-design.md +226 -0
- package/docs/superpowers/specs/2026-04-07-blog-plugin-design.md +300 -0
- package/docs/superpowers/specs/2026-04-07-calendar-plugin-design.md +538 -0
- package/docs/superpowers/specs/2026-04-17-components-area-design.md +368 -0
- package/docs/superpowers/specs/2026-05-02-effects-slideover-overhaul-design.md +193 -0
- package/docs/superpowers/specs/2026-05-06-dashboard-rebuild-design.md +257 -0
- package/docs/superpowers/specs/2026-05-24-menus-design.md +466 -0
- package/docs/superpowers/specs/2026-05-25-admin-sidebar-via-menus-design.md +358 -0
- package/docs/superpowers/specs/2026-05-25-projects-design.md +380 -0
- package/docs/superpowers/specs/2026-06-08-navbar-mobile-drawer-and-menu-enrichment-design.md +292 -0
- package/docs/superpowers/specs/2026-06-08-sidebar-tools-plugins-folders-design.md +59 -0
- package/docs/superpowers/specs/2026-06-25-domma-docs-project-design.md +192 -0
- package/docs/superpowers/specs/2026-06-26-domma-charts-upstream-proposal.md +44 -0
- package/docs/superpowers/specs/2026-06-26-manager-scheduler-upstream-proposal.md +46 -0
- package/docs/superpowers/specs/2026-06-26-surveys-pro-plugin-design.md +206 -0
- package/docs/superpowers/specs/2026-07-02-shortcode-context-menu-design.md +96 -0
- package/docs/superpowers/specs/2026-08-19-collection-context-sort-export-design.md +330 -0
- package/docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md +1150 -0
- package/docs/superpowers/specs/2026-10-09-content-api-design.md +325 -0
- package/docs/superpowers/specs/plans/2026-03-12-form-followup-actions.md +624 -0
- package/docs/superpowers/specs/plans/2026-03-12-page-editor-form-insert-and-per-page-theme.md +125 -0
- package/docs/superpowers/specs/plans/2026-03-17-nav-hidden-items.md +578 -0
- package/docs/superpowers/specs/plans/2026-03-19-accordion-insert.md +635 -0
- package/docs/superpowers/specs/plans/2026-03-25-text-shortcode.md +545 -0
- package/docs/superpowers/specs/plans/2026-04-05-card-builder.md +1970 -0
- package/docs/superpowers/specs/plans/2026-04-06-insert-builders.md +1049 -0
- package/docs/superpowers/specs/plans/2026-04-06-layouts.md +1015 -0
- package/docs/superpowers/specs/plans/2026-04-07-calendar-plugin.md +1526 -0
- package/docs/superpowers/specs/plans/2026-04-08-plugin-monetisation-split.md +242 -0
- package/docs/superpowers/specs/plans/2026-04-17-components-area.md +2038 -0
- package/docs/superpowers/specs/plans/2026-05-02-effects-slideover-overhaul.md +2335 -0
- package/docs/superpowers/specs/plans/2026-05-06-dashboard-rebuild.md +1999 -0
- package/docs/superpowers/specs/plans/2026-05-17-cache-system.md +1436 -0
- package/docs/superpowers/specs/plans/2026-05-24-menus.md +3096 -0
- package/docs/superpowers/specs/plans/2026-05-25-projects.md +2869 -0
- package/docs/superpowers/specs/plans/2026-09-22-marketplace-step2-consolidation.md +505 -0
- package/docs/superpowers/specs/plans/2026-09-22-marketplace-step3-catalogue-and-key.md +154 -0
- package/docs/superpowers/specs/plans/2026-09-22-tools-promotion.md +260 -0
- package/docs/theming.md +320 -0
- package/package.json +2 -2
- package/plugins/_lib/admin/mail/keyboard.js +49 -0
- package/plugins/_lib/admin/mail/reader-view.js +4 -2
- package/public/js/celebrations/core/canvas.js +1 -1
- package/public/js/celebrations/index.js +1 -1
- package/public/js/celebrations/licenses/ABeeZee-Regular-OFL.txt +93 -0
- package/public/js/celebrations/licenses/Allura-Regular-OFL.txt +93 -0
- package/public/js/celebrations/message-attrs.mjs +1 -0
- package/public/js/celebrations/themes/anniversary.js +1 -0
- package/public/js/celebrations/themes/autumn.js +1 -0
- package/public/js/celebrations/themes/birthday.js +1 -0
- package/public/js/celebrations/themes/butterfly-flight.js +1 -0
- package/public/js/celebrations/themes/calendar.js +1 -0
- package/public/js/celebrations/themes/chinese-new-year-dates.js +1 -0
- package/public/js/celebrations/themes/chinese-new-year.js +1 -0
- package/public/js/celebrations/themes/christmas.js +1 -1
- package/public/js/celebrations/themes/easter.js +1 -0
- package/public/js/celebrations/themes/garden-drawing.js +1 -0
- package/public/js/celebrations/themes/garden.js +1 -0
- package/public/js/celebrations/themes/graduation.js +1 -0
- package/public/js/celebrations/themes/greeting.js +1 -0
- package/public/js/celebrations/themes/make-it-rain.js +1 -0
- package/public/js/celebrations/themes/money-rain.js +1 -0
- package/public/js/celebrations/themes/new-year.js +1 -0
- package/public/js/celebrations/themes/occasion-drawing.js +1 -0
- package/public/js/celebrations/themes/occasions.js +1 -0
- package/public/js/celebrations/themes/rain-control.js +1 -0
- package/public/js/celebrations/themes/sparkler-font-data.js +1 -0
- package/public/js/celebrations/themes/sparkler-font.js +1 -0
- package/public/js/celebrations/themes/sparkler-writing.js +1 -0
- package/public/js/celebrations/themes/summer-garden.js +1 -0
- package/public/js/celebrations/themes/winter.js +1 -0
- package/public/js/celebrations/trait-data.mjs +1 -1
- package/public/js/effects.js +1 -1
- package/scripts/build.js +4 -0
- package/server/services/markdown.js +5 -2
- package/server/templates/page.html +1 -1
|
@@ -0,0 +1,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 |
|
package/docs/superpowers/specs/2026-03-12-page-editor-form-insert-and-per-page-theme-design.md
ADDED
|
@@ -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 `"` 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
|