domma-cms 1.6.60 → 1.7.1

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 (168) hide show
  1. package/CHANGELOG.md +4349 -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/mail-compose.js +1 -0
  8. package/admin/js/lib/page-editor-reference.js +5 -5
  9. package/admin/js/lib/shortcode-tags.js +1 -1
  10. package/admin/js/templates/call-new.html +1 -1
  11. package/admin/js/templates/docs/usage-shortcodes.html +39 -0
  12. package/admin/js/views/call-new.js +1 -1
  13. package/admin/js/views/contacts.js +15 -15
  14. package/admin/js/views/form-submissions.js +2 -2
  15. package/docs/DommaFeatures.md +3005 -0
  16. package/docs/MultiTenant.md +176 -0
  17. package/docs/api-reference.md +1667 -0
  18. package/docs/cache.md +96 -0
  19. package/docs/configuration.md +1811 -0
  20. package/docs/editions-and-licences.md +190 -0
  21. package/docs/getting-started.md +201 -0
  22. package/docs/guides/form-triggers.md +260 -0
  23. package/docs/guides/mongodb-storage-adapter.md +282 -0
  24. package/docs/guides/multi-page-forms.md +121 -0
  25. package/docs/images/scribe-composer.png +0 -0
  26. package/docs/markdown-shortcodes.md +1738 -0
  27. package/docs/pentest-report-2026-04-03.md +181 -0
  28. package/docs/plans/2026-02-28-admin-topbar-login-forms.md +349 -0
  29. package/docs/plans/2026-02-28-domma-ui-polish.md +286 -0
  30. package/docs/plans/2026-02-28-reset-seed-fresh-commands.md +520 -0
  31. package/docs/plans/2026-03-02-cookie-consent-design.md +86 -0
  32. package/docs/plans/2026-03-02-cookie-consent-plugin.md +453 -0
  33. package/docs/plans/2026-03-02-tooltip-sweep-and-back-to-top-plugin-design.md +126 -0
  34. package/docs/plans/2026-03-02-tooltip-sweep-back-to-top.md +427 -0
  35. package/docs/plans/2026-03-05-cookie-consent-btt-core.md +115 -0
  36. package/docs/plans/2026-03-05-custom-css-migrate-to-core-design.md +35 -0
  37. package/docs/plans/2026-03-05-custom-css-migrate-to-core.md +352 -0
  38. package/docs/plans/2026-03-05-scribe-script-shortcode.md +373 -0
  39. package/docs/plans/2026-03-05-user-types-design.md +139 -0
  40. package/docs/plans/2026-03-05-user-types-implementation.md +1035 -0
  41. package/docs/plans/2026-03-09-page-editor-view-cta-inserts-design.md +78 -0
  42. package/docs/plans/2026-03-09-page-editor-view-cta-inserts.md +388 -0
  43. package/docs/plans/2026-03-09-pro-documentation-design.md +80 -0
  44. package/docs/plans/2026-03-09-pro-documentation.md +1017 -0
  45. package/docs/plans/2026-06-10-core-project-design.md +240 -0
  46. package/docs/plugin-development.md +827 -0
  47. package/docs/recruitment-recipe.md +501 -0
  48. package/docs/scaffolding.md +312 -0
  49. package/docs/screenshots/calculator-floating.png +0 -0
  50. package/docs/screenshots/calculator-standard.png +0 -0
  51. package/docs/screenshots/email-settings.png +0 -0
  52. package/docs/screenshots/messages-count.png +0 -0
  53. package/docs/structure/user-types.md +107 -0
  54. package/docs/superpowers/plans/2026-05-25-admin-sidebar-via-menus.md +1112 -0
  55. package/docs/superpowers/plans/2026-06-08-navbar-mobile-drawer-and-menu-enrichment.md +1161 -0
  56. package/docs/superpowers/plans/2026-06-25-domma-docs-project.md +695 -0
  57. package/docs/superpowers/plans/2026-06-26-surveys-HANDOFF.md +68 -0
  58. package/docs/superpowers/plans/2026-06-26-surveys-pro-plugin.md +1902 -0
  59. package/docs/superpowers/plans/2026-08-19-collection-context-sort-export.md +1577 -0
  60. package/docs/superpowers/plans/2026-10-09-content-api.md +1192 -0
  61. package/docs/superpowers/specs/2026-03-12-form-followup-actions-design.md +152 -0
  62. package/docs/superpowers/specs/2026-03-12-page-editor-form-insert-and-per-page-theme-design.md +115 -0
  63. package/docs/superpowers/specs/2026-03-17-nav-hidden-items-design.md +85 -0
  64. package/docs/superpowers/specs/2026-03-19-accordion-insert-design.md +175 -0
  65. package/docs/superpowers/specs/2026-03-25-static-block-shortcode-design.md +153 -0
  66. package/docs/superpowers/specs/2026-03-25-text-shortcode-design.md +168 -0
  67. package/docs/superpowers/specs/2026-04-05-card-builder-design.md +315 -0
  68. package/docs/superpowers/specs/2026-04-06-insert-builders-design.md +278 -0
  69. package/docs/superpowers/specs/2026-04-06-layouts-design.md +226 -0
  70. package/docs/superpowers/specs/2026-04-07-blog-plugin-design.md +300 -0
  71. package/docs/superpowers/specs/2026-04-07-calendar-plugin-design.md +538 -0
  72. package/docs/superpowers/specs/2026-04-17-components-area-design.md +368 -0
  73. package/docs/superpowers/specs/2026-05-02-effects-slideover-overhaul-design.md +193 -0
  74. package/docs/superpowers/specs/2026-05-06-dashboard-rebuild-design.md +257 -0
  75. package/docs/superpowers/specs/2026-05-24-menus-design.md +466 -0
  76. package/docs/superpowers/specs/2026-05-25-admin-sidebar-via-menus-design.md +358 -0
  77. package/docs/superpowers/specs/2026-05-25-projects-design.md +380 -0
  78. package/docs/superpowers/specs/2026-06-08-navbar-mobile-drawer-and-menu-enrichment-design.md +292 -0
  79. package/docs/superpowers/specs/2026-06-08-sidebar-tools-plugins-folders-design.md +59 -0
  80. package/docs/superpowers/specs/2026-06-25-domma-docs-project-design.md +192 -0
  81. package/docs/superpowers/specs/2026-06-26-domma-charts-upstream-proposal.md +44 -0
  82. package/docs/superpowers/specs/2026-06-26-manager-scheduler-upstream-proposal.md +46 -0
  83. package/docs/superpowers/specs/2026-06-26-surveys-pro-plugin-design.md +206 -0
  84. package/docs/superpowers/specs/2026-07-02-shortcode-context-menu-design.md +96 -0
  85. package/docs/superpowers/specs/2026-08-19-collection-context-sort-export-design.md +330 -0
  86. package/docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md +1150 -0
  87. package/docs/superpowers/specs/2026-10-09-content-api-design.md +325 -0
  88. package/docs/superpowers/specs/plans/2026-03-12-form-followup-actions.md +624 -0
  89. package/docs/superpowers/specs/plans/2026-03-12-page-editor-form-insert-and-per-page-theme.md +125 -0
  90. package/docs/superpowers/specs/plans/2026-03-17-nav-hidden-items.md +578 -0
  91. package/docs/superpowers/specs/plans/2026-03-19-accordion-insert.md +635 -0
  92. package/docs/superpowers/specs/plans/2026-03-25-text-shortcode.md +545 -0
  93. package/docs/superpowers/specs/plans/2026-04-05-card-builder.md +1970 -0
  94. package/docs/superpowers/specs/plans/2026-04-06-insert-builders.md +1049 -0
  95. package/docs/superpowers/specs/plans/2026-04-06-layouts.md +1015 -0
  96. package/docs/superpowers/specs/plans/2026-04-07-calendar-plugin.md +1526 -0
  97. package/docs/superpowers/specs/plans/2026-04-08-plugin-monetisation-split.md +242 -0
  98. package/docs/superpowers/specs/plans/2026-04-17-components-area.md +2038 -0
  99. package/docs/superpowers/specs/plans/2026-05-02-effects-slideover-overhaul.md +2335 -0
  100. package/docs/superpowers/specs/plans/2026-05-06-dashboard-rebuild.md +1999 -0
  101. package/docs/superpowers/specs/plans/2026-05-17-cache-system.md +1436 -0
  102. package/docs/superpowers/specs/plans/2026-05-24-menus.md +3096 -0
  103. package/docs/superpowers/specs/plans/2026-05-25-projects.md +2869 -0
  104. package/docs/superpowers/specs/plans/2026-09-22-marketplace-step2-consolidation.md +505 -0
  105. package/docs/superpowers/specs/plans/2026-09-22-marketplace-step3-catalogue-and-key.md +154 -0
  106. package/docs/superpowers/specs/plans/2026-09-22-tools-promotion.md +260 -0
  107. package/docs/theming.md +320 -0
  108. package/package.json +2 -2
  109. package/plugins/_lib/admin/mail/compose-window.js +3 -1
  110. package/plugins/_lib/admin/mail/identity.js +18 -3
  111. package/plugins/_lib/admin/mail/reader-view.js +32 -3
  112. package/plugins/blog/CLAUDE.md +4 -0
  113. package/plugins/blog/admin/lib/email.js +191 -0
  114. package/plugins/blog/admin/views/comments.js +3 -1
  115. package/plugins/blog/plugin.json +2 -2
  116. package/plugins/blog/plugin.public.js +1 -1
  117. package/plugins/blog/tests/email.test.js +69 -0
  118. package/plugins/booking/CLAUDE.md +4 -0
  119. package/plugins/booking/admin/lib/email.js +191 -0
  120. package/plugins/booking/admin/views/panels.js +7 -3
  121. package/plugins/booking/plugin.json +1 -1
  122. package/plugins/booking/tests/email.test.js +69 -0
  123. package/plugins/free-tier.lock.json +27 -19
  124. package/plugins/shopping-cart/CLAUDE.md +4 -0
  125. package/plugins/shopping-cart/admin/lib/email.js +191 -0
  126. package/plugins/shopping-cart/admin/views/orders.js +11 -3
  127. package/plugins/shopping-cart/plugin.json +2 -2
  128. package/plugins/shopping-cart/tests/email.test.js +69 -0
  129. package/plugins/support/CLAUDE.md +4 -0
  130. package/plugins/support/admin/lib/email.js +191 -0
  131. package/plugins/support/admin/views/desk.js +4 -1
  132. package/plugins/support/admin/views/inbox.js +6 -2
  133. package/plugins/support/plugin.json +2 -2
  134. package/plugins/support/tests/email.test.js +69 -0
  135. package/public/js/celebrations/core/canvas.js +1 -1
  136. package/public/js/celebrations/index.js +1 -1
  137. package/public/js/celebrations/licenses/ABeeZee-Regular-OFL.txt +93 -0
  138. package/public/js/celebrations/licenses/Allura-Regular-OFL.txt +93 -0
  139. package/public/js/celebrations/message-attrs.mjs +1 -0
  140. package/public/js/celebrations/themes/anniversary.js +1 -0
  141. package/public/js/celebrations/themes/autumn.js +1 -0
  142. package/public/js/celebrations/themes/birthday.js +1 -0
  143. package/public/js/celebrations/themes/butterfly-flight.js +1 -0
  144. package/public/js/celebrations/themes/calendar.js +1 -0
  145. package/public/js/celebrations/themes/chinese-new-year-dates.js +1 -0
  146. package/public/js/celebrations/themes/chinese-new-year.js +1 -0
  147. package/public/js/celebrations/themes/christmas.js +1 -1
  148. package/public/js/celebrations/themes/easter.js +1 -0
  149. package/public/js/celebrations/themes/garden-drawing.js +1 -0
  150. package/public/js/celebrations/themes/garden.js +1 -0
  151. package/public/js/celebrations/themes/graduation.js +1 -0
  152. package/public/js/celebrations/themes/greeting.js +1 -0
  153. package/public/js/celebrations/themes/make-it-rain.js +1 -0
  154. package/public/js/celebrations/themes/money-rain.js +1 -0
  155. package/public/js/celebrations/themes/new-year.js +1 -0
  156. package/public/js/celebrations/themes/occasion-drawing.js +1 -0
  157. package/public/js/celebrations/themes/occasions.js +1 -0
  158. package/public/js/celebrations/themes/rain-control.js +1 -0
  159. package/public/js/celebrations/themes/sparkler-font-data.js +1 -0
  160. package/public/js/celebrations/themes/sparkler-font.js +1 -0
  161. package/public/js/celebrations/themes/sparkler-writing.js +1 -0
  162. package/public/js/celebrations/themes/summer-garden.js +1 -0
  163. package/public/js/celebrations/themes/winter.js +1 -0
  164. package/public/js/celebrations/trait-data.mjs +1 -1
  165. package/public/js/effects.js +1 -1
  166. package/scripts/build.js +4 -0
  167. package/server/services/markdown.js +5 -2
  168. package/server/templates/page.html +1 -1
