domma-cms 0.2.1 → 0.3.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/admin/css/admin.css +1 -1200
- package/admin/js/api.js +1 -242
- package/admin/js/app.js +5 -279
- package/admin/js/config/sidebar-config.js +1 -115
- package/admin/js/lib/card.js +1 -63
- package/admin/js/lib/image-editor.js +1 -869
- package/admin/js/lib/markdown-toolbar.js +46 -421
- package/admin/js/templates/layouts.html +44 -7
- package/admin/js/templates/page-editor.html +9 -0
- package/admin/js/templates/settings.html +18 -1
- package/admin/js/templates/users.html +29 -4
- package/admin/js/views/collection-editor.js +3 -487
- package/admin/js/views/collection-entries.js +1 -484
- package/admin/js/views/collections.js +1 -153
- package/admin/js/views/dashboard.js +1 -56
- package/admin/js/views/documentation.js +1 -12
- package/admin/js/views/index.js +1 -39
- package/admin/js/views/layouts.js +9 -42
- package/admin/js/views/login.js +7 -251
- package/admin/js/views/media.js +1 -240
- package/admin/js/views/navigation.js +14 -212
- package/admin/js/views/page-editor.js +53 -661
- package/admin/js/views/pages.js +5 -72
- package/admin/js/views/plugins.js +13 -90
- package/admin/js/views/settings.js +1 -199
- package/admin/js/views/tutorials.js +1 -12
- package/admin/js/views/user-editor.js +1 -88
- package/admin/js/views/users.js +7 -76
- package/config/auth.json +1 -17
- package/config/navigation.json +15 -0
- package/config/site.json +5 -4
- package/package.json +1 -1
- package/plugins/domma-effects/public/celebrations/core/canvas.js +2 -104
- package/plugins/domma-effects/public/celebrations/core/particles.js +1 -144
- package/plugins/domma-effects/public/celebrations/core/physics.js +1 -166
- package/plugins/domma-effects/public/celebrations/index.js +1 -535
- package/plugins/domma-effects/public/celebrations/themes/christmas.js +1 -1805
- package/plugins/domma-effects/public/celebrations/themes/guy-fawkes.js +1 -1477
- package/plugins/domma-effects/public/celebrations/themes/halloween.js +1 -1837
- package/plugins/domma-effects/public/celebrations/themes/st-andrews.js +1 -1175
- package/plugins/domma-effects/public/celebrations/themes/st-davids.js +1 -1258
- package/plugins/domma-effects/public/celebrations/themes/st-georges.js +1 -1754
- package/plugins/domma-effects/public/celebrations/themes/st-patricks.js +1 -1290
- package/plugins/domma-effects/public/celebrations/themes/valentines.js +1 -1361
- package/plugins/example-analytics/stats.json +16 -12
- package/plugins/form-builder/admin/templates/form-editor.html +158 -130
- package/plugins/form-builder/admin/views/form-editor.js +3 -1
- package/plugins/form-builder/data/forms/contact-details.json +71 -35
- package/plugins/form-builder/data/forms/feedback.json +130 -0
- package/plugins/form-builder/data/submissions/feedback.json +1 -0
- package/plugins/form-builder/public/form-logic-engine.js +1 -568
- package/public/css/site.css +1 -302
- package/public/js/btt.js +1 -90
- package/public/js/cookie-consent.js +1 -61
- package/public/js/site.js +1 -204
- package/scripts/setup.js +4 -4
- package/server/middleware/auth.js +44 -21
- package/server/routes/api/auth.js +38 -8
- package/server/routes/api/collections.js +18 -5
- package/server/routes/api/layouts.js +18 -4
- package/server/routes/api/media.js +2 -3
- package/server/routes/api/navigation.js +2 -3
- package/server/routes/api/pages.js +3 -3
- package/server/routes/api/settings.js +2 -3
- package/server/routes/api/users.js +4 -6
- package/server/routes/public.js +3 -3
- package/server/server.js +8 -0
- package/server/services/markdown.js +102 -3
- package/server/services/userTypes.js +167 -0
- package/plugins/form-builder/email.js +0 -103
|
@@ -1,661 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const rows = [
|
|
55
|
-
['**bold**', 'bold (rendered bold)'],
|
|
56
|
-
['_italic_', 'italic (rendered italic)'],
|
|
57
|
-
['## Heading', 'Heading (h2)'],
|
|
58
|
-
['[text](url)', 'Link'],
|
|
59
|
-
['', 'Image'],
|
|
60
|
-
['- item', 'Bullet list'],
|
|
61
|
-
['`code`', 'Inline code'],
|
|
62
|
-
['---', 'Divider'],
|
|
63
|
-
];
|
|
64
|
-
rows.forEach(([syntax, result]) => {
|
|
65
|
-
const tr = document.createElement('tr');
|
|
66
|
-
const tdSyntax = document.createElement('td');
|
|
67
|
-
tdSyntax.style.cssText = 'padding:.35rem .5rem;vertical-align:top;';
|
|
68
|
-
tdSyntax.appendChild(makeCode(syntax));
|
|
69
|
-
const tdResult = document.createElement('td');
|
|
70
|
-
tdResult.style.cssText = 'padding:.35rem .5rem;vertical-align:top;color:var(--dm-text-muted,#aaa);font-size:.85em;';
|
|
71
|
-
tdResult.textContent = result;
|
|
72
|
-
tr.appendChild(tdSyntax);
|
|
73
|
-
tr.appendChild(tdResult);
|
|
74
|
-
tbody.appendChild(tr);
|
|
75
|
-
});
|
|
76
|
-
table.appendChild(tbody);
|
|
77
|
-
section1.appendChild(table);
|
|
78
|
-
wrap.appendChild(section1);
|
|
79
|
-
|
|
80
|
-
// --- Section 2: Grid shortcodes ---
|
|
81
|
-
const section2 = document.createElement('div');
|
|
82
|
-
section2.appendChild(makeH3('Grid & Columns'));
|
|
83
|
-
|
|
84
|
-
function makeNote(text) {
|
|
85
|
-
const el = document.createElement('p');
|
|
86
|
-
el.style.cssText = 'margin:.3rem 0 .6rem;font-size:.82em;color:var(--dm-text-muted,#aaa);';
|
|
87
|
-
el.textContent = text;
|
|
88
|
-
return el;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function makeSnippet(label, code, note) {
|
|
92
|
-
const lbl = document.createElement('p');
|
|
93
|
-
lbl.style.cssText = 'margin:.75rem 0 .25rem;font-size:.85em;font-weight:600;';
|
|
94
|
-
lbl.textContent = label;
|
|
95
|
-
const pre = document.createElement('pre');
|
|
96
|
-
pre.style.cssText = codeStyle + 'display:block;padding:.5rem .75rem;white-space:pre;overflow-x:auto;margin:0;';
|
|
97
|
-
pre.textContent = code;
|
|
98
|
-
const frag = document.createDocumentFragment();
|
|
99
|
-
frag.appendChild(lbl);
|
|
100
|
-
frag.appendChild(pre);
|
|
101
|
-
if (note) frag.appendChild(makeNote(note));
|
|
102
|
-
return frag;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const gridAttrTable = document.createElement('table');
|
|
106
|
-
gridAttrTable.style.cssText = 'width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;';
|
|
107
|
-
[
|
|
108
|
-
['cols="N"', '[grid] only', 'Number of columns (1–12)'],
|
|
109
|
-
['gap="N"', '[grid], [row]', 'Gap between columns/rows (1–6)'],
|
|
110
|
-
['span="N"', '[col] only', 'How many columns this cell spans'],
|
|
111
|
-
['class="x"', 'all', 'Add extra CSS classes'],
|
|
112
|
-
].forEach(([attr, where, desc]) => {
|
|
113
|
-
const tr = document.createElement('tr');
|
|
114
|
-
[attr, where, desc].forEach((txt, i) => {
|
|
115
|
-
const td = document.createElement('td');
|
|
116
|
-
td.style.cssText = 'padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;';
|
|
117
|
-
if (i === 0) {
|
|
118
|
-
td.appendChild(makeCode(attr));
|
|
119
|
-
} else {
|
|
120
|
-
td.style.color = 'var(--dm-text-muted,#aaa)';
|
|
121
|
-
td.textContent = txt;
|
|
122
|
-
}
|
|
123
|
-
tr.appendChild(td);
|
|
124
|
-
});
|
|
125
|
-
gridAttrTable.appendChild(tr);
|
|
126
|
-
});
|
|
127
|
-
section2.appendChild(gridAttrTable);
|
|
128
|
-
|
|
129
|
-
section2.appendChild(makeSnippet(
|
|
130
|
-
'Equal columns',
|
|
131
|
-
'[grid cols="3" gap="4"]\n[col]One[/col]\n[col]Two[/col]\n[col]Three[/col]\n[/grid]',
|
|
132
|
-
'cols sets the track count; each [col] fills one track.'
|
|
133
|
-
));
|
|
134
|
-
|
|
135
|
-
section2.appendChild(makeSnippet(
|
|
136
|
-
'Column spanning (12-col base)',
|
|
137
|
-
'[grid cols="12" gap="3"]\n[col span="8"]Main content[/col]\n[col span="4"]Sidebar[/col]\n[/grid]',
|
|
138
|
-
'span values must add up to cols. Common splits: 8/4, 6/6, 9/3.'
|
|
139
|
-
));
|
|
140
|
-
|
|
141
|
-
section2.appendChild(makeSnippet(
|
|
142
|
-
'Row (flexbox, equal-width)',
|
|
143
|
-
'[row gap="4"]\n[col]Left[/col]\n[col]Right[/col]\n[/row]',
|
|
144
|
-
'Use [row] when you want equal-width columns without specifying a count.'
|
|
145
|
-
));
|
|
146
|
-
|
|
147
|
-
section2.appendChild(makeSnippet(
|
|
148
|
-
'Card in a grid',
|
|
149
|
-
'[grid cols="3" gap="4"]\n[col]\n[card title="One"]Content[/card]\n[/col]\n[col]\n[card title="Two"]Content[/card]\n[/col]\n[col]\n[card title="Three"]Content[/card]\n[/col]\n[/grid]',
|
|
150
|
-
null
|
|
151
|
-
));
|
|
152
|
-
|
|
153
|
-
wrap.appendChild(section2);
|
|
154
|
-
|
|
155
|
-
// --- Section 3: Card shortcode ---
|
|
156
|
-
const section3 = document.createElement('div');
|
|
157
|
-
section3.appendChild(makeH3('Cards'));
|
|
158
|
-
|
|
159
|
-
section3.appendChild(makeSnippet(
|
|
160
|
-
'Basic card',
|
|
161
|
-
'[card title="Optional Title"]\nMarkdown **works** here.\n[/card]',
|
|
162
|
-
'Omit title for a card with no header.'
|
|
163
|
-
));
|
|
164
|
-
|
|
165
|
-
section3.appendChild(makeSnippet(
|
|
166
|
-
'Collapsible card',
|
|
167
|
-
'[card title="Click to expand" collapsible="true"]\nHidden by default.\n[/card]',
|
|
168
|
-
null
|
|
169
|
-
));
|
|
170
|
-
|
|
171
|
-
wrap.appendChild(section3);
|
|
172
|
-
|
|
173
|
-
// --- Section 3c: Hero shortcode ---
|
|
174
|
-
const section3c = document.createElement('div');
|
|
175
|
-
section3c.appendChild(makeH3('Hero'));
|
|
176
|
-
section3c.appendChild(makeNote('Full-width hero sections — no plugin required. Uses Domma\'s built-in Hero CSS component.'));
|
|
177
|
-
|
|
178
|
-
const heroAttrTable = document.createElement('table');
|
|
179
|
-
heroAttrTable.style.cssText = 'width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;';
|
|
180
|
-
[
|
|
181
|
-
['title="..."', 'Heading text'],
|
|
182
|
-
['tagline="..."', 'Subtitle text'],
|
|
183
|
-
['size="sm|lg|full"', 'Height variant (default: normal)'],
|
|
184
|
-
['variant="dark|primary|gradient-blue|gradient-purple|gradient-sunset|gradient-ocean"', 'Colour / gradient preset'],
|
|
185
|
-
['image="url"', 'Background image URL (adds cover mode)'],
|
|
186
|
-
['overlay="light|dark|darker|gradient|gradient-reverse"', 'Image overlay style'],
|
|
187
|
-
['align="center|left"', 'Content alignment (default: center)'],
|
|
188
|
-
['fullwidth="true"', 'Break out of the page container to span full viewport width'],
|
|
189
|
-
['class="..."', 'Extra CSS classes'],
|
|
190
|
-
['id="..."', 'Element id'],
|
|
191
|
-
].forEach(([attr, desc]) => {
|
|
192
|
-
const tr = document.createElement('tr');
|
|
193
|
-
const tdCode = document.createElement('td');
|
|
194
|
-
tdCode.style.cssText = 'padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;';
|
|
195
|
-
tdCode.appendChild(makeCode(attr));
|
|
196
|
-
const tdDesc = document.createElement('td');
|
|
197
|
-
tdDesc.style.cssText = 'padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);';
|
|
198
|
-
tdDesc.textContent = desc;
|
|
199
|
-
tr.appendChild(tdCode);
|
|
200
|
-
tr.appendChild(tdDesc);
|
|
201
|
-
heroAttrTable.appendChild(tr);
|
|
202
|
-
});
|
|
203
|
-
section3c.appendChild(heroAttrTable);
|
|
204
|
-
|
|
205
|
-
section3c.appendChild(makeSnippet(
|
|
206
|
-
'Basic hero',
|
|
207
|
-
'[hero title="Welcome" tagline="Build something great"][/hero]',
|
|
208
|
-
null
|
|
209
|
-
));
|
|
210
|
-
|
|
211
|
-
section3c.appendChild(makeSnippet(
|
|
212
|
-
'Gradient with body content',
|
|
213
|
-
'[hero title="Get Started" tagline="Everything you need." size="lg" variant="gradient-blue" align="center"]\nSome introductory **Markdown** content here.\n[/hero]',
|
|
214
|
-
null
|
|
215
|
-
));
|
|
216
|
-
|
|
217
|
-
section3c.appendChild(makeSnippet(
|
|
218
|
-
'Background image with overlay',
|
|
219
|
-
'[hero title="Our Story" image="/media/hero.jpg" overlay="dark" size="full"][/hero]',
|
|
220
|
-
'Combine image + overlay for text legibility over photos.'
|
|
221
|
-
));
|
|
222
|
-
|
|
223
|
-
wrap.appendChild(section3c);
|
|
224
|
-
|
|
225
|
-
// --- Section 3b: Interactive Components ---
|
|
226
|
-
const section3b = document.createElement('div');
|
|
227
|
-
section3b.appendChild(makeH3('Interactive Components'));
|
|
228
|
-
section3b.appendChild(makeNote('Tabs, accordion, carousel, and countdown — no plugin required.'));
|
|
229
|
-
|
|
230
|
-
section3b.appendChild(makeSnippet(
|
|
231
|
-
'Tabs',
|
|
232
|
-
'[tabs]\n[tab title="First"]Content **A**[/tab]\n[tab title="Second"]Content **B**[/tab]\n[/tabs]',
|
|
233
|
-
'Add style="pills" for pill-style navigation.'
|
|
234
|
-
));
|
|
235
|
-
|
|
236
|
-
section3b.appendChild(makeSnippet(
|
|
237
|
-
'Accordion',
|
|
238
|
-
'[accordion]\n[item title="Question 1"]Answer here.[/item]\n[item title="Question 2"]Answer here.[/item]\n[/accordion]',
|
|
239
|
-
'Add multiple="true" to allow several panels open at once.'
|
|
240
|
-
));
|
|
241
|
-
|
|
242
|
-
section3b.appendChild(makeSnippet(
|
|
243
|
-
'Carousel',
|
|
244
|
-
'[carousel autoplay="true" interval="5000"]\n[slide title="Slide 1"]Description[/slide]\n[slide image="/media/photo.jpg" title="Slide 2"]Caption[/slide]\n[/carousel]',
|
|
245
|
-
'Omit autoplay for a manual carousel. loop="false" disables wrapping.'
|
|
246
|
-
));
|
|
247
|
-
|
|
248
|
-
section3b.appendChild(makeSnippet(
|
|
249
|
-
'Countdown (to date)',
|
|
250
|
-
'[countdown to="2026-12-31" format="DD:HH:mm:ss" /]',
|
|
251
|
-
null
|
|
252
|
-
));
|
|
253
|
-
|
|
254
|
-
section3b.appendChild(makeSnippet(
|
|
255
|
-
'Countdown (duration)',
|
|
256
|
-
'[countdown duration="300" format="mm:ss" /]',
|
|
257
|
-
'duration is in seconds. format options: mm:ss · HH:mm:ss · DD:HH:mm:ss'
|
|
258
|
-
));
|
|
259
|
-
|
|
260
|
-
wrap.appendChild(section3b);
|
|
261
|
-
|
|
262
|
-
// --- Section 4: Embedding a Form ---
|
|
263
|
-
const section4 = document.createElement('div');
|
|
264
|
-
section4.appendChild(makeH3('Embedding a Form'));
|
|
265
|
-
|
|
266
|
-
const formPre = document.createElement('pre');
|
|
267
|
-
formPre.style.cssText = codeStyle + 'display:block;padding:.5rem .75rem;white-space:pre;overflow-x:auto;';
|
|
268
|
-
formPre.textContent = '<div data-form="contact"></div>';
|
|
269
|
-
section4.appendChild(formPre);
|
|
270
|
-
section4.appendChild(makeNote('Replace "contact" with the form slug. Forms are managed under Forms in the sidebar.'));
|
|
271
|
-
|
|
272
|
-
wrap.appendChild(section4);
|
|
273
|
-
|
|
274
|
-
// --- Section 5: Effects shortcodes ---
|
|
275
|
-
const section5 = document.createElement('div');
|
|
276
|
-
section5.appendChild(makeH3('Effects'));
|
|
277
|
-
section5.appendChild(makeNote('Shortcodes for scroll-triggered animations, counters, typewriter text, and more. Requires the Domma Effects plugin.'));
|
|
278
|
-
|
|
279
|
-
const effectsRows = [
|
|
280
|
-
['[reveal animation="fade"]...[/reveal]', 'Fade/slide/zoom on scroll'],
|
|
281
|
-
['[breathe]...[/breathe]', 'Continuous gentle scale loop'],
|
|
282
|
-
['[pulse]...[/pulse]', 'Repeating scale pulse'],
|
|
283
|
-
['[shake]...[/shake]', 'One-shot shake'],
|
|
284
|
-
['[scribe speed="50"]...[/scribe]', 'Typewriter — simple mode (text typed letter by letter)'],
|
|
285
|
-
['[scribe loop="true"][render]Text[/render][wait]1500[/wait][undo /][/scribe]', 'Typewriter — script mode (sequenced render/wait/undo actions)'],
|
|
286
|
-
['[scramble]...[/scramble]', 'Character-scramble reveal'],
|
|
287
|
-
['[counter to="100" /]', 'Animated number counter (self-closing)'],
|
|
288
|
-
['[ripple]...[/ripple]', 'Click-triggered ripple'],
|
|
289
|
-
['[twinkle count="50"]...[/twinkle]', 'Particle/star overlay'],
|
|
290
|
-
['[animate type="fade-in-up"]...[/animate]', 'CSS-only animation (no plugin needed)'],
|
|
291
|
-
['[ambient type="aurora"]...[/ambient]', 'CSS-only animated background'],
|
|
292
|
-
['[firework type="burst" colour="rainbow" /]', 'CSS firework (self-closing)'],
|
|
293
|
-
['[fireworks]...[/fireworks]', 'Fireworks display container'],
|
|
294
|
-
['[celebrate theme="auto" /]', 'Seasonal canvas celebration'],
|
|
295
|
-
];
|
|
296
|
-
|
|
297
|
-
const effectsTable = document.createElement('table');
|
|
298
|
-
effectsTable.style.cssText = 'width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;';
|
|
299
|
-
effectsRows.forEach(([syntax, desc]) => {
|
|
300
|
-
const tr = document.createElement('tr');
|
|
301
|
-
const tdCode = document.createElement('td');
|
|
302
|
-
tdCode.style.cssText = 'padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;';
|
|
303
|
-
tdCode.appendChild(makeCode(syntax));
|
|
304
|
-
const tdDesc = document.createElement('td');
|
|
305
|
-
tdDesc.style.cssText = 'padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);';
|
|
306
|
-
tdDesc.textContent = desc;
|
|
307
|
-
tr.appendChild(tdCode);
|
|
308
|
-
tr.appendChild(tdDesc);
|
|
309
|
-
effectsTable.appendChild(tr);
|
|
310
|
-
});
|
|
311
|
-
section5.appendChild(effectsTable);
|
|
312
|
-
|
|
313
|
-
section5.appendChild(makeSnippet(
|
|
314
|
-
'Scribe script — looping multi-phrase typewriter',
|
|
315
|
-
'[scribe loop="true" loop-delay="2000" delete-speed="20"]\n[render]We build things that matter.[/render]\n[wait]1200[/wait]\n[undo all="true" /]\n[render]We design for humans.[/render]\n[wait]1200[/wait]\n[undo all="true" /]\n[/scribe]',
|
|
316
|
-
'Use [render]...[/render] to type text, [wait]Ms[/wait] to pause, and [undo /] to delete. loop="true" replays the sequence. Only [render], [wait], and [undo] shortcodes are parsed — plain text between actions is ignored.'
|
|
317
|
-
));
|
|
318
|
-
|
|
319
|
-
section5.appendChild(makeNote('Enable the Domma Effects plugin to activate these shortcodes on your site.'));
|
|
320
|
-
|
|
321
|
-
wrap.appendChild(section5);
|
|
322
|
-
|
|
323
|
-
// --- Section 6: Tables shortcode ---
|
|
324
|
-
const section6t = document.createElement('div');
|
|
325
|
-
section6t.appendChild(makeH3('Tables'));
|
|
326
|
-
section6t.appendChild(makeNote('Wrap a standard Markdown (GFM) table with Domma CSS classes and responsive horizontal scrolling.'));
|
|
327
|
-
|
|
328
|
-
const tableAttrTable = document.createElement('table');
|
|
329
|
-
tableAttrTable.style.cssText = 'width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;';
|
|
330
|
-
[
|
|
331
|
-
['striped="true"', 'Alternate row background (.table-striped)'],
|
|
332
|
-
['bordered="true"', 'Borders on all cells (.table-bordered)'],
|
|
333
|
-
['compact="true"', 'Reduced cell padding (.table-compact)'],
|
|
334
|
-
['caption="..."', 'Caption text above the table'],
|
|
335
|
-
['class="..."', 'Extra CSS classes appended to .table'],
|
|
336
|
-
['id="..."', 'id attribute on the <table> element'],
|
|
337
|
-
].forEach(([attr, desc]) => {
|
|
338
|
-
const tr = document.createElement('tr');
|
|
339
|
-
const tdCode = document.createElement('td');
|
|
340
|
-
tdCode.style.cssText = 'padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;';
|
|
341
|
-
tdCode.appendChild(makeCode(attr));
|
|
342
|
-
const tdDesc = document.createElement('td');
|
|
343
|
-
tdDesc.style.cssText = 'padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);';
|
|
344
|
-
tdDesc.textContent = desc;
|
|
345
|
-
tr.appendChild(tdCode);
|
|
346
|
-
tr.appendChild(tdDesc);
|
|
347
|
-
tableAttrTable.appendChild(tr);
|
|
348
|
-
});
|
|
349
|
-
section6t.appendChild(tableAttrTable);
|
|
350
|
-
|
|
351
|
-
section6t.appendChild(makeSnippet(
|
|
352
|
-
'Striped + bordered table',
|
|
353
|
-
'[table striped="true" bordered="true" caption="Product Pricing"]\n| Product | Price |\n| ------- | ----: |\n| Widget | $9.99 |\n| Gadget | $14.99 |\n[/table]',
|
|
354
|
-
'The inner content must be a valid GFM Markdown table. The shortcode adds Domma CSS classes and wraps in a responsive scroll container.'
|
|
355
|
-
));
|
|
356
|
-
|
|
357
|
-
wrap.appendChild(section6t);
|
|
358
|
-
|
|
359
|
-
// --- Section 7: Slideover shortcode ---
|
|
360
|
-
const section6 = document.createElement('div');
|
|
361
|
-
section6.appendChild(makeH3('Slideover'));
|
|
362
|
-
section6.appendChild(makeNote('A trigger button that opens a slide-in panel with Markdown content. No plugin required.'));
|
|
363
|
-
|
|
364
|
-
const soAttrTable = document.createElement('table');
|
|
365
|
-
soAttrTable.style.cssText = 'width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;';
|
|
366
|
-
[
|
|
367
|
-
['title="..."', 'Panel header text'],
|
|
368
|
-
['trigger="..."', 'Button label (default: "Open")'],
|
|
369
|
-
['size="sm|md|lg"', 'Panel width (default: md)'],
|
|
370
|
-
['position="right|left"', 'Slide direction (default: right)'],
|
|
371
|
-
].forEach(([attr, desc]) => {
|
|
372
|
-
const tr = document.createElement('tr');
|
|
373
|
-
const tdCode = document.createElement('td');
|
|
374
|
-
tdCode.style.cssText = 'padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;';
|
|
375
|
-
tdCode.appendChild(makeCode(attr));
|
|
376
|
-
const tdDesc = document.createElement('td');
|
|
377
|
-
tdDesc.style.cssText = 'padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);';
|
|
378
|
-
tdDesc.textContent = desc;
|
|
379
|
-
tr.appendChild(tdCode);
|
|
380
|
-
tr.appendChild(tdDesc);
|
|
381
|
-
soAttrTable.appendChild(tr);
|
|
382
|
-
});
|
|
383
|
-
section6.appendChild(soAttrTable);
|
|
384
|
-
|
|
385
|
-
section6.appendChild(makeSnippet(
|
|
386
|
-
'Example',
|
|
387
|
-
'[slideover title="More Info" trigger="Read more" size="md"]\n## Details\nMarkdown content here.\n[/slideover]',
|
|
388
|
-
'Nested [card] and [grid] shortcodes work inside the slideover body.'
|
|
389
|
-
));
|
|
390
|
-
|
|
391
|
-
wrap.appendChild(section6);
|
|
392
|
-
|
|
393
|
-
// --- Section 7: DConfig ---
|
|
394
|
-
const section7 = document.createElement('div');
|
|
395
|
-
section7.appendChild(makeH3('DConfig — Declarative Behaviour'));
|
|
396
|
-
section7.appendChild(makeNote('Define click handlers and class toggles without writing JavaScript. Use the DConfig section above or embed inline with [dconfig]...[/dconfig] in the content body. Inline shortcodes win on selector conflict.'));
|
|
397
|
-
|
|
398
|
-
section7.appendChild(makeSnippet(
|
|
399
|
-
'Toggle a class on click',
|
|
400
|
-
'{ "#my-btn": { "events": { "click": { "target": "#panel", "toggleClass": "hidden" } } } }',
|
|
401
|
-
'Selector keys use standard CSS selectors. "target" is optional — defaults to the element itself.'
|
|
402
|
-
));
|
|
403
|
-
|
|
404
|
-
section7.appendChild(makeSnippet(
|
|
405
|
-
'Inline shortcode syntax',
|
|
406
|
-
'[dconfig]\n{ "#my-btn": { "events": { "click": { "target": "#panel", "toggleClass": "hidden" } } } }\n[/dconfig]',
|
|
407
|
-
null
|
|
408
|
-
));
|
|
409
|
-
|
|
410
|
-
wrap.appendChild(section7);
|
|
411
|
-
|
|
412
|
-
so.setContent(wrap);
|
|
413
|
-
so.open();
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Module-level state for unsaved-changes guard
|
|
417
|
-
let _dirty = false;
|
|
418
|
-
let _beforeUnload = null;
|
|
419
|
-
let _guardRegistered = false;
|
|
420
|
-
|
|
421
|
-
export const pageEditorView = {
|
|
422
|
-
templateUrl: '/admin/js/templates/page-editor.html',
|
|
423
|
-
|
|
424
|
-
async onMount($container) {
|
|
425
|
-
const hash = window.location.hash;
|
|
426
|
-
const editMatch = hash.match(/#\/pages\/edit(\/.*)/); // .* allows home page path "/"
|
|
427
|
-
const urlPath = editMatch ? editMatch[1] : null;
|
|
428
|
-
const isEdit = !!urlPath;
|
|
429
|
-
|
|
430
|
-
const fetches = [api.layouts.get().catch(() => ({}))];
|
|
431
|
-
if (isEdit) fetches.push(api.pages.get(urlPath).catch(() => null));
|
|
432
|
-
|
|
433
|
-
const [layouts, page] = await Promise.all(fetches);
|
|
434
|
-
|
|
435
|
-
if (isEdit && !page) {
|
|
436
|
-
E.toast('Page not found.', { type: 'error' });
|
|
437
|
-
R.navigate('/pages');
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Title
|
|
442
|
-
$container.find('#editor-title').text(isEdit ? 'Edit Page' : 'New Page');
|
|
443
|
-
|
|
444
|
-
// URL path field
|
|
445
|
-
if (isEdit) {
|
|
446
|
-
$container.find('#page-url-path').val(urlPath);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Populate form fields
|
|
450
|
-
if (page) {
|
|
451
|
-
$container.find('#field-title').val(page.title || '');
|
|
452
|
-
$container.find('#field-description').val(page.description || '');
|
|
453
|
-
$container.find('#field-status').val(page.status || 'draft');
|
|
454
|
-
$container.find('#field-sort-order').val(page.sortOrder ?? 99);
|
|
455
|
-
$container.find('#field-show-in-nav').prop('checked', !!page.showInNav);
|
|
456
|
-
$container.find('#field-sidebar').prop('checked', !!page.sidebar);
|
|
457
|
-
$container.find('#field-category').val(page.category || '');
|
|
458
|
-
$container.find('#field-visibility').val(page.visibility || 'public');
|
|
459
|
-
$container.find('#field-seo-title').val(page.seo?.title || '');
|
|
460
|
-
$container.find('#field-seo-desc').val(page.seo?.description || '');
|
|
461
|
-
if (page.dconfig) {
|
|
462
|
-
$container.find('#field-dconfig').val(JSON.stringify(page.dconfig, null, 2));
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// Layouts dropdown
|
|
467
|
-
const $layoutSelect = $container.find('#field-layout').empty();
|
|
468
|
-
Object.entries(layouts).forEach(([key, preset]) => {
|
|
469
|
-
const selected = (page?.layout || 'default') === key ? 'selected' : '';
|
|
470
|
-
$layoutSelect.append(`<option value="${key}" ${selected}>${preset.label || key}</option>`);
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
// Initialise meta tabs
|
|
474
|
-
E.tabs($container.find('#editor-meta-tabs').get(0));
|
|
475
|
-
|
|
476
|
-
// Markdown editor + live preview
|
|
477
|
-
const $editor = $container.find('#markdown-editor');
|
|
478
|
-
const $preview = $container.find('#markdown-preview');
|
|
479
|
-
if (page) $editor.val(page.content || '');
|
|
480
|
-
|
|
481
|
-
let _previewTimer = null;
|
|
482
|
-
const updatePreview = () => {
|
|
483
|
-
clearTimeout(_previewTimer);
|
|
484
|
-
_previewTimer = setTimeout(async () => {
|
|
485
|
-
try {
|
|
486
|
-
const {html} = await api.pages.preview($editor.val());
|
|
487
|
-
$preview.html(html);
|
|
488
|
-
Domma.icons.scan();
|
|
489
|
-
} catch {
|
|
490
|
-
if (window.marked) $preview.html(marked.parse($editor.val()));
|
|
491
|
-
}
|
|
492
|
-
}, 400);
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
// Create toolbar
|
|
496
|
-
const toolbar = createToolbar($editor, $container.find('#editor-toolbar'));
|
|
497
|
-
|
|
498
|
-
// Link callback: prompt for URL
|
|
499
|
-
toolbar.onLink((ta) => {
|
|
500
|
-
const url = prompt('Enter URL:');
|
|
501
|
-
if (url) wrapSelection(ta, '[', `](${url})`);
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
// Image callback: open media picker modal
|
|
505
|
-
toolbar.onImage(async (ta) => {
|
|
506
|
-
const files = await api.media.list().catch(() => []);
|
|
507
|
-
const images = files.filter(f => /\.(png|jpe?g|gif|webp|svg)$/i.test(f.name));
|
|
508
|
-
|
|
509
|
-
const grid = document.createElement('div');
|
|
510
|
-
grid.className = 'media-picker-grid';
|
|
511
|
-
|
|
512
|
-
if (images.length) {
|
|
513
|
-
images.forEach(img => {
|
|
514
|
-
const item = document.createElement('div');
|
|
515
|
-
item.className = 'media-picker-item';
|
|
516
|
-
item.dataset.url = img.url;
|
|
517
|
-
|
|
518
|
-
const image = document.createElement('img');
|
|
519
|
-
image.src = img.url;
|
|
520
|
-
image.alt = img.name;
|
|
521
|
-
|
|
522
|
-
const label = document.createElement('span');
|
|
523
|
-
label.textContent = img.name;
|
|
524
|
-
|
|
525
|
-
item.appendChild(image);
|
|
526
|
-
item.appendChild(label);
|
|
527
|
-
grid.appendChild(item);
|
|
528
|
-
});
|
|
529
|
-
} else {
|
|
530
|
-
const empty = document.createElement('p');
|
|
531
|
-
empty.className = 'text-muted p-3';
|
|
532
|
-
empty.textContent = 'No images uploaded yet.';
|
|
533
|
-
grid.appendChild(empty);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
const modal = E.modal({title: 'Insert Image', size: 'lg'});
|
|
537
|
-
modal.element.appendChild(grid);
|
|
538
|
-
|
|
539
|
-
$(modal.element).on('click', '.media-picker-item', function () {
|
|
540
|
-
const url = $(this).data('url');
|
|
541
|
-
const alt = $(this).find('span').text();
|
|
542
|
-
insertAtCursor(ta, ``);
|
|
543
|
-
modal.close();
|
|
544
|
-
$editor.get(0).dispatchEvent(new Event('input', {bubbles: true}));
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
modal.open();
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
// Help callback: open editor reference slideover
|
|
551
|
-
toolbar.onHelp(() => {
|
|
552
|
-
openEditorHelp();
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
// View mode switching
|
|
556
|
-
$container.find('.editor-view-btn').on('click', function () {
|
|
557
|
-
const mode = $(this).data('mode');
|
|
558
|
-
$container.find('.editor-view-btn').removeClass('active');
|
|
559
|
-
$(this).addClass('active');
|
|
560
|
-
$container.find('#editor-body')
|
|
561
|
-
.removeClass('editor-mode-split editor-mode-write editor-mode-preview')
|
|
562
|
-
.addClass(`editor-mode-${mode}`);
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
// Fullscreen toggle
|
|
566
|
-
$container.find('#fullscreen-btn').on('click', function () {
|
|
567
|
-
$container.find('.editor-card').toggleClass('editor-fullscreen');
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
// Dirty tracking + unsaved-changes guard
|
|
571
|
-
_dirty = false;
|
|
572
|
-
if (_beforeUnload) window.removeEventListener('beforeunload', _beforeUnload);
|
|
573
|
-
_beforeUnload = (e) => {
|
|
574
|
-
if (_dirty) e.preventDefault();
|
|
575
|
-
};
|
|
576
|
-
window.addEventListener('beforeunload', _beforeUnload);
|
|
577
|
-
|
|
578
|
-
// SPA navigation guard — registered once at module level
|
|
579
|
-
if (!_guardRegistered) {
|
|
580
|
-
_guardRegistered = true;
|
|
581
|
-
R.use((to, from, next) => {
|
|
582
|
-
const onEditor = window.location.hash.startsWith('#/pages/edit') ||
|
|
583
|
-
window.location.hash === '#/pages/new';
|
|
584
|
-
if (!_dirty || !onEditor) return next();
|
|
585
|
-
E.confirm('You have unsaved changes. Leave this page?').then(ok => {
|
|
586
|
-
if (ok) {
|
|
587
|
-
_dirty = false;
|
|
588
|
-
next();
|
|
589
|
-
}
|
|
590
|
-
});
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
$editor.on('input', () => {
|
|
595
|
-
_dirty = true;
|
|
596
|
-
updatePreview();
|
|
597
|
-
});
|
|
598
|
-
updatePreview();
|
|
599
|
-
|
|
600
|
-
// Save
|
|
601
|
-
$container.find('#save-btn').on('click', async () => {
|
|
602
|
-
const enteredPath = $container.find('#page-url-path').val().trim();
|
|
603
|
-
if (!enteredPath) { E.toast('URL path is required.', { type: 'warning' }); return; }
|
|
604
|
-
|
|
605
|
-
// Validate DConfig JSON before save attempt
|
|
606
|
-
const dconfigRaw = $container.find('#field-dconfig').val().trim();
|
|
607
|
-
let dconfig = null;
|
|
608
|
-
if (dconfigRaw) {
|
|
609
|
-
try {
|
|
610
|
-
dconfig = JSON.parse(dconfigRaw);
|
|
611
|
-
} catch {
|
|
612
|
-
E.toast('DConfig JSON is invalid. Please check the format before saving.', {type: 'warning'});
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
const frontmatter = {
|
|
618
|
-
title: $container.find('#field-title').val().trim() || 'Untitled',
|
|
619
|
-
description: $container.find('#field-description').val().trim(),
|
|
620
|
-
layout: $container.find('#field-layout').val(),
|
|
621
|
-
status: $container.find('#field-status').val(),
|
|
622
|
-
sortOrder: parseInt($container.find('#field-sort-order').val(), 10) || 99,
|
|
623
|
-
showInNav: $container.find('#field-show-in-nav').is(':checked'),
|
|
624
|
-
sidebar: $container.find('#field-sidebar').is(':checked'),
|
|
625
|
-
category: $container.find('#field-category').val().trim() || null,
|
|
626
|
-
visibility: $container.find('#field-visibility').val() || 'public',
|
|
627
|
-
seo: {
|
|
628
|
-
title: $container.find('#field-seo-title').val().trim(),
|
|
629
|
-
description: $container.find('#field-seo-desc').val().trim()
|
|
630
|
-
},
|
|
631
|
-
dconfig
|
|
632
|
-
};
|
|
633
|
-
|
|
634
|
-
try {
|
|
635
|
-
$container.find('#save-btn').prop('disabled', true).text('Saving…');
|
|
636
|
-
if (isEdit) {
|
|
637
|
-
const payload = { frontmatter, body: $editor.val() };
|
|
638
|
-
if (enteredPath !== urlPath) payload.newUrlPath = enteredPath;
|
|
639
|
-
await api.pages.update(urlPath, payload);
|
|
640
|
-
E.toast('Page saved successfully.', { type: 'success' });
|
|
641
|
-
_dirty = false;
|
|
642
|
-
if (enteredPath !== urlPath) R.navigate(`/pages/edit${enteredPath}`);
|
|
643
|
-
} else {
|
|
644
|
-
await api.pages.create({ urlPath: enteredPath, frontmatter, body: $editor.val() });
|
|
645
|
-
E.toast('Page saved successfully.', { type: 'success' });
|
|
646
|
-
_dirty = false;
|
|
647
|
-
R.navigate('/pages');
|
|
648
|
-
}
|
|
649
|
-
} catch (err) {
|
|
650
|
-
E.toast(`Save failed: ${err.message || 'Unknown error'}`, { type: 'error' });
|
|
651
|
-
} finally {
|
|
652
|
-
$container.find('#save-btn').prop('disabled', false).text('Save');
|
|
653
|
-
}
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
// Cancel
|
|
657
|
-
$container.find('#cancel-btn').on('click', () => R.navigate('/pages'));
|
|
658
|
-
|
|
659
|
-
Domma.icons.scan();
|
|
660
|
-
}
|
|
661
|
-
};
|
|
1
|
+
import{api as x}from"../api.js";import{createToolbar as _,insertAtCursor as j,wrapSelection as J}from"../lib/markdown-toolbar.js";function Q(){const e=E.slideover({title:"Editor Reference",size:"md",position:"right"}),M="background:var(--dm-surface-subtle,#1a1a2e);padding:.2rem .4rem;border-radius:3px;font-size:.85em;font-family:monospace;",B="margin:1rem 0 .5rem;font-size:1rem;";function f(l){const n=document.createElement("code");return n.style.cssText=M,n.textContent=l,n}function c(l){const n=document.createElement("h3");return n.style.cssText=B,n.textContent=l,n}const g=document.createElement("div");g.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:1rem;";const z=document.createElement("div");z.appendChild(c("Markdown Basics"));const S=document.createElement("table");S.style.cssText="width:100%;border-collapse:collapse;font-size:.9em;";const s=document.createElement("thead"),U=document.createElement("tr");["Syntax","Result"].forEach(l=>{const n=document.createElement("th");n.style.cssText="text-align:left;padding:.4rem .5rem;border-bottom:1px solid var(--dm-border,#333);",n.textContent=l,U.appendChild(n)}),s.appendChild(U),S.appendChild(s);const F=document.createElement("tbody");[["**bold**","bold (rendered bold)"],["_italic_","italic (rendered italic)"],["## Heading","Heading (h2)"],["[text](url)","Link"],["","Image"],["- item","Bullet list"],["`code`","Inline code"],["---","Divider"]].forEach(([l,n])=>{const o=document.createElement("tr"),a=document.createElement("td");a.style.cssText="padding:.35rem .5rem;vertical-align:top;",a.appendChild(f(l));const d=document.createElement("td");d.style.cssText="padding:.35rem .5rem;vertical-align:top;color:var(--dm-text-muted,#aaa);font-size:.85em;",d.textContent=n,o.appendChild(a),o.appendChild(d),F.appendChild(o)}),S.appendChild(F),z.appendChild(S),g.appendChild(z);const C=document.createElement("div");C.appendChild(c("Grid & Columns"));function v(l){const n=document.createElement("p");return n.style.cssText="margin:.3rem 0 .6rem;font-size:.82em;color:var(--dm-text-muted,#aaa);",n.textContent=l,n}function i(l,n,o){const a=document.createElement("p");a.style.cssText="margin:.75rem 0 .25rem;font-size:.85em;font-weight:600;",a.textContent=l;const d=document.createElement("pre");d.style.cssText=M+"display:block;padding:.5rem .75rem;white-space:pre;overflow-x:auto;margin:0;",d.textContent=n;const D=document.createDocumentFragment();return D.appendChild(a),D.appendChild(d),o&&D.appendChild(v(o)),D}const y=document.createElement("table");y.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['cols="N"',"[grid] only","Number of columns (1\u201312)"],['gap="N"',"[grid], [row]","Gap between columns/rows (1\u20136)"],['span="N"',"[col] only","How many columns this cell spans"],['class="x"',"all","Add extra CSS classes"]].forEach(([l,n,o])=>{const a=document.createElement("tr");[l,n,o].forEach((d,D)=>{const A=document.createElement("td");A.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",D===0?A.appendChild(f(l)):(A.style.color="var(--dm-text-muted,#aaa)",A.textContent=d),a.appendChild(A)}),y.appendChild(a)}),C.appendChild(y),C.appendChild(i("Equal columns",`[grid cols="3" gap="4"]
|
|
2
|
+
[col]One[/col]
|
|
3
|
+
[col]Two[/col]
|
|
4
|
+
[col]Three[/col]
|
|
5
|
+
[/grid]`,"cols sets the track count; each [col] fills one track.")),C.appendChild(i("Column spanning (12-col base)",`[grid cols="12" gap="3"]
|
|
6
|
+
[col span="8"]Main content[/col]
|
|
7
|
+
[col span="4"]Sidebar[/col]
|
|
8
|
+
[/grid]`,"span values must add up to cols. Common splits: 8/4, 6/6, 9/3.")),C.appendChild(i("Row (flexbox, equal-width)",`[row gap="4"]
|
|
9
|
+
[col]Left[/col]
|
|
10
|
+
[col]Right[/col]
|
|
11
|
+
[/row]`,"Use [row] when you want equal-width columns without specifying a count.")),C.appendChild(i("Card in a grid",`[grid cols="3" gap="4"]
|
|
12
|
+
[col]
|
|
13
|
+
[card title="One"]Content[/card]
|
|
14
|
+
[/col]
|
|
15
|
+
[col]
|
|
16
|
+
[card title="Two"]Content[/card]
|
|
17
|
+
[/col]
|
|
18
|
+
[col]
|
|
19
|
+
[card title="Three"]Content[/card]
|
|
20
|
+
[/col]
|
|
21
|
+
[/grid]`,null)),g.appendChild(C);const t=document.createElement("div");t.appendChild(c("Cards")),t.appendChild(i("Basic card",`[card title="Optional Title"]
|
|
22
|
+
Markdown **works** here.
|
|
23
|
+
[/card]`,"Omit title for a card with no header.")),t.appendChild(i("Collapsible card",`[card title="Click to expand" collapsible="true"]
|
|
24
|
+
Hidden by default.
|
|
25
|
+
[/card]`,null)),g.appendChild(t);const r=document.createElement("div");r.appendChild(c("Hero")),r.appendChild(v("Full-width hero sections \u2014 no plugin required. Uses Domma's built-in Hero CSS component."));const m=document.createElement("table");m.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['title="..."',"Heading text"],['tagline="..."',"Subtitle text"],['size="sm|lg|full"',"Height variant (default: normal)"],['variant="dark|primary|gradient-blue|gradient-purple|gradient-sunset|gradient-ocean"',"Colour / gradient preset"],['image="url"',"Background image URL (adds cover mode)"],['overlay="light|dark|darker|gradient|gradient-reverse"',"Image overlay style"],['align="center|left"',"Content alignment (default: center)"],['fullwidth="true"',"Break out of the page container to span full viewport width"],['class="..."',"Extra CSS classes"],['id="..."',"Element id"]].forEach(([l,n])=>{const o=document.createElement("tr"),a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",a.appendChild(f(l));const d=document.createElement("td");d.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",d.textContent=n,o.appendChild(a),o.appendChild(d),m.appendChild(o)}),r.appendChild(m),r.appendChild(i("Basic hero",'[hero title="Welcome" tagline="Build something great"][/hero]',null)),r.appendChild(i("Gradient with body content",`[hero title="Get Started" tagline="Everything you need." size="lg" variant="gradient-blue" align="center"]
|
|
26
|
+
Some introductory **Markdown** content here.
|
|
27
|
+
[/hero]`,null)),r.appendChild(i("Background image with overlay",'[hero title="Our Story" image="/media/hero.jpg" overlay="dark" size="full"][/hero]',"Combine image + overlay for text legibility over photos.")),g.appendChild(r);const p=document.createElement("div");p.appendChild(c("Interactive Components")),p.appendChild(v("Tabs, accordion, carousel, and countdown \u2014 no plugin required.")),p.appendChild(i("Tabs",`[tabs]
|
|
28
|
+
[tab title="First"]Content **A**[/tab]
|
|
29
|
+
[tab title="Second"]Content **B**[/tab]
|
|
30
|
+
[/tabs]`,'Add style="pills" for pill-style navigation.')),p.appendChild(i("Accordion",`[accordion]
|
|
31
|
+
[item title="Question 1"]Answer here.[/item]
|
|
32
|
+
[item title="Question 2"]Answer here.[/item]
|
|
33
|
+
[/accordion]`,'Add multiple="true" to allow several panels open at once.')),p.appendChild(i("Carousel",`[carousel autoplay="true" interval="5000"]
|
|
34
|
+
[slide title="Slide 1"]Description[/slide]
|
|
35
|
+
[slide image="/media/photo.jpg" title="Slide 2"]Caption[/slide]
|
|
36
|
+
[/carousel]`,'Omit autoplay for a manual carousel. loop="false" disables wrapping.')),p.appendChild(i("Countdown (to date)",'[countdown to="2026-12-31" format="DD:HH:mm:ss" /]',null)),p.appendChild(i("Countdown (duration)",'[countdown duration="300" format="mm:ss" /]',"duration is in seconds. format options: mm:ss \xB7 HH:mm:ss \xB7 DD:HH:mm:ss")),g.appendChild(p);const u=document.createElement("div");u.appendChild(c("Embedding a Form"));const h=document.createElement("pre");h.style.cssText=M+"display:block;padding:.5rem .75rem;white-space:pre;overflow-x:auto;",h.textContent='<div data-form="contact"></div>',u.appendChild(h),u.appendChild(v('Replace "contact" with the form slug. Forms are managed under Forms in the sidebar.')),g.appendChild(u);const b=document.createElement("div");b.appendChild(c("Effects")),b.appendChild(v("Shortcodes for scroll-triggered animations, counters, typewriter text, and more. Requires the Domma Effects plugin."));const P=[['[reveal animation="fade"]...[/reveal]',"Fade/slide/zoom on scroll"],["[breathe]...[/breathe]","Continuous gentle scale loop"],["[pulse]...[/pulse]","Repeating scale pulse"],["[shake]...[/shake]","One-shot shake"],['[scribe speed="50"]...[/scribe]',"Typewriter \u2014 simple mode (text typed letter by letter)"],['[scribe loop="true"][render]Text[/render][wait]1500[/wait][undo /][/scribe]',"Typewriter \u2014 script mode (sequenced render/wait/undo actions)"],["[scramble]...[/scramble]","Character-scramble reveal"],['[counter to="100" /]',"Animated number counter (self-closing)"],["[ripple]...[/ripple]","Click-triggered ripple"],['[twinkle count="50"]...[/twinkle]',"Particle/star overlay"],['[animate type="fade-in-up"]...[/animate]',"CSS-only animation (no plugin needed)"],['[ambient type="aurora"]...[/ambient]',"CSS-only animated background"],['[firework type="burst" colour="rainbow" /]',"CSS firework (self-closing)"],["[fireworks]...[/fireworks]","Fireworks display container"],['[celebrate theme="auto" /]',"Seasonal canvas celebration"]],T=document.createElement("table");T.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",P.forEach(([l,n])=>{const o=document.createElement("tr"),a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",a.appendChild(f(l));const d=document.createElement("td");d.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",d.textContent=n,o.appendChild(a),o.appendChild(d),T.appendChild(o)}),b.appendChild(T),b.appendChild(i("Scribe script \u2014 looping multi-phrase typewriter",`[scribe loop="true" loop-delay="2000" delete-speed="20"]
|
|
37
|
+
[render]We build things that matter.[/render]
|
|
38
|
+
[wait]1200[/wait]
|
|
39
|
+
[undo all="true" /]
|
|
40
|
+
[render]We design for humans.[/render]
|
|
41
|
+
[wait]1200[/wait]
|
|
42
|
+
[undo all="true" /]
|
|
43
|
+
[/scribe]`,'Use [render]...[/render] to type text, [wait]Ms[/wait] to pause, and [undo /] to delete. loop="true" replays the sequence. Only [render], [wait], and [undo] shortcodes are parsed \u2014 plain text between actions is ignored.')),b.appendChild(v("Enable the Domma Effects plugin to activate these shortcodes on your site.")),g.appendChild(b);const H=document.createElement("div");H.appendChild(c("Tables")),H.appendChild(v("Wrap a standard Markdown (GFM) table with Domma CSS classes and responsive horizontal scrolling."));const q=document.createElement("table");q.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['striped="true"',"Alternate row background (.table-striped)"],['bordered="true"',"Borders on all cells (.table-bordered)"],['compact="true"',"Reduced cell padding (.table-compact)"],['caption="..."',"Caption text above the table"],['class="..."',"Extra CSS classes appended to .table"],['id="..."',"id attribute on the <table> element"]].forEach(([l,n])=>{const o=document.createElement("tr"),a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",a.appendChild(f(l));const d=document.createElement("td");d.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",d.textContent=n,o.appendChild(a),o.appendChild(d),q.appendChild(o)}),H.appendChild(q),H.appendChild(i("Striped + bordered table",`[table striped="true" bordered="true" caption="Product Pricing"]
|
|
44
|
+
| Product | Price |
|
|
45
|
+
| ------- | ----: |
|
|
46
|
+
| Widget | $9.99 |
|
|
47
|
+
| Gadget | $14.99 |
|
|
48
|
+
[/table]`,"The inner content must be a valid GFM Markdown table. The shortcode adds Domma CSS classes and wraps in a responsive scroll container.")),g.appendChild(H);const N=document.createElement("div");N.appendChild(c("Slideover")),N.appendChild(v("A trigger button that opens a slide-in panel with Markdown content. No plugin required."));const G=document.createElement("table");G.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['title="..."',"Panel header text"],['trigger="..."','Button label (default: "Open")'],['size="sm|md|lg"',"Panel width (default: md)"],['position="right|left"',"Slide direction (default: right)"]].forEach(([l,n])=>{const o=document.createElement("tr"),a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",a.appendChild(f(l));const d=document.createElement("td");d.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",d.textContent=n,o.appendChild(a),o.appendChild(d),G.appendChild(o)}),N.appendChild(G),N.appendChild(i("Example",`[slideover title="More Info" trigger="Read more" size="md"]
|
|
49
|
+
## Details
|
|
50
|
+
Markdown content here.
|
|
51
|
+
[/slideover]`,"Nested [card] and [grid] shortcodes work inside the slideover body.")),g.appendChild(N);const O=document.createElement("div");O.appendChild(c("DConfig \u2014 Declarative Behaviour")),O.appendChild(v("Define click handlers and class toggles without writing JavaScript. Use the DConfig section above or embed inline with [dconfig]...[/dconfig] in the content body. Inline shortcodes win on selector conflict.")),O.appendChild(i("Toggle a class on click",'{ "#my-btn": { "events": { "click": { "target": "#panel", "toggleClass": "hidden" } } } }','Selector keys use standard CSS selectors. "target" is optional \u2014 defaults to the element itself.')),O.appendChild(i("Inline shortcode syntax",`[dconfig]
|
|
52
|
+
{ "#my-btn": { "events": { "click": { "target": "#panel", "toggleClass": "hidden" } } } }
|
|
53
|
+
[/dconfig]`,null)),g.appendChild(O),e.setContent(g),e.open()}let k=!1,L=null,W=!1;export const pageEditorView={templateUrl:"/admin/js/templates/page-editor.html",async onMount(e){const B=window.location.hash.match(/#\/pages\/edit(\/.*)/),f=B?B[1]:null,c=!!f,g=[x.layouts.get().catch(()=>({})),x.settings.get().catch(()=>({}))];c&&g.push(x.pages.get(f).catch(()=>null));const[z,S,s]=await Promise.all(g),U=S?.layoutOptions?.spacerSize??40;if(c&&!s){E.toast("Page not found.",{type:"error"}),R.navigate("/pages");return}e.find("#editor-title").text(c?"Edit Page":"New Page"),c&&e.find("#page-url-path").val(f),s&&(e.find("#field-title").val(s.title||""),e.find("#field-description").val(s.description||""),e.find("#field-status").val(s.status||"draft"),e.find("#field-sort-order").val(s.sortOrder??99),e.find("#field-show-in-nav").prop("checked",!!s.showInNav),e.find("#field-sidebar").prop("checked",!!s.sidebar),e.find("#field-category").val(s.category||""),e.find("#field-visibility").val(s.visibility||"public"),e.find("#field-seo-title").val(s.seo?.title||""),e.find("#field-seo-desc").val(s.seo?.description||""),s.dconfig&&e.find("#field-dconfig").val(JSON.stringify(s.dconfig,null,2)));const F=e.find("#field-layout").empty();if(Object.entries(z).forEach(([t,r])=>{const m=(s?.layout||"default")===t?"selected":"";F.append(`<option value="${t}" ${m}>${r.label||t}</option>`)}),c){const t=e.find("#view-page-btn").get(0);t.href=f,t.style.display="";const r=e.find("#live-preview-tab").get(0);r.style.display=""}if(E.tabs(e.find("#editor-meta-tabs").get(0)),c){const t=e.find("#live-preview-tab").get(0),r=e.find("#live-preview-frame").get(0);let m=!1;t.addEventListener("click",function(){m||(r.src=f,m=!0)})}const w=e.find("#markdown-editor"),C=e.find("#markdown-preview");s&&w.val(s.content||"");let v=null;const i=()=>{clearTimeout(v),v=setTimeout(async()=>{try{const{html:t}=await x.pages.preview(w.val());C.html(t,{safe:!1}),I.scan(C.get(0))}catch{window.marked&&C.html(marked.parse(w.val()))}},400)},y=_(w,e.find("#editor-toolbar"),{spacerDefault:U});y.onLink(t=>{const r=prompt("Enter URL:");r&&J(t,"[",`](${r})`)}),y.onImage(async t=>{const m=(await x.media.list().catch(()=>[])).filter(h=>/\.(png|jpe?g|gif|webp|svg)$/i.test(h.name)),p=document.createElement("div");if(p.className="media-picker-grid",m.length)m.forEach(h=>{const b=document.createElement("div");b.className="media-picker-item",b.dataset.url=h.url;const P=document.createElement("img");P.src=h.url,P.alt=h.name;const T=document.createElement("span");T.textContent=h.name,b.appendChild(P),b.appendChild(T),p.appendChild(b)});else{const h=document.createElement("p");h.className="text-muted p-3",h.textContent="No images uploaded yet.",p.appendChild(h)}const u=E.modal({title:"Insert Image",size:"lg"});u.element.appendChild(p),$(u.element).on("click",".media-picker-item",function(){const h=$(this).data("url"),b=$(this).find("span").text();j(t,``),u.close(),w.get(0).dispatchEvent(new Event("input",{bubbles:!0}))}),u.open()}),y.onHelp(()=>{Q()}),e.find(".editor-view-btn").on("click",function(){const t=$(this).data("mode");e.find(".editor-view-btn").removeClass("active"),$(this).addClass("active"),e.find("#editor-body").removeClass("editor-mode-split editor-mode-write editor-mode-preview").addClass(`editor-mode-${t}`)}),e.find("#fullscreen-btn").on("click",function(){e.find(".editor-card").toggleClass("editor-fullscreen")}),k=!1,L&&window.removeEventListener("beforeunload",L),L=t=>{k&&t.preventDefault()},window.addEventListener("beforeunload",L),W||(W=!0,R.use((t,r,m)=>{const p=window.location.hash.startsWith("#/pages/edit")||window.location.hash==="#/pages/new";if(!k||!p)return m();E.confirm("You have unsaved changes. Leave this page?").then(u=>{u&&(k=!1,m())})})),w.on("input",()=>{k=!0,i()}),i(),e.find("#save-btn").on("click",async()=>{const t=e.find("#page-url-path").val().trim();if(!t){E.toast("URL path is required.",{type:"warning"});return}const r=e.find("#field-dconfig").val().trim();let m=null;if(r)try{m=JSON.parse(r)}catch{E.toast("DConfig JSON is invalid. Please check the format before saving.",{type:"warning"});return}const p={title:e.find("#field-title").val().trim()||"Untitled",description:e.find("#field-description").val().trim(),layout:e.find("#field-layout").val(),status:e.find("#field-status").val(),sortOrder:parseInt(e.find("#field-sort-order").val(),10)||99,showInNav:e.find("#field-show-in-nav").is(":checked"),sidebar:e.find("#field-sidebar").is(":checked"),category:e.find("#field-category").val().trim()||null,visibility:e.find("#field-visibility").val()||"public",seo:{title:e.find("#field-seo-title").val().trim(),description:e.find("#field-seo-desc").val().trim()},dconfig:m};try{if(e.find("#save-btn").prop("disabled",!0).text("Saving\u2026"),c){const u={frontmatter:p,body:w.val()};t!==f&&(u.newUrlPath=t),await x.pages.update(f,u),E.toast("Page saved successfully.",{type:"success"}),k=!1,t!==f&&R.navigate(`/pages/edit${t}`)}else await x.pages.create({urlPath:t,frontmatter:p,body:w.val()}),E.toast("Page saved successfully.",{type:"success"}),k=!1,R.navigate("/pages")}catch(u){E.toast(`Save failed: ${u.message||"Unknown error"}`,{type:"error"})}finally{e.find("#save-btn").prop("disabled",!1).text("Save")}}),e.find("#cancel-btn").on("click",()=>R.navigate("/pages")),Domma.icons.scan()}};
|