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.
- 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/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/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/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,545 @@
|
|
|
1
|
+
# [text] Shortcode Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:
|
|
4
|
+
> executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
5
|
+
|
|
6
|
+
**Goal:** Add a `[text]` shortcode that renders a styled `<span>` with inline CSS, accessible via a modal in the Page
|
|
7
|
+
Editor Insert menu.
|
|
8
|
+
|
|
9
|
+
**Architecture:** Server-side processor `processTextBlocks()` in `markdown.js` handles rendering; the Insert menu entry
|
|
10
|
+
and modal live entirely in `markdown-toolbar.js`; `docs/markdown-shortcodes.md` is updated to document the shortcode.
|
|
11
|
+
|
|
12
|
+
**Tech Stack:** Fastify 5 (ESM), Domma SPA admin (`E.modal`, `insertAtCursor`), `sanitize-html`, `gray-matter` +
|
|
13
|
+
`marked`.
|
|
14
|
+
|
|
15
|
+
**Spec:** `docs/superpowers/specs/2026-03-25-text-shortcode-design.md`
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## File Map
|
|
20
|
+
|
|
21
|
+
| File | Change |
|
|
22
|
+
|------------------------------------|----------------------------------------------------------------------------------------------------------|
|
|
23
|
+
| `server/services/markdown.js` | Add `processTextBlocks()` function + wire into pipeline between `withBadge` and `withButton` |
|
|
24
|
+
| `admin/js/lib/markdown-toolbar.js` | Register `text-style` icon + add Insert menu entry + add `action === 'text'` branch in `executeAction()` |
|
|
25
|
+
| `docs/markdown-shortcodes.md` | Add `[text]` section |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Task 1: `processTextBlocks()` - server-side shortcode processor
|
|
30
|
+
|
|
31
|
+
**Files:**
|
|
32
|
+
|
|
33
|
+
- Modify: `server/services/markdown.js`
|
|
34
|
+
|
|
35
|
+
### Background
|
|
36
|
+
|
|
37
|
+
`processBadgeBlocks` (just above the insertion point) is the closest model - it also outputs a `<span>`, uses
|
|
38
|
+
`scrubCodeRegions`/`restore`, and handles flag attributes via regex rather than `parseShortcodeAttrs`.
|
|
39
|
+
|
|
40
|
+
Two subtleties:
|
|
41
|
+
|
|
42
|
+
- `bold` and `italic` are **flag** attributes - use `/\bbold\b/i.test(attrStr)` for consistency with
|
|
43
|
+
`processBadgeBlocks` (`pill`, `outline`). While `parseShortcodeAttrs` Pass 2 does capture standalone flags as `true`,
|
|
44
|
+
the regex approach matches existing convention and avoids edge-case ambiguity.
|
|
45
|
+
- `bold` flag takes precedence over `weight` attribute when both are present
|
|
46
|
+
|
|
47
|
+
- [ ] **Step 1: Add `processTextBlocks` function to `markdown.js`**
|
|
48
|
+
|
|
49
|
+
Find the closing `}` of `processBadgeBlocks` (search for the line `return restore(result);` followed by `}` in that
|
|
50
|
+
function) and add the following function immediately after it:
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
/**
|
|
54
|
+
* Pre-process [text] shortcodes before running through marked.
|
|
55
|
+
*
|
|
56
|
+
* Syntax (paired):
|
|
57
|
+
* [text size="xl" bold italic color="primary" font="Georgia"]Your text[/text]
|
|
58
|
+
*
|
|
59
|
+
* Outputs a <span> with all resolved styles inlined.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} markdown
|
|
62
|
+
* @returns {string}
|
|
63
|
+
*/
|
|
64
|
+
function processTextBlocks(markdown) {
|
|
65
|
+
const {scrubbed, restore} = scrubCodeRegions(markdown);
|
|
66
|
+
|
|
67
|
+
const SIZE_MAP = {
|
|
68
|
+
xs: '.75rem', sm: '.875rem', base: '1rem', lg: '1.125rem',
|
|
69
|
+
xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem', '4xl': '2.25rem',
|
|
70
|
+
};
|
|
71
|
+
const WEIGHT_MAP = {
|
|
72
|
+
thin: '100', light: '300', normal: '400', medium: '500',
|
|
73
|
+
semibold: '600', bold: '700', extrabold: '800', black: '900',
|
|
74
|
+
};
|
|
75
|
+
const SPACING_MAP = {
|
|
76
|
+
tight: '-0.05em', normal: '0em', wide: '0.05em', wider: '0.1em',
|
|
77
|
+
};
|
|
78
|
+
const COLOR_TOKENS = {
|
|
79
|
+
primary: 'var(--dm-color-primary)', secondary: 'var(--dm-color-secondary)',
|
|
80
|
+
muted: 'var(--dm-text-muted)', danger: 'var(--dm-color-danger)',
|
|
81
|
+
success: 'var(--dm-color-success)', warning: 'var(--dm-color-warning)',
|
|
82
|
+
info: 'var(--dm-color-info)',
|
|
83
|
+
};
|
|
84
|
+
const FONT_MAP = {
|
|
85
|
+
'Georgia': 'Georgia,serif',
|
|
86
|
+
'Arial': 'Arial,sans-serif',
|
|
87
|
+
'Verdana': 'Verdana,sans-serif',
|
|
88
|
+
'Courier New': "'Courier New',monospace",
|
|
89
|
+
'Times New Roman': "'Times New Roman',serif",
|
|
90
|
+
'Trebuchet MS': "'Trebuchet MS',sans-serif",
|
|
91
|
+
};
|
|
92
|
+
const TRANSFORM_MAP = {
|
|
93
|
+
upper: 'uppercase', lower: 'lowercase', capitalize: 'capitalize', none: 'none',
|
|
94
|
+
};
|
|
95
|
+
const DECORATION_MAP = {
|
|
96
|
+
underline: 'underline', 'line-through': 'line-through', none: 'none',
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
function buildText(attrStr, inner) {
|
|
100
|
+
const attrs = parseShortcodeAttrs(attrStr || '');
|
|
101
|
+
const styles = [];
|
|
102
|
+
|
|
103
|
+
if (attrs.size) {
|
|
104
|
+
styles.push(`font-size:${SIZE_MAP[attrs.size] || attrs.size}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// bold flag takes precedence over weight attr
|
|
108
|
+
const isBold = /\bbold\b/i.test(attrStr);
|
|
109
|
+
if (isBold) {
|
|
110
|
+
styles.push('font-weight:700');
|
|
111
|
+
} else if (attrs.weight && WEIGHT_MAP[attrs.weight]) {
|
|
112
|
+
styles.push(`font-weight:${WEIGHT_MAP[attrs.weight]}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (/\bitalic\b/i.test(attrStr)) {
|
|
116
|
+
styles.push('font-style:italic');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (attrs.color) {
|
|
120
|
+
styles.push(`color:${COLOR_TOKENS[attrs.color] || attrs.color}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (attrs.font && FONT_MAP[attrs.font]) {
|
|
124
|
+
styles.push(`font-family:${FONT_MAP[attrs.font]}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (attrs.transform && TRANSFORM_MAP[attrs.transform]) {
|
|
128
|
+
styles.push(`text-transform:${TRANSFORM_MAP[attrs.transform]}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (attrs.decoration && DECORATION_MAP[attrs.decoration]) {
|
|
132
|
+
styles.push(`text-decoration:${DECORATION_MAP[attrs.decoration]}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (attrs.spacing) {
|
|
136
|
+
styles.push(`letter-spacing:${SPACING_MAP[attrs.spacing] || attrs.spacing}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const styleAttr = styles.length ? ` style="${styles.join(';')}"` : '';
|
|
140
|
+
const classAttr = attrs.class ? ` class="${escapeAttr(attrs.class)}"` : '';
|
|
141
|
+
const idAttr = attrs.id ? ` id="${escapeAttr(attrs.id)}"` : '';
|
|
142
|
+
|
|
143
|
+
return `<span${styleAttr}${classAttr}${idAttr}>${inner.trim()}</span>`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const result = scrubbed.replace(
|
|
147
|
+
/\[text([^\]]*)\]([\s\S]*?)\[\/text\]/gi,
|
|
148
|
+
(_, attrStr, body) => buildText(attrStr, body)
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return restore(result);
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- [ ] **Step 2: Wire `processTextBlocks` into the parse pipeline**
|
|
156
|
+
|
|
157
|
+
In the same file, find these two adjacent lines (around the `withBadge`/`withButton` section):
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
const withBadge = processBadgeBlocks(withTable);
|
|
161
|
+
const withButton = processButtonBlocks(withBadge);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Replace with:
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
const withBadge = processBadgeBlocks(withTable);
|
|
168
|
+
const withText = processTextBlocks(withBadge);
|
|
169
|
+
const withButton = processButtonBlocks(withText);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
- [ ] **Step 3: Verify the processor works**
|
|
173
|
+
|
|
174
|
+
The server is always running on port 4096. Create a quick test page using the API - or just edit an existing page's
|
|
175
|
+
content via the admin - to contain:
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
[text size="xl" bold italic color="primary"]Hello styled world[/text]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Then curl the public page and check the rendered HTML contains:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
<span style="font-size:1.25rem;font-weight:700;font-style:italic;color:var(--dm-color-primary);">Hello styled world</span>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Also verify these edge cases render correctly:
|
|
188
|
+
|
|
189
|
+
- `[text]Plain text[/text]` → `<span>Plain text</span>` (no style attr)
|
|
190
|
+
- `[text bold weight="light"]Text[/text]` → `font-weight:700` only (bold wins)
|
|
191
|
+
- `[text size="2em"]Big[/text]` → `font-size:2em` (raw value passthrough)
|
|
192
|
+
- Shortcode inside a fenced code block should be left untouched (scrubCodeRegions)
|
|
193
|
+
|
|
194
|
+
- [ ] **Step 4: Commit**
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
git add server/services/markdown.js
|
|
198
|
+
git commit -m "feat: add [text] shortcode server-side processor"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Task 2: Insert menu entry + modal - `markdown-toolbar.js`
|
|
204
|
+
|
|
205
|
+
**Files:**
|
|
206
|
+
|
|
207
|
+
- Modify: `admin/js/lib/markdown-toolbar.js`
|
|
208
|
+
|
|
209
|
+
### Background
|
|
210
|
+
|
|
211
|
+
All insert-action modals live directly inside `executeAction()` in `markdown-toolbar.js` - there is no `onText` callback
|
|
212
|
+
and no `page-editor.js` change. The modal uses `E.modal` (Domma global), `insertAtCursor` (imported in the same file),
|
|
213
|
+
and the same `makeRow`/`makeSelect`/`makeInput` helper pattern seen in other actions.
|
|
214
|
+
|
|
215
|
+
The live preview uses a `<span>` inside a wrapping `<div>` - styles are applied directly to the `<span>` to match the
|
|
216
|
+
actual rendered element (inline context).
|
|
217
|
+
|
|
218
|
+
- [ ] **Step 1: Register the `text-style` icon**
|
|
219
|
+
|
|
220
|
+
Inside `registerIcons()`, add after the last `I.register(...)` call:
|
|
221
|
+
|
|
222
|
+
```js
|
|
223
|
+
I.register('text-style', {
|
|
224
|
+
viewBox: '0 0 24 24',
|
|
225
|
+
paths: [
|
|
226
|
+
'M4 7V5h16v2',
|
|
227
|
+
'M9 19h6',
|
|
228
|
+
'M12 5v14',
|
|
229
|
+
'M5 12h14',
|
|
230
|
+
],
|
|
231
|
+
stroke: 'currentColor', fill: 'none', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round'
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
- [ ] **Step 2: Add the Insert menu entry**
|
|
236
|
+
|
|
237
|
+
Find the Insert dropdown items array (search for `{label: 'Tabs', icon:`). Add the `Text` entry between `Tabs` and
|
|
238
|
+
`Timeline`:
|
|
239
|
+
|
|
240
|
+
```js
|
|
241
|
+
{label: 'Tabs', icon: 'layout-list', action: 'tabs'},
|
|
242
|
+
{label: 'Text', icon: 'text-style', action: 'text'}, // ← add this line
|
|
243
|
+
{label: 'Timeline', icon: 'activity', action: 'timeline'},
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
- [ ] **Step 3: Add the `text` action handler in `executeAction()`**
|
|
247
|
+
|
|
248
|
+
Find the closing branch before the final `}` of `executeAction` - it ends with:
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
} else if (action === 'help') {
|
|
252
|
+
if (handlers.help) handlers.help(ta);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Insert the `text` branch before the `help` branch:
|
|
258
|
+
|
|
259
|
+
```js
|
|
260
|
+
} else if (action === 'text') {
|
|
261
|
+
const start = ta.selectionStart;
|
|
262
|
+
const end = ta.selectionEnd;
|
|
263
|
+
const selected = ta.value.substring(start, end) || '';
|
|
264
|
+
|
|
265
|
+
const labelStyle = 'display:block;font-size:.7rem;font-weight:600;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;';
|
|
266
|
+
const inputStyle = 'width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;';
|
|
267
|
+
|
|
268
|
+
function makeRow(labelText, control) {
|
|
269
|
+
const row = document.createElement('div');
|
|
270
|
+
const lbl = document.createElement('label');
|
|
271
|
+
lbl.style.cssText = labelStyle;
|
|
272
|
+
lbl.textContent = labelText;
|
|
273
|
+
row.appendChild(lbl);
|
|
274
|
+
row.appendChild(control);
|
|
275
|
+
return row;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function makeSelect(options) {
|
|
279
|
+
const sel = document.createElement('select');
|
|
280
|
+
sel.style.cssText = inputStyle;
|
|
281
|
+
[['', '- default -'], ...options].forEach(([val, text]) => {
|
|
282
|
+
const opt = document.createElement('option');
|
|
283
|
+
opt.value = val;
|
|
284
|
+
opt.textContent = text;
|
|
285
|
+
sel.appendChild(opt);
|
|
286
|
+
});
|
|
287
|
+
return sel;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function makeTextInput(placeholder, value) {
|
|
291
|
+
const inp = document.createElement('input');
|
|
292
|
+
inp.type = 'text';
|
|
293
|
+
inp.placeholder = placeholder || '';
|
|
294
|
+
inp.value = value || '';
|
|
295
|
+
inp.style.cssText = inputStyle;
|
|
296
|
+
return inp;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function makeCheckbox(labelText) {
|
|
300
|
+
const wrap = document.createElement('label');
|
|
301
|
+
wrap.style.cssText = 'display:flex;align-items:center;gap:.5rem;cursor:pointer;';
|
|
302
|
+
const cb = document.createElement('input');
|
|
303
|
+
cb.type = 'checkbox';
|
|
304
|
+
const span = document.createElement('span');
|
|
305
|
+
span.style.cssText = 'font-size:.85em;color:var(--dm-text,#eee);';
|
|
306
|
+
span.textContent = labelText;
|
|
307
|
+
wrap.appendChild(cb);
|
|
308
|
+
wrap.appendChild(span);
|
|
309
|
+
return {wrap, cb};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const wrapper = document.createElement('div');
|
|
313
|
+
wrapper.style.cssText = 'padding:1rem;display:flex;flex-direction:column;gap:.75rem;';
|
|
314
|
+
|
|
315
|
+
const contentInput = makeTextInput('Text to style…', selected);
|
|
316
|
+
wrapper.appendChild(makeRow('Content', contentInput));
|
|
317
|
+
|
|
318
|
+
const sizeSelect = makeSelect([
|
|
319
|
+
['xs','xs - 0.75rem'], ['sm','sm - 0.875rem'], ['base','base - 1rem'],
|
|
320
|
+
['lg','lg - 1.125rem'], ['xl','xl - 1.25rem'], ['2xl','2xl - 1.5rem'],
|
|
321
|
+
['3xl','3xl - 1.875rem'], ['4xl','4xl - 2.25rem'],
|
|
322
|
+
]);
|
|
323
|
+
wrapper.appendChild(makeRow('Size', sizeSelect));
|
|
324
|
+
|
|
325
|
+
const weightSelect = makeSelect([
|
|
326
|
+
['thin','Thin (100)'], ['light','Light (300)'], ['normal','Normal (400)'],
|
|
327
|
+
['medium','Medium (500)'], ['semibold','Semibold (600)'], ['bold','Bold (700)'],
|
|
328
|
+
['extrabold','Extrabold (800)'], ['black','Black (900)'],
|
|
329
|
+
]);
|
|
330
|
+
wrapper.appendChild(makeRow('Weight', weightSelect));
|
|
331
|
+
|
|
332
|
+
const {wrap: boldWrap, cb: boldCb} = makeCheckbox('Bold (shorthand - overrides Weight)');
|
|
333
|
+
wrapper.appendChild(boldWrap);
|
|
334
|
+
|
|
335
|
+
const {wrap: italicWrap, cb: italicCb} = makeCheckbox('Italic');
|
|
336
|
+
wrapper.appendChild(italicWrap);
|
|
337
|
+
|
|
338
|
+
const colorInput = makeTextInput('e.g. primary, #ff0000, rgb(255,0,0)');
|
|
339
|
+
wrapper.appendChild(makeRow('Colour', colorInput));
|
|
340
|
+
|
|
341
|
+
const fontSelect = makeSelect([
|
|
342
|
+
['Georgia','Georgia (serif)'], ['Arial','Arial (sans-serif)'],
|
|
343
|
+
['Verdana','Verdana (sans-serif)'], ['Courier New','Courier New (monospace)'],
|
|
344
|
+
['Times New Roman','Times New Roman (serif)'], ['Trebuchet MS','Trebuchet MS (sans-serif)'],
|
|
345
|
+
]);
|
|
346
|
+
wrapper.appendChild(makeRow('Font', fontSelect));
|
|
347
|
+
|
|
348
|
+
const transformSelect = makeSelect([
|
|
349
|
+
['upper','Uppercase'], ['lower','Lowercase'], ['capitalize','Capitalise'], ['none','None'],
|
|
350
|
+
]);
|
|
351
|
+
wrapper.appendChild(makeRow('Transform', transformSelect));
|
|
352
|
+
|
|
353
|
+
const decorationSelect = makeSelect([
|
|
354
|
+
['underline','Underline'], ['line-through','Line-through'], ['none','None'],
|
|
355
|
+
]);
|
|
356
|
+
wrapper.appendChild(makeRow('Decoration', decorationSelect));
|
|
357
|
+
|
|
358
|
+
const spacingSelect = makeSelect([
|
|
359
|
+
['tight','Tight'], ['normal','Normal'], ['wide','Wide'], ['wider','Wider'],
|
|
360
|
+
]);
|
|
361
|
+
wrapper.appendChild(makeRow('Spacing', spacingSelect));
|
|
362
|
+
|
|
363
|
+
const classInput = makeTextInput('Optional CSS class');
|
|
364
|
+
wrapper.appendChild(makeRow('CSS Class', classInput));
|
|
365
|
+
|
|
366
|
+
const idInput = makeTextInput('Optional ID');
|
|
367
|
+
wrapper.appendChild(makeRow('ID', idInput));
|
|
368
|
+
|
|
369
|
+
// Live preview - <span> inside <div> for accurate inline-context representation
|
|
370
|
+
const previewWrap = document.createElement('div');
|
|
371
|
+
previewWrap.style.cssText = 'padding:.75rem;background:var(--dm-surface-subtle,#111);border-radius:4px;min-height:2.5rem;display:flex;align-items:center;';
|
|
372
|
+
const previewSpan = document.createElement('span');
|
|
373
|
+
previewSpan.textContent = selected || 'Preview text';
|
|
374
|
+
previewWrap.appendChild(previewSpan);
|
|
375
|
+
wrapper.appendChild(makeRow('Preview', previewWrap));
|
|
376
|
+
|
|
377
|
+
const COLOR_TOKENS_PREV = {
|
|
378
|
+
primary:'var(--dm-color-primary)',secondary:'var(--dm-color-secondary)',
|
|
379
|
+
muted:'var(--dm-text-muted)',danger:'var(--dm-color-danger)',
|
|
380
|
+
success:'var(--dm-color-success)',warning:'var(--dm-color-warning)',info:'var(--dm-color-info)',
|
|
381
|
+
};
|
|
382
|
+
const SIZE_MAP_PREV = {xs:'.75rem',sm:'.875rem',base:'1rem',lg:'1.125rem',xl:'1.25rem','2xl':'1.5rem','3xl':'1.875rem','4xl':'2.25rem'};
|
|
383
|
+
const WEIGHT_MAP_PREV = {thin:'100',light:'300',normal:'400',medium:'500',semibold:'600',bold:'700',extrabold:'800',black:'900'};
|
|
384
|
+
const SPACING_MAP_PREV = {tight:'-0.05em',normal:'0em',wide:'0.05em',wider:'0.1em'};
|
|
385
|
+
const FONT_MAP_PREV = {'Georgia':'Georgia,serif','Arial':'Arial,sans-serif','Verdana':'Verdana,sans-serif','Courier New':"'Courier New',monospace",'Times New Roman':"'Times New Roman',serif",'Trebuchet MS':"'Trebuchet MS',sans-serif"};
|
|
386
|
+
const TRANSFORM_MAP_PREV = {upper:'uppercase',lower:'lowercase',capitalize:'capitalize',none:'none'};
|
|
387
|
+
const DECORATION_MAP_PREV = {underline:'underline','line-through':'line-through',none:'none'};
|
|
388
|
+
|
|
389
|
+
function updatePreview() {
|
|
390
|
+
const styles = [];
|
|
391
|
+
if (sizeSelect.value) styles.push(`font-size:${SIZE_MAP_PREV[sizeSelect.value] || sizeSelect.value}`);
|
|
392
|
+
if (boldCb.checked) {
|
|
393
|
+
styles.push('font-weight:700');
|
|
394
|
+
} else if (weightSelect.value) {
|
|
395
|
+
styles.push(`font-weight:${WEIGHT_MAP_PREV[weightSelect.value] || weightSelect.value}`);
|
|
396
|
+
}
|
|
397
|
+
if (italicCb.checked) styles.push('font-style:italic');
|
|
398
|
+
const col = colorInput.value.trim();
|
|
399
|
+
if (col) styles.push(`color:${COLOR_TOKENS_PREV[col] || col}`);
|
|
400
|
+
if (fontSelect.value) styles.push(`font-family:${FONT_MAP_PREV[fontSelect.value]}`);
|
|
401
|
+
if (transformSelect.value) styles.push(`text-transform:${TRANSFORM_MAP_PREV[transformSelect.value]}`);
|
|
402
|
+
if (decorationSelect.value) styles.push(`text-decoration:${DECORATION_MAP_PREV[decorationSelect.value]}`);
|
|
403
|
+
if (spacingSelect.value) styles.push(`letter-spacing:${SPACING_MAP_PREV[spacingSelect.value]}`);
|
|
404
|
+
previewSpan.style.cssText = styles.join(';');
|
|
405
|
+
previewSpan.textContent = contentInput.value || 'Preview text';
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
boldCb.addEventListener('change', () => {
|
|
409
|
+
weightSelect.disabled = boldCb.checked;
|
|
410
|
+
if (boldCb.checked) weightSelect.value = '';
|
|
411
|
+
updatePreview();
|
|
412
|
+
});
|
|
413
|
+
[sizeSelect, weightSelect, fontSelect, transformSelect, decorationSelect, spacingSelect].forEach(el => {
|
|
414
|
+
el.addEventListener('change', updatePreview);
|
|
415
|
+
});
|
|
416
|
+
[colorInput, contentInput].forEach(el => {
|
|
417
|
+
el.addEventListener('input', updatePreview);
|
|
418
|
+
});
|
|
419
|
+
italicCb.addEventListener('change', updatePreview);
|
|
420
|
+
|
|
421
|
+
const insertBtn = document.createElement('button');
|
|
422
|
+
insertBtn.type = 'button';
|
|
423
|
+
insertBtn.className = 'btn btn-primary';
|
|
424
|
+
insertBtn.textContent = 'Insert';
|
|
425
|
+
wrapper.appendChild(insertBtn);
|
|
426
|
+
|
|
427
|
+
const modal = E.modal({title: 'Style Text', size: 'sm'});
|
|
428
|
+
modal.element.appendChild(wrapper);
|
|
429
|
+
modal.open();
|
|
430
|
+
|
|
431
|
+
insertBtn.addEventListener('click', () => {
|
|
432
|
+
const content = contentInput.value;
|
|
433
|
+
if (!content) return;
|
|
434
|
+
const parts = [];
|
|
435
|
+
if (sizeSelect.value) parts.push(`size="${sizeSelect.value}"`);
|
|
436
|
+
if (boldCb.checked) {
|
|
437
|
+
parts.push('bold');
|
|
438
|
+
} else if (weightSelect.value) {
|
|
439
|
+
parts.push(`weight="${weightSelect.value}"`);
|
|
440
|
+
}
|
|
441
|
+
if (italicCb.checked) parts.push('italic');
|
|
442
|
+
if (colorInput.value.trim()) parts.push(`color="${colorInput.value.trim()}"`);
|
|
443
|
+
if (fontSelect.value) parts.push(`font="${fontSelect.value}"`);
|
|
444
|
+
if (transformSelect.value) parts.push(`transform="${transformSelect.value}"`);
|
|
445
|
+
if (decorationSelect.value) parts.push(`decoration="${decorationSelect.value}"`);
|
|
446
|
+
if (spacingSelect.value) parts.push(`spacing="${spacingSelect.value}"`);
|
|
447
|
+
if (classInput.value.trim()) parts.push(`class="${classInput.value.trim()}"`);
|
|
448
|
+
if (idInput.value.trim()) parts.push(`id="${idInput.value.trim()}"`);
|
|
449
|
+
const attrStr = parts.length ? ' ' + parts.join(' ') : '';
|
|
450
|
+
const snippet = `[text${attrStr}]${content}[/text]`;
|
|
451
|
+
modal.close();
|
|
452
|
+
insertAtCursor(ta, snippet);
|
|
453
|
+
ta.dispatchEvent(new Event('input', {bubbles: true}));
|
|
454
|
+
ta.focus();
|
|
455
|
+
});
|
|
456
|
+
// end of action === 'text' branch
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
- [ ] **Step 4: Verify in the browser**
|
|
461
|
+
|
|
462
|
+
Open the page editor in the admin. Click the Insert (`+`) dropdown. Confirm **Text** appears between Tabs and Timeline
|
|
463
|
+
with the `text-style` icon.
|
|
464
|
+
|
|
465
|
+
Click Text → confirm the "Style Text" modal opens with all controls. Test:
|
|
466
|
+
|
|
467
|
+
- Select a size, enable Bold, add a colour token (`primary`) → preview updates in real time
|
|
468
|
+
- Enable Bold → confirm Weight select is disabled
|
|
469
|
+
- Type content → confirm preview text updates
|
|
470
|
+
- Click Insert → confirm the correct shortcode is inserted at the cursor, modal closes
|
|
471
|
+
|
|
472
|
+
Also verify that selecting text in the editor before opening the modal pre-fills the Content field.
|
|
473
|
+
|
|
474
|
+
- [ ] **Step 5: Commit**
|
|
475
|
+
|
|
476
|
+
```bash
|
|
477
|
+
git add admin/js/lib/markdown-toolbar.js
|
|
478
|
+
git commit -m "feat: add [text] shortcode insert modal to Page Editor"
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
## Task 3: Document `[text]` in `docs/markdown-shortcodes.md`
|
|
484
|
+
|
|
485
|
+
**Files:**
|
|
486
|
+
|
|
487
|
+
- Modify: `docs/markdown-shortcodes.md`
|
|
488
|
+
|
|
489
|
+
- [ ] **Step 1: Add the `[text]` section**
|
|
490
|
+
|
|
491
|
+
Find the `## [badge]` section and add the following section immediately before it (text sorts before badge
|
|
492
|
+
alphabetically, but place it wherever the doc's existing ordering convention dictates - check if sections are
|
|
493
|
+
alphabetical or by category):
|
|
494
|
+
|
|
495
|
+
```markdown
|
|
496
|
+
## `[text]`
|
|
497
|
+
|
|
498
|
+
Wraps inline text in a `<span>` with custom styles. All attributes are optional.
|
|
499
|
+
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
[text size="xl" bold italic color="primary"]Styled text[/text]
|
|
503
|
+
[text font="Georgia" decoration="underline" spacing="wide"]Classic[/text]
|
|
504
|
+
[text size="2em" color="#ff6600"]Custom size and colour[/text]
|
|
505
|
+
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Attributes
|
|
509
|
+
|
|
510
|
+
| Attribute | Values | Notes |
|
|
511
|
+
|---|---|---|
|
|
512
|
+
| `size` | `xs` `sm` `base` `lg` `xl` `2xl` `3xl` `4xl` or raw CSS (e.g. `"1.5em"`) | Maps to `font-size` |
|
|
513
|
+
| `weight` | `thin` `light` `normal` `medium` `semibold` `bold` `extrabold` `black` | Maps to `font-weight` |
|
|
514
|
+
| `bold` | flag | Shorthand for `font-weight:700`. Overrides `weight` if both present |
|
|
515
|
+
| `italic` | flag | `font-style:italic` |
|
|
516
|
+
| `color` | Token (`primary` `secondary` `muted` `danger` `success` `warning` `info`) or any CSS colour | Maps to `color` |
|
|
517
|
+
| `font` | `Georgia` `Arial` `Verdana` `Courier New` `Times New Roman` `Trebuchet MS` | Web-safe fonts only |
|
|
518
|
+
| `transform` | `upper` `lower` `capitalize` `none` | Maps to `text-transform` |
|
|
519
|
+
| `decoration` | `underline` `line-through` `none` | Maps to `text-decoration` |
|
|
520
|
+
| `spacing` | `tight` `normal` `wide` `wider` or raw CSS (e.g. `"0.1em"`) | Maps to `letter-spacing` |
|
|
521
|
+
| `class` | CSS class name(s) | Added to the `<span>` element |
|
|
522
|
+
| `id` | Element ID | Added to the `<span>` element |
|
|
523
|
+
|
|
524
|
+
### Notes
|
|
525
|
+
|
|
526
|
+
- `align` is intentionally not supported - `text-align` has no effect on inline elements. Use `[center]` for centring or a grid/card for other layout alignment.
|
|
527
|
+
- The Page Editor Insert menu includes a **Text** entry that opens a configuration modal with live preview.
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
- [ ] **Step 2: Commit**
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
git add docs/markdown-shortcodes.md
|
|
534
|
+
git commit -m "docs: document [text] shortcode"
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## Done
|
|
540
|
+
|
|
541
|
+
At this point:
|
|
542
|
+
|
|
543
|
+
- `[text size="xl" bold italic]Hello[/text]` renders correctly in public pages
|
|
544
|
+
- The Page Editor Insert → Text menu item opens the modal, live preview works, Insert button writes the shortcode
|
|
545
|
+
- `docs/markdown-shortcodes.md` is updated
|