@@ -0,0 +1,278 @@
1
+ # Insert Builders - Tabs, Hero, Banner
2
+
3
+ **Date:** 2026-04-06
4
+ **Status:** Approved
5
+ **Scope:** Admin page editor - Insert dropdown enhancements
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ Three improvements to the Page Editor Insert toolbar:
12
+
13
+ 1. **Tabs** - upgrade the existing basic modal to a richer, field-organised dialog with live shortcode preview
14
+ 2. **Hero** - wire up the currently dead `onHero` handler with a full-featured insert dialog
15
+ 3. **Banner** - new `[banner]` shortcode (server-side + CSS + insert dialog + toolbar item)
16
+
17
+ A shared utility module (`shortcode-modal.js`) provides UI primitives for the new and upgraded handlers. Each of
18
+ `onTabs` (upgraded), `onHero` (new), and `onBanner` (new) in `page-editor.js` will import from it. Note:
19
+ `markdown-toolbar.js` also has its own duplicated helpers inside its action-handler closures (for text, timeline,
20
+ etc.) - those are a separate concern and not in scope here.
21
+
22
+ ---
23
+
24
+ ## Architecture
25
+
26
+ ### Files
27
+
28
+ | File | Change |
29
+ |------------------------------------|--------------------------------------------------------------------|
30
+ | `admin/js/lib/shortcode-modal.js` | **New** - shared UI primitives |
31
+ | `admin/js/views/page-editor.js` | Upgrade `onTabs`, add `onHero`, add `onBanner` |
32
+ | `admin/js/lib/markdown-toolbar.js` | Add `banner` insert item, `onBanner` handler, `banner-insert` icon |
33
+ | `server/services/markdown.js` | Add `processBannerBlocks()` |
34
+ | `public/css/site.css` | Add `.dm-banner` styles and type variants |
35
+
36
+ ### Shared utility: `shortcode-modal.js`
37
+
38
+ Exports the following helpers. Imported by `page-editor.js` for the upgraded `onTabs` and new `onHero`/`onBanner`
39
+ handlers:
40
+
41
+ ```js
42
+ export function makeField(label, control) // label + control wrapper div
43
+ export function makeSelect(items) // styled <select>
44
+ export function makeTextInput(placeholder, val) // styled <input type="text">
45
+ export function makeCheckbox(label) // styled checkbox row
46
+ export function makeLivePreview() // returns { el, update(str) }
47
+ ```
48
+
49
+ `makeLivePreview()` returns a `<pre>` element that displays the current shortcode string and a helper `update(str)`
50
+ function to refresh it. Wired to field `input`/`change` events in each handler.
51
+
52
+ ---
53
+
54
+ ## Section 1: Tabs Builder
55
+
56
+ ### Handler: `onTabs` in `page-editor.js`
57
+
58
+ Replaces the existing basic handler. Opens an `E.modal({ title: 'Insert Tabs', size: 'sm' })`.
59
+
60
+ ### Fields
61
+
62
+ | Field | Control | Shortcode attribute |
63
+ |----------------|------------------------------------|--------------------------------------|
64
+ | Style | Select: Default / Pills / Bordered | `style="pills"` / `style="bordered"` |
65
+ | Alignment | Select: Left / Centre / Right | `align="center"` / `align="right"` |
66
+ | Animation | Select: None / Fade | `fade` (boolean flag) |
67
+ | Multiple open | Checkbox | `multiple="true"` |
68
+ | Tab list | Dynamic rows (title + icon inputs) | `[tab title="..." icon="..."]` |
69
+ | Add Tab button | - | max 10 tabs, min 2 |
70
+ | Live preview | `<pre>` box | - |
71
+
72
+ ### Tab rows
73
+
74
+ Each tab row contains:
75
+
76
+ - Text input: tab title (required, placeholder "Tab title")
77
+ - Text input: icon name (optional, placeholder "icon (optional)")
78
+ - Remove button (disabled when ≤2 tabs)
79
+
80
+ Default state: 2 tabs pre-populated ("Overview", "Details").
81
+
82
+ ### Shortcode output
83
+
84
+ ```
85
+ [tabs style="pills" align="center" fade]
86
+ [tab title="Overview" icon="home"]Tab content here.[/tab]
87
+ [tab title="Details"]Tab content here.[/tab]
88
+ [/tabs]
89
+ ```
90
+
91
+ Omit attributes that are default/empty (e.g. no `style` attribute if "Default" selected, no `icon` if blank).
92
+
93
+ ---
94
+
95
+ ## Section 2: Hero Builder
96
+
97
+ ### Handler: `onHero` in `page-editor.js`
98
+
99
+ The toolbar item `{label: 'Hero', icon: 'hero', action: 'hero'}` **already exists** in the `markdown-toolbar.js` items
100
+ array, and the dispatch `if (handlers.hero) handlers.hero(ta)` already exists in the action switch. What is missing is
101
+ the handler registration in `page-editor.js` - so clicking Insert → Hero currently does nothing. This task adds only the
102
+ `page-editor.js` handler; no changes to `markdown-toolbar.js` are required for Hero.
103
+
104
+ Opens `E.modal({ title: 'Insert Hero', size: 'sm' })`.
105
+
106
+ ### Fields
107
+
108
+ | Field | Control | Shortcode attribute |
109
+ |--------------|---------------------------------------|-------------------------------------|
110
+ | Title | Text input (required) | `title="..."` |
111
+ | Tagline | Text input (optional) | `tagline="..."` |
112
+ | Size | Select: Small / Medium / Large / Full | `size="sm/md/lg/full"` |
113
+ | Alignment | Select: Left / Centre / Right | `align="center"` |
114
+ | Background | Radio: None / Colour / Image | - |
115
+ | → Colour | Text input (hex/CSS) | `color="..."` - shown when Colour |
116
+ | → Image path | Text input | `image="..."` - shown when Image |
117
+ | → Overlay | Select: None / Dark / Light | `overlay="dark"` - shown when Image |
118
+ | Min height | Text input (optional, e.g. `400px`) | `min-height="..."` |
119
+ | Live preview | `<pre>` box | - |
120
+
121
+ The Background radio group conditionally shows/hides Colour and Image fields. When "None" is selected, both are hidden.
122
+
123
+ ### Shortcode output
124
+
125
+ ```
126
+ [hero title="Our Story" tagline="Building something great" size="lg" align="center" image="/media/hero.jpg" overlay="dark"]
127
+ Content here.
128
+ [/hero]
129
+ ```
130
+
131
+ Omit attributes that are default/empty.
132
+
133
+ ---
134
+
135
+ ## Section 3: Banner Shortcode
136
+
137
+ ### 3a. Server side - `markdown.js`
138
+
139
+ Add `processBannerBlocks(content)` function. The existing pipeline order in `parseMarkdown` is:
140
+
141
+ ```
142
+ processCollectionBlocks → processViewBlocks → processStaticBlocks
143
+ → processDConfigBlocks → processGridBlocks → processCardBlocks
144
+ → processSlideoverBlocks
145
+ ```
146
+
147
+ Insert `processBannerBlocks` **after `processCardBlocks` and before `processSlideoverBlocks`** - this ensures banners
148
+ render inside grid columns but can also sit inside a slideover body. Nesting banners inside card bodies is out of scope
149
+ for the initial implementation (card body rendering would need a separate call to `processBannerBlocks` on the inner
150
+ content).
151
+
152
+ **Syntax:**
153
+
154
+ ```
155
+ [banner type="info" title="Note" icon="info" dismissible]
156
+ Read this message.
157
+ [/banner]
158
+ ```
159
+
160
+ **Attributes:**
161
+
162
+ | Attribute | Values | Default |
163
+ |---------------|-------------------------------------------------------|---------|
164
+ | `type` | `info` / `success` / `warning` / `danger` / `neutral` | `info` |
165
+ | `title` | String | - |
166
+ | `icon` | Domma icon name | - |
167
+ | `dismissible` | Boolean flag | false |
168
+ | `class` | Extra CSS class | - |
169
+
170
+ **Rendered HTML:**
171
+
172
+ ```html
173
+ <div class="dm-banner dm-banner--info [extra-class]">
174
+ <span class="dm-banner__icon" data-icon="info"></span>
175
+ <div class="dm-banner__body">
176
+ <strong class="dm-banner__title">Note</strong>
177
+ <p><!-- body content (Markdown parsed) --></p>
178
+ </div>
179
+ <button class="dm-banner__dismiss" aria-label="Dismiss">×</button>
180
+ </div>
181
+ ```
182
+
183
+ - `dm-banner__icon` omitted if no `icon` attribute
184
+ - `dm-banner__title` omitted if no `title` attribute
185
+ - `dm-banner__dismiss` omitted if `dismissible` not set
186
+
187
+ ### 3b. CSS - `public/css/site.css`
188
+
189
+ Five type variants using Domma CSS custom properties. Base styles cover layout (flex row, aligned icon + body),
190
+ border-left accent, padding, border-radius. Each `--info/success/warning/danger/neutral` variant sets background tint
191
+ and accent colour.
192
+
193
+ `.dm-banner` base style must include `position: relative` for the dismiss button's absolute positioning to work. Dismiss
194
+ button: `position: absolute; top: 0.5rem; right: 0.5rem;` - hides banner on click via `site.js`.
195
+
196
+ ### 3c. Client dismiss handler - `public/js/site.js`
197
+
198
+ Add event delegation for `.dm-banner__dismiss` clicks:
199
+
200
+ ```js
201
+ $(document).on('click', '.dm-banner__dismiss', function() {
202
+ $(this).closest('.dm-banner').remove();
203
+ });
204
+ ```
205
+
206
+ Note: `.closest()` is a standard Domma (jQuery-compatible) method - confirmed available.
207
+
208
+ ### 3d. Toolbar - `markdown-toolbar.js`
209
+
210
+ Three changes:
211
+
212
+ 1. **Register icon** - new `banner-insert` SVG (horizontal strip with lines, consistent with toolbar icon style)
213
+ 2. **Add insert item** to the items array (alphabetical order between `Accordion` and `Badge`):
214
+ ```js
215
+ {label: 'Banner', icon: 'banner-insert', action: 'banner'},
216
+ ```
217
+ 3. **Add dispatch** in the action switch:
218
+ ```js
219
+ } else if (action === 'banner') {
220
+ if (handlers.banner) handlers.banner(ta);
221
+ }
222
+ ```
223
+ 4. **Add `onBanner` to the returned handler chain and JSDoc**
224
+
225
+ ### 3e. Insert dialog - `onBanner` in `page-editor.js`
226
+
227
+ Opens `E.modal({ title: 'Insert Banner', size: 'sm' })`.
228
+
229
+ | Field | Control |
230
+ |--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
231
+ | Type | Segmented button group (5 buttons, each with a coloured dot + label) - not a `<select>`, since `<option>` colour styling is browser-inconsistent. Active button gets `.active` class. |
232
+ | Title | Text input (optional) |
233
+ | Icon | Text input (optional, Domma icon name) |
234
+ | Dismissible | Checkbox |
235
+ | Live preview | `<pre>` box |
236
+
237
+ **Shortcode output:**
238
+
239
+ ```
240
+ [banner type="success" title="All done" icon="check-circle" dismissible]
241
+ Banner content here.
242
+ [/banner]
243
+ ```
244
+
245
+ ---
246
+
247
+ ## Data Flow
248
+
249
+ ```
250
+ User clicks Insert → Banner
251
+ → onBanner(ta) fires
252
+ → E.modal opens with fields
253
+ → fields change → live preview updates
254
+ → user clicks Insert Banner
255
+ → shortcode inserted at cursor in textarea
256
+ → preview re-renders via existing debounced preview call
257
+ → server processBannerBlocks() → HTML → CSS styles applied
258
+ ```
259
+
260
+ ---
261
+
262
+ ## Out of Scope
263
+
264
+ - Layout management (separate spec)
265
+ - Banner `action` link attribute (can be added in future)
266
+ - Banner nested inside card bodies (requires `processBannerBlocks` call inside card body render)
267
+ - Tabs per-tab body content editing within the dialog (user fills body content in the editor after insert)
268
+ - Hero video background (future enhancement)
269
+
270
+ ## In Scope - Server-side Tabs Rendering
271
+
272
+ The new `[tabs]` attributes (`style="bordered"`, `fade`, per-tab `icon`) must be rendered server-side or the insert
273
+ dialog generates attributes the server silently ignores - a misleading UX. `markdown.js` `processTabsBlocks()` (or
274
+ equivalent) must be updated to:
275
+
276
+ - `style="bordered"` → add `tabs--bordered` class to wrapper
277
+ - `fade` → add `tabs--fade` class to wrapper
278
+ - Per-tab `icon` attribute → render `<span data-icon="...">` before tab title text in the tab button
@@ -0,0 +1,226 @@
1
+ # Layout Presets - Design Spec
2
+
3
+ ## Goal
4
+
5
+ Extend the existing layout preset system so presets control page width, background colour, background image, and custom
6
+ CSS class - in addition to the existing navbar/footer/sidebar toggles. Add full CRUD in the admin UI (create, edit,
7
+ delete), with three built-in presets (default, landing, blank) that can be edited but not deleted.
8
+
9
+ ---
10
+
11
+ ## Data Model
12
+
13
+ Each layout preset is a keyed object stored in `config/presets.json` (via `getConfig('presets')` /
14
+ `saveConfig('presets', data)`).
15
+
16
+ **Full shape:**
17
+
18
+ ```json
19
+ {
20
+ "default": {
21
+ "key": "default",
22
+ "label": "Default",
23
+ "description": "Standard page with navbar and footer.",
24
+ "builtin": true,
25
+ "navbar": true,
26
+ "footer": true,
27
+ "sidebar": false,
28
+ "width": "normal",
29
+ "bgColor": "",
30
+ "bgImage": "",
31
+ "class": ""
32
+ }
33
+ }
34
+ ```
35
+
36
+ **Fields:**
37
+
38
+ | Field | Type | Description |
39
+ |---------------|---------|---------------------------------------------------------------------------------|
40
+ | `key` | string | Unique identifier (slug). Auto-generated from label on create. |
41
+ | `label` | string | Display name shown in the admin and the page editor dropdown. |
42
+ | `description` | string | Optional short description shown on the preset card. |
43
+ | `builtin` | boolean | If `true`, the preset cannot be deleted via the API. |
44
+ | `navbar` | boolean | Whether the public navbar renders on this layout. |
45
+ | `footer` | boolean | Whether the public footer renders on this layout. |
46
+ | `sidebar` | boolean | Whether a page sidebar region renders. |
47
+ | `width` | string | One of: `narrow`, `normal`, `wide`, `full`. Default: `normal`. |
48
+ | `bgColor` | string | CSS colour value for the page background (e.g. `#f5f5f5`). Empty = no override. |
49
+ | `bgImage` | string | URL for a background image. Empty = no override. |
50
+ | `class` | string | Space-separated extra CSS classes added to `<body>`. |
51
+
52
+ **Built-in presets** - seeded on server startup (see API section):
53
+
54
+ | Key | navbar | footer | sidebar | width |
55
+ |-----------|--------|--------|---------|--------|
56
+ | `default` | true | true | false | normal |
57
+ | `landing` | true | false | false | full |
58
+ | `blank` | false | false | false | normal |
59
+
60
+ ---
61
+
62
+ ## Server - API (`server/routes/api/layouts.js`)
63
+
64
+ **Existing routes kept as-is:**
65
+
66
+ - `GET /api/layouts` - returns all presets
67
+ - `PUT /api/layouts` - bulk-saves presets
68
+ - `GET /api/layouts/options` / `PUT /api/layouts/options` - spacer config (unchanged)
69
+
70
+ **New routes:**
71
+
72
+ | Method | Path | Purpose |
73
+ |----------|---------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
74
+ | `POST` | `/api/layouts` | Create a new preset. Validates: `label` required, `key` auto-generated (slugified from label), key must be unique, `builtin` always forced to `false`. |
75
+ | `PUT` | `/api/layouts/:key` | Update a single preset by key. All fields editable except `builtin`. Returns `404` if the key does not exist. |
76
+ | `DELETE` | `/api/layouts/:key` | Delete a preset. Returns `400` if `builtin: true`. Returns `404` if the key does not exist. |
77
+
78
+ **Key collision on `POST`:** If the slugified label produces a key that already exists, return `409` with
79
+ `{ error: 'A preset with this key already exists' }`. The client shows the error message in a toast and keeps the
80
+ slideover open.
81
+
82
+ **Validation rules:**
83
+
84
+ - `width` must be one of `narrow | normal | wide | full` (default `normal` if missing/invalid).
85
+ - `bgColor`: if non-empty, must match `/^[a-zA-Z0-9#(),.\s%/-]+$/`. This guard is intentionally permissive - it prevents
86
+ CSS injection, not invalid colour syntax.
87
+ - `bgImage`: stored as-is (URL); renderer applies `url(...)` wrapper.
88
+ - `class`: stripped of any characters outside `[a-zA-Z0-9\s_-]`.
89
+ - `label`: max 60 chars.
90
+ - `description`: max 200 chars.
91
+
92
+ **Built-in preset seeding:** `config/presets.json` is committed to the repository with the three built-in presets as its
93
+ initial content. On server startup (in `server/routes/api/layouts.js` route registration), a merge-in check runs: for
94
+ each of the three built-in keys (`default`, `landing`, `blank`), if the key is absent from the loaded config, it is
95
+ written in. This handles upgrades on existing installs without overwriting user edits. Do not duplicate this logic
96
+ elsewhere. `config/presets.json` must not be gitignored.
97
+
98
+ ---
99
+
100
+ ## Server - Renderer (`server/services/renderer.js`)
101
+
102
+ When assembling a public page, look up the page's `layout` frontmatter value against the presets config:
103
+
104
+ ```js
105
+ const presets = getConfig('presets') || {};
106
+ const preset = presets[page.layout] || presets['default'] || {};
107
+ ```
108
+
109
+ Apply the preset to the rendered HTML:
110
+
111
+ 1. **Width class** - add `dm-layout-{width}` to `<body>` classlist (e.g. `dm-layout-normal`). If `width` is missing, use
112
+ `normal`.
113
+ 2. **Custom class** - append preset `class` string to `<body>` classlist.
114
+ 3. **Background colour** - if `bgColor` is non-empty, add `background-color:{bgColor}` to the inline style of the
115
+ `.page-body` wrapper element.
116
+ 4. **Background image** - if `bgImage` is non-empty, append
117
+ `background-image:url('{bgImage}');background-size:cover;background-position:center;` to the `.page-body` inline
118
+ style. Both `bgColor` and `bgImage` may be set simultaneously - the image renders over the colour as per normal CSS
119
+ stacking.
120
+ 5. **Navbar / footer** - existing mechanism (already controlled by preset); no change needed.
121
+
122
+ The `data-layout="{{layout}}"` attribute on `<body>` is preserved as-is for CSS targeting.
123
+
124
+ ---
125
+
126
+ ## Public CSS (`public/css/site.css`)
127
+
128
+ Four width utility classes controlling `.page-body` max-width:
129
+
130
+ ```css
131
+ body.dm-layout-narrow .page-body {
132
+ max-width: 768px;
133
+ }
134
+
135
+ body.dm-layout-normal .page-body {
136
+ max-width: 1100px;
137
+ }
138
+
139
+ body.dm-layout-wide .page-body {
140
+ max-width: 1280px;
141
+ }
142
+
143
+ body.dm-layout-full .page-body {
144
+ max-width: none;
145
+ }
146
+ ```
147
+
148
+ All four are centred: `.page-body` already has `margin: 0 auto` (or this is added if missing).
149
+
150
+ ---
151
+
152
+ ## Admin UI (`admin/js/views/layouts.js`)
153
+
154
+ ### Preset Cards Grid
155
+
156
+ Each card shows:
157
+
158
+ - **Label** (bold) + builtin badge (`built-in`) if `builtin: true`
159
+ - **Width** as a small pill (e.g. `normal`)
160
+ - **Toggle indicators**: three small labelled dots or icons - Navbar / Footer / Sidebar - filled if enabled, muted if
161
+ not
162
+ - **Background colour swatch** (small square) if `bgColor` is set
163
+ - Click anywhere on the card → opens the edit slideover
164
+
165
+ **Create button** - top-right of the grid section. Opens a blank edit slideover (all defaults).
166
+
167
+ ### Edit Slideover
168
+
169
+ Opened via `E.slideover({ title: 'Edit Layout' / 'New Layout', size: 'md' })` using the light DOM slot pattern.
170
+
171
+ **Form fields (top to bottom):**
172
+
173
+ 1. **Label** - text input (required)
174
+ 2. **Description** - text input (optional)
175
+ 3. **Width** - `<select>`: Narrow (768px) / Normal (1100px) / Wide (1280px) / Full
176
+ 4. **Navbar** - checkbox
177
+ 5. **Footer** - checkbox
178
+ 6. **Sidebar** - checkbox
179
+ 7. **Background Colour** - `<input type="color">` paired with a "Clear" button. When the current `bgColor` value is
180
+ `""`, the colour picker is disabled and visually greyed out; the label reads "None". The user must click "Choose" (or
181
+ interact with the picker) to enable it, which sets a default of `#ffffff`. Clicking "Clear" disables the picker again
182
+ and writes `""` back.
183
+ 8. **Background Image** - text input (URL). Empty string = no image.
184
+ 9. **Custom CSS Class** - text input. Empty string = no extra class.
185
+
186
+ **Action buttons:**
187
+
188
+ - **Save** - calls `POST /api/layouts` (create) or `PUT /api/layouts/:key` (update). On success: closes slideover,
189
+ refreshes card grid, shows success toast. On error: shows error toast with the server's message; slideover remains
190
+ open.
191
+ - **Delete** (custom presets only, not shown for `builtin: true`) - `await E.confirm('Delete this layout?')` →
192
+ `DELETE /api/layouts/:key` → on success: closes slideover, removes card, shows success toast. On error: shows error
193
+ toast; slideover remains open.
194
+
195
+ ### Layout Options Tab
196
+
197
+ The existing spacer config tab is unchanged.
198
+
199
+ ---
200
+
201
+ ## Page Editor Integration
202
+
203
+ No changes required. The page editor already populates the layout dropdown from `api.layouts.get()` and saves `layout`
204
+ to frontmatter. The new fields are transparent to the editor - they are applied server-side at render time.
205
+
206
+ ---
207
+
208
+ ## Files to Create / Modify
209
+
210
+ | File | Change |
211
+ |-----------------------------------|---------------------------------------------------------------------------|
212
+ | `config/presets.json` | Update committed file to include new fields on all three built-in presets |
213
+ | `server/routes/api/layouts.js` | Add `POST`, `PUT /:key`, `DELETE /:key` routes; add field validation |
214
+ | `server/services/renderer.js` | Look up preset, apply width class, custom class, bg styles to page output |
215
+ | `admin/js/views/layouts.js` | Full rebuild: CRUD cards + slideover edit form |
216
+ | `admin/js/templates/layouts.html` | Update template if needed for new card structure |
217
+ | `public/css/site.css` | Add four `dm-layout-*` width utility classes |
218
+
219
+ ---
220
+
221
+ ## Out of Scope
222
+
223
+ - Drag-to-reorder presets
224
+ - Per-column or per-section width overrides
225
+ - Background image upload (URL input only; media browser integration is a future enhancement)
226
+ - Sidebar region implementation (toggle stored in preset but sidebar HTML is not in scope for this spec)