domma-cms 0.2.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +3 -3
  2. package/admin/css/admin.css +1 -1200
  3. package/admin/dist/domma/domma-tools.css +2313 -0
  4. package/admin/dist/domma/domma-tools.min.js +10 -0
  5. package/admin/index.html +4 -0
  6. package/admin/js/api.js +1 -242
  7. package/admin/js/app.js +9 -279
  8. package/admin/js/config/sidebar-config.js +1 -115
  9. package/admin/js/lib/card.js +1 -63
  10. package/admin/js/lib/image-editor.js +1 -869
  11. package/admin/js/lib/markdown-toolbar.js +54 -421
  12. package/admin/js/templates/action-editor.html +171 -0
  13. package/admin/js/templates/actions-list.html +19 -0
  14. package/admin/js/templates/api-reference.html +1411 -0
  15. package/admin/js/templates/block-editor.html +158 -0
  16. package/admin/js/templates/blocks.html +8 -0
  17. package/admin/js/templates/collection-editor.html +47 -0
  18. package/admin/js/templates/collection-entries.html +3 -0
  19. package/admin/js/templates/collections.html +51 -4
  20. package/admin/js/templates/documentation.html +258 -0
  21. package/admin/js/templates/form-editor.html +238 -0
  22. package/{plugins/form-builder/admin → admin/js}/templates/form-submissions.html +30 -30
  23. package/{plugins/form-builder/admin/templates/forms-list.html → admin/js/templates/forms.html} +17 -17
  24. package/admin/js/templates/layouts.html +44 -7
  25. package/admin/js/templates/login.html +29 -4
  26. package/admin/js/templates/my-profile.html +17 -0
  27. package/admin/js/templates/page-editor.html +48 -0
  28. package/admin/js/templates/pages.html +6 -1
  29. package/admin/js/templates/pro-docs.html +259 -0
  30. package/admin/js/templates/role-editor.html +59 -0
  31. package/admin/js/templates/roles.html +10 -0
  32. package/admin/js/templates/settings.html +137 -18
  33. package/admin/js/templates/tutorials.html +81 -0
  34. package/admin/js/templates/user-editor.html +7 -0
  35. package/admin/js/templates/users.html +3 -1
  36. package/admin/js/templates/view-editor.html +201 -0
  37. package/admin/js/templates/view-preview.html +51 -0
  38. package/admin/js/templates/views-list.html +19 -0
  39. package/admin/js/views/action-editor.js +1 -0
  40. package/admin/js/views/actions-list.js +1 -0
  41. package/admin/js/views/api-reference.js +1 -0
  42. package/admin/js/views/block-editor.js +8 -0
  43. package/admin/js/views/blocks.js +4 -0
  44. package/admin/js/views/collection-editor.js +3 -487
  45. package/admin/js/views/collection-entries.js +1 -484
  46. package/admin/js/views/collections.js +1 -153
  47. package/admin/js/views/dashboard.js +1 -56
  48. package/admin/js/views/documentation.js +1 -12
  49. package/admin/js/views/form-editor.js +8 -0
  50. package/admin/js/views/form-submissions.js +1 -0
  51. package/admin/js/views/forms.js +1 -0
  52. package/admin/js/views/index.js +1 -39
  53. package/admin/js/views/layouts.js +9 -42
  54. package/admin/js/views/login.js +7 -251
  55. package/admin/js/views/media.js +1 -240
  56. package/admin/js/views/my-profile.js +1 -0
  57. package/admin/js/views/navigation.js +14 -212
  58. package/admin/js/views/page-editor.js +72 -661
  59. package/admin/js/views/pages.js +5 -72
  60. package/admin/js/views/plugins.js +13 -90
  61. package/admin/js/views/pro-docs.js +1 -0
  62. package/admin/js/views/role-editor.js +1 -0
  63. package/admin/js/views/roles.js +4 -0
  64. package/admin/js/views/settings.js +3 -199
  65. package/admin/js/views/tutorials.js +1 -12
  66. package/admin/js/views/user-editor.js +1 -88
  67. package/admin/js/views/users.js +4 -76
  68. package/admin/js/views/view-editor.js +1 -0
  69. package/admin/js/views/view-preview.js +1 -0
  70. package/admin/js/views/views-list.js +1 -0
  71. package/bin/cli.js +1 -1
  72. package/config/auth.json +2 -17
  73. package/config/connections.json.bak +9 -0
  74. package/config/connections.json.example +9 -0
  75. package/config/navigation.json +15 -0
  76. package/config/plugins.json +19 -29
  77. package/config/server.json +6 -6
  78. package/config/site.json +17 -6
  79. package/package.json +24 -10
  80. package/plugins/domma-effects/public/celebrations/core/canvas.js +2 -104
  81. package/plugins/domma-effects/public/celebrations/core/particles.js +1 -144
  82. package/plugins/domma-effects/public/celebrations/core/physics.js +1 -166
  83. package/plugins/domma-effects/public/celebrations/index.js +1 -535
  84. package/plugins/domma-effects/public/celebrations/themes/christmas.js +1 -1805
  85. package/plugins/domma-effects/public/celebrations/themes/guy-fawkes.js +1 -1477
  86. package/plugins/domma-effects/public/celebrations/themes/halloween.js +1 -1837
  87. package/plugins/domma-effects/public/celebrations/themes/st-andrews.js +1 -1175
  88. package/plugins/domma-effects/public/celebrations/themes/st-davids.js +1 -1258
  89. package/plugins/domma-effects/public/celebrations/themes/st-georges.js +1 -1754
  90. package/plugins/domma-effects/public/celebrations/themes/st-patricks.js +1 -1290
  91. package/plugins/domma-effects/public/celebrations/themes/valentines.js +1 -1361
  92. package/plugins/example-analytics/stats.json +21 -12
  93. package/plugins/theme-roller/admin/templates/theme-roller.html +71 -0
  94. package/plugins/theme-roller/admin/views/theme-roller-view.js +403 -0
  95. package/plugins/theme-roller/config.js +1 -0
  96. package/plugins/theme-roller/plugin.js +233 -0
  97. package/plugins/theme-roller/plugin.json +31 -0
  98. package/plugins/theme-roller/public/active-theme.css +0 -0
  99. package/plugins/theme-roller/public/inject-head-late.html +1 -0
  100. package/public/css/forms.css +1 -0
  101. package/public/css/site.css +1 -302
  102. package/public/js/btt.js +1 -90
  103. package/public/js/cookie-consent.js +1 -61
  104. package/public/js/form-logic-engine.js +1 -0
  105. package/public/js/forms.js +1 -0
  106. package/public/js/site.js +1 -204
  107. package/scripts/build.js +194 -129
  108. package/scripts/pro.js +254 -0
  109. package/scripts/reset.js +33 -8
  110. package/scripts/seed.js +343 -78
  111. package/scripts/setup.js +5 -4
  112. package/server/middleware/auth.js +136 -97
  113. package/server/routes/api/actions.js +200 -0
  114. package/server/routes/api/auth.js +292 -116
  115. package/server/routes/api/blocks.js +84 -0
  116. package/server/routes/api/collections.js +88 -23
  117. package/{plugins/form-builder/plugin.js → server/routes/api/forms.js} +483 -505
  118. package/server/routes/api/layouts.js +49 -25
  119. package/server/routes/api/media.js +118 -93
  120. package/server/routes/api/navigation.js +40 -37
  121. package/server/routes/api/pages.js +132 -118
  122. package/server/routes/api/plugins.js +6 -3
  123. package/server/routes/api/settings.js +104 -89
  124. package/server/routes/api/users.js +27 -21
  125. package/server/routes/api/views.js +148 -0
  126. package/server/routes/public.js +124 -108
  127. package/server/server.js +269 -173
  128. package/server/services/actions.js +387 -0
  129. package/server/services/adapterRegistry.js +98 -0
  130. package/server/services/adapters/FileAdapter.js +192 -0
  131. package/server/services/adapters/MongoAdapter.js +220 -0
  132. package/server/services/blocks.js +162 -0
  133. package/server/services/collections.js +74 -86
  134. package/server/services/connectionManager.js +102 -0
  135. package/server/services/content.js +312 -307
  136. package/{plugins/form-builder → server/services}/email.js +126 -103
  137. package/server/services/forms.js +173 -0
  138. package/server/services/markdown.js +1378 -648
  139. package/server/services/permissionRegistry.js +173 -0
  140. package/server/services/presetCollections.js +251 -0
  141. package/server/services/renderer.js +75 -1
  142. package/server/services/roles.js +227 -0
  143. package/server/services/rowAccess.js +104 -0
  144. package/server/services/userProfiles.js +199 -0
  145. package/server/services/users.js +281 -212
  146. package/server/services/views.js +280 -0
  147. package/server/templates/page.html +119 -113
  148. package/plugins/form-builder/admin/templates/form-editor.html +0 -171
  149. package/plugins/form-builder/admin/templates/form-settings.html +0 -29
  150. package/plugins/form-builder/admin/views/form-editor.js +0 -1442
  151. package/plugins/form-builder/admin/views/form-settings.js +0 -38
  152. package/plugins/form-builder/admin/views/form-submissions.js +0 -295
  153. package/plugins/form-builder/admin/views/forms-list.js +0 -164
  154. package/plugins/form-builder/config.js +0 -9
  155. package/plugins/form-builder/data/forms/consent.json +0 -104
  156. package/plugins/form-builder/data/forms/contact-details.json +0 -63
  157. package/plugins/form-builder/data/forms/contacts.json +0 -66
  158. package/plugins/form-builder/data/submissions/consent.json +0 -13
  159. package/plugins/form-builder/data/submissions/contact-details.json +0 -1
  160. package/plugins/form-builder/data/submissions/contacts.json +0 -26
  161. package/plugins/form-builder/plugin.json +0 -52
  162. package/plugins/form-builder/public/form-logic-engine.js +0 -568
  163. package/plugins/form-builder/public/inject-body.html +0 -352
  164. package/plugins/form-builder/public/inject-head.html +0 -58
  165. package/plugins/form-builder/public/package.json +0 -1
  166. package/scripts/copy-domma.js +0 -48
@@ -1,661 +1,72 @@
1
- /**
2
- * Page Editor View
3
- * Handles both create (new) and edit (existing) modes.
4
- * Provides a split-pane Markdown editor with live preview, toolbar, and media picker.
5
- */
6
- import {api} from '../api.js';
7
- import {createToolbar, insertAtCursor, wrapSelection} from '../lib/markdown-toolbar.js';
8
-
9
- /**
10
- * Builds and opens the editor help slideover with a cheat-sheet reference.
11
- */
12
- function openEditorHelp() {
13
- const so = E.slideover({ title: 'Editor Reference', size: 'md', position: 'right' });
14
-
15
- const codeStyle = 'background:var(--dm-surface-subtle,#1a1a2e);padding:.2rem .4rem;border-radius:3px;font-size:.85em;font-family:monospace;';
16
- const h3Style = 'margin:1rem 0 .5rem;font-size:1rem;';
17
-
18
- function makeCode(text) {
19
- const el = document.createElement('code');
20
- el.style.cssText = codeStyle;
21
- el.textContent = text;
22
- return el;
23
- }
24
-
25
- function makeH3(text) {
26
- const el = document.createElement('h3');
27
- el.style.cssText = h3Style;
28
- el.textContent = text;
29
- return el;
30
- }
31
-
32
- const wrap = document.createElement('div');
33
- wrap.style.cssText = 'padding:1rem;display:flex;flex-direction:column;gap:1rem;';
34
-
35
- // --- Section 1: Markdown Basics ---
36
- const section1 = document.createElement('div');
37
- section1.appendChild(makeH3('Markdown Basics'));
38
-
39
- const table = document.createElement('table');
40
- table.style.cssText = 'width:100%;border-collapse:collapse;font-size:.9em;';
41
-
42
- const thead = document.createElement('thead');
43
- const headerRow = document.createElement('tr');
44
- ['Syntax', 'Result'].forEach(text => {
45
- const th = document.createElement('th');
46
- th.style.cssText = 'text-align:left;padding:.4rem .5rem;border-bottom:1px solid var(--dm-border,#333);';
47
- th.textContent = text;
48
- headerRow.appendChild(th);
49
- });
50
- thead.appendChild(headerRow);
51
- table.appendChild(thead);
52
-
53
- const tbody = document.createElement('tbody');
54
- const rows = [
55
- ['**bold**', 'bold (rendered bold)'],
56
- ['_italic_', 'italic (rendered italic)'],
57
- ['## Heading', 'Heading (h2)'],
58
- ['[text](url)', 'Link'],
59
- ['![alt](url)', '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, `![${alt}](${url})`);
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 U}from"../api.js";import{createToolbar as ye,insertAtCursor as he}from"../lib/markdown-toolbar.js";function xe(){const o=E.slideover({title:"Editor Reference",size:"md",position:"right"}),be="background:var(--dm-surface-subtle,#1a1a2e);padding:.2rem .4rem;border-radius:3px;font-size:.85em;font-family:monospace;",ge="margin:1rem 0 .5rem;font-size:1rem;";function T(i){const d=document.createElement("code");return d.style.cssText=be,d.textContent=i,d}function C(i){const d=document.createElement("h3");return d.style.cssText=ge,d.textContent=i,d}const se=document.createElement("div");se.style.cssText="padding:1rem;";const Z=document.createElement("div");Z.className="tabs";const ce=document.createElement("div");ce.className="tab-list",["Basics","Layout","Components","Data","Advanced"].forEach((i,d)=>{const t=document.createElement("button");t.className="tab-item"+(d===0?" active":""),t.textContent=i,ce.appendChild(t)});const J=document.createElement("div");J.className="tab-content";const v="display:flex;flex-direction:column;gap:1rem;",ne=document.createElement("div");ne.className="tab-panel active",ne.style.cssText=v;const Q=document.createElement("div");Q.className="tab-panel",Q.style.cssText=v;const O=document.createElement("div");O.className="tab-panel",O.style.cssText=v;const G=document.createElement("div");G.className="tab-panel",G.style.cssText=v;const ae=document.createElement("div");ae.className="tab-panel",ae.style.cssText=v,J.appendChild(ne),J.appendChild(Q),J.appendChild(O),J.appendChild(G),J.appendChild(ae),Z.appendChild(ce),Z.appendChild(J),se.appendChild(Z);const q=document.createElement("div");q.appendChild(C("Markdown Basics"));const ee=document.createElement("table");ee.style.cssText="width:100%;border-collapse:collapse;font-size:.9em;";const j=document.createElement("thead"),te=document.createElement("tr");["Syntax","Result"].forEach(i=>{const d=document.createElement("th");d.style.cssText="text-align:left;padding:.4rem .5rem;border-bottom:1px solid var(--dm-border,#333);",d.textContent=i,te.appendChild(d)}),j.appendChild(te),ee.appendChild(j);const pe=document.createElement("tbody");[["**bold**","bold (rendered bold)"],["_italic_","italic (rendered italic)"],["## Heading","Heading (h2)"],["[text](url)","Link"],["![alt](url)","Image"],["- item","Bullet list"],["`code`","Inline code"],["---","Divider"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.35rem .5rem;vertical-align:top;",n.appendChild(T(i));const a=document.createElement("td");a.style.cssText="padding:.35rem .5rem;vertical-align:top;color:var(--dm-text-muted,#aaa);font-size:.85em;",a.textContent=d,t.appendChild(n),t.appendChild(a),pe.appendChild(t)}),ee.appendChild(pe),q.appendChild(ee),ne.appendChild(q);const _=document.createElement("div");_.appendChild(C("Grid & Columns"));function k(i){const d=document.createElement("p");return d.style.cssText="margin:.3rem 0 .6rem;font-size:.82em;color:var(--dm-text-muted,#aaa);",d.textContent=i,d}function e(i,d,t){const n=document.createElement("p");n.style.cssText="margin:.75rem 0 .25rem;font-size:.85em;font-weight:600;",n.textContent=i;const a=document.createElement("pre");a.style.cssText=be+"display:block;padding:.5rem .75rem;white-space:pre;overflow-x:auto;margin:0;",a.textContent=d;const re=document.createDocumentFragment();return re.appendChild(n),re.appendChild(a),t&&re.appendChild(k(t)),re}const f=document.createElement("table");f.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"],['fullwidth="true"',"[grid] only","Break out of page container to span full viewport"],['class="x"',"all","Add extra CSS classes"]].forEach(([i,d,t])=>{const n=document.createElement("tr");[i,d,t].forEach((a,re)=>{const ue=document.createElement("td");ue.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",re===0?ue.appendChild(T(i)):(ue.style.color="var(--dm-text-muted,#aaa)",ue.textContent=a),n.appendChild(ue)}),f.appendChild(n)}),_.appendChild(f),_.appendChild(e("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.")),_.appendChild(e("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.")),_.appendChild(e("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.")),_.appendChild(e("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)),Q.appendChild(_);const l=document.createElement("div");l.appendChild(C("Cards")),l.appendChild(e("Basic card",`[card title="Optional Title"]
22
+ Markdown **works** here.
23
+ [/card]`,"Omit title for a card with no header.")),l.appendChild(e("Icon \u2014 inline (default)",`[card title="Feature" icon="star"]
24
+ Icon sits left of the title in a flex row.
25
+ [/card]`,"icon accepts any Domma icon name. Default layout when icon is set.")),l.appendChild(e("Icon \u2014 stacked (centred)",`[card title="Feature" icon="star" icon-layout="stacked" hover]
26
+ Icon is centred above the title. Great for feature tiles.
27
+ [/card]`,'icon-layout="stacked" centres the icon above the title. Combine with hover for a lift effect.')),l.appendChild(e("Icon with subtitle",`[card title="Feature" subtitle="Tagline here" icon="star" icon-layout="stacked"]
28
+ Body content.
29
+ [/card]`,"subtitle adds a secondary line beneath the title in both layouts.")),l.appendChild(e("Collapsible card",`[card title="Click to expand" collapsible="true"]
30
+ Hidden by default.
31
+ [/card]`,null)),l.appendChild(e("Full attribute reference",`[card title="Title" subtitle="Sub" icon="star" icon-layout="stacked"
32
+ variant="primary" hover footer="Footer text"
33
+ collapsible="true" class="extra" id="my-card"]
34
+ Body content.
35
+ [/card]`,'All attributes are optional. variant accepts "primary". icon-layout accepts "inline" (default) or "stacked".')),O.appendChild(l);const c=document.createElement("div");c.appendChild(C("Badge")),c.appendChild(k("Inline badge/label elements. Self-closing renders an empty coloured dot."));const h=document.createElement("table");h.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['variant="..."',"primary (default), secondary, success, danger, warning, info, light, dark"],["pill","Flag: rounded pill shape (.badge-pill)"],["outline","Flag: outlined style (.badge-outline)"],['size="small|large"',"Reduced or enlarged badge"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(T(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),h.appendChild(t)}),c.appendChild(h),c.appendChild(e("Basic badge",'[badge variant="success"]New[/badge]',null)),c.appendChild(e("Pill outline badge",'[badge variant="danger" outline pill]Deprecated[/badge]',null)),O.appendChild(c);const r=document.createElement("div");r.appendChild(C("Spacer")),r.appendChild(k("Self-closing shortcode that inserts a fixed-height blank gap."));const b=document.createElement("table");b.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['size="N"',"Height in px (default: 8)"],['class="..."',"Extra CSS class on the spacer div"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(T(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),b.appendChild(t)}),r.appendChild(b),r.appendChild(e("32px gap",'[spacer size="32" /]',null)),Q.appendChild(r);const p=document.createElement("div");p.appendChild(C("Icon")),p.appendChild(k("Self-closing shortcode that renders any Domma icon inline."));const m=document.createElement("table");m.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['name="..."',"Domma icon name (required). Also accepted as src=."],['size="N"',"Width and height in px"],['color="..."',"CSS colour applied via style"],['class="..."',"Extra CSS classes"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(T(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),m.appendChild(t)}),p.appendChild(m),p.appendChild(e("Coloured icon",'[icon name="star" size="24" color="#f5a623" /]',null)),O.appendChild(p);const y=document.createElement("div");y.appendChild(C("Center")),y.appendChild(k("Wraps content in a centred div. Works with any content including cards, grids, and text.")),y.appendChild(e("Example","[center]Centred content here[/center]",'Accepts an optional class="..." attribute for extra styling.')),Q.appendChild(y);const s=document.createElement("div");s.appendChild(C("Hero")),s.appendChild(k("Full-width hero sections \u2014 no plugin required. Uses Domma's built-in Hero CSS component."));const g=document.createElement("table");g.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"],['bg="..."',"Background colour CSS value"],["twinkle","Flag: adds particle overlay (requires Effects plugin)"],['twinkle-count="N"',"Number of particles"],['twinkle-colour="..."',"Particle colour CSS value"],["blobs","Flag: adds ambient blob background"],['blobs-type="..."',"Blob animation type (default: float-blobs)"],['class="..."',"Extra CSS classes"],['id="..."',"Element id"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(T(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),g.appendChild(t)}),s.appendChild(g),s.appendChild(e("Basic hero",'[hero title="Welcome" tagline="Build something great"][/hero]',null)),s.appendChild(e("Gradient with body content",`[hero title="Get Started" tagline="Everything you need." size="lg" variant="gradient-blue" align="center"]
36
+ Some introductory **Markdown** content here.
37
+ [/hero]`,null)),s.appendChild(e("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.")),Q.appendChild(s);const S=document.createElement("div");S.appendChild(C("Interactive Components")),S.appendChild(k("Tabs, accordion, carousel, and countdown \u2014 no plugin required.")),S.appendChild(e("Tabs",`[tabs]
38
+ [tab title="First"]Content **A**[/tab]
39
+ [tab title="Second"]Content **B**[/tab]
40
+ [/tabs]`,'Add style="pills" for pill-style navigation.')),S.appendChild(e("Accordion",`[accordion]
41
+ [item title="Question 1"]Answer here.[/item]
42
+ [item title="Question 2"]Answer here.[/item]
43
+ [/accordion]`,'Add multiple="true" to allow several panels open at once.')),S.appendChild(e("Carousel",`[carousel autoplay="true" interval="5000"]
44
+ [slide title="Slide 1"]Description[/slide]
45
+ [slide image="/media/photo.jpg" title="Slide 2"]Caption[/slide]
46
+ [/carousel]`,'Omit autoplay for a manual carousel. loop="false" disables wrapping.')),S.appendChild(e("Countdown (to date)",'[countdown to="2026-12-31" format="DD:HH:mm:ss" /]',null)),S.appendChild(e("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")),O.appendChild(S);const u=document.createElement("div");u.appendChild(C("Timeline")),u.appendChild(k("Renders a Domma Progression component with event items."));const w=document.createElement("table");w.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['layout="vertical|centred|horizontal"',"[timeline] \u2014 layout orientation (default: vertical)"],['theme="minimal|corporate|modern"',"[timeline] \u2014 visual theme (default: minimal)"],['mode="timeline|roadmap"',"[timeline] \u2014 display mode (default: timeline)"],['class="..."',"[timeline] \u2014 extra CSS classes"],['id="..."',"[timeline] \u2014 element id"],['title="..."',"[event] \u2014 event heading"],['date="..."',"[event] \u2014 display date string"],['status="planned|in-progress|completed|blocked"',"[event] \u2014 progress status"],['icon="..."',"[event] \u2014 Domma icon name"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(T(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),w.appendChild(t)}),u.appendChild(w),u.appendChild(e("Vertical timeline",`[timeline layout="vertical" theme="modern"]
47
+ [event title="Kickoff" date="Jan 2025" status="completed" icon="flag"]
48
+ Project launched.
49
+ [/event]
50
+ [event title="Beta" date="Jun 2025" status="in-progress" icon="rocket"]
51
+ In active development.
52
+ [/event]
53
+ [/timeline]`,null)),O.appendChild(u);const B=document.createElement("div");B.appendChild(C("Embedding a Form"));const x=document.createElement("table");x.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['name="..."',"Form slug (required). Also accepted as slug=."],['class="..."',"Extra CSS classes on the wrapper div"],['id="..."',"id attribute on the wrapper div"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(T(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),x.appendChild(t)}),B.appendChild(x),B.appendChild(e("Basic",'[form name="contact" /]',"Forms are managed under Forms in the sidebar.")),B.appendChild(e("With wrapper styling",'[form name="newsletter" class="mt-4" id="newsletter-form" /]',null)),G.appendChild(B);const z=document.createElement("div");z.appendChild(C("Displaying a Collection"));const L=document.createElement("pre");L.style.cssText=be+"display:block;padding:.5rem .75rem;white-space:pre;overflow-x:auto;",L.textContent='[collection slug="enquiries" display="table" /]',z.appendChild(L);const A=[["slug","Required. The collection slug."],["display","table (default), cards, or list."],["fields","Comma-separated field keys to show. Defaults to all."],["limit","Maximum number of entries to display."],["sort","Field key to sort by."],["order","asc or desc (default asc)."],["title-field","Field used as the card/list title (cards + list)."],["columns","Grid columns for cards display (2\u20134, default 3)."],["search","true/false \u2014 enable search in table mode (default true)."],["sortable","true/false \u2014 enable column sorting in table mode (default true)."],["exportable","true/false \u2014 show CSV export button in table mode (default false)."],["page-size","Rows per page in table mode (default 25)."],["empty","Text shown when there are no entries."],["cta","Action slug: enables per-entry CTA buttons."],["cta-label",'CTA button label (default: "Run").'],["cta-icon","CTA button icon name."],["cta-style","CTA button variant (default: primary)."],["cta-confirm","CTA confirmation prompt text."]],H=document.createElement("table");H.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",A.forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;width:35%;",n.appendChild(T(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),H.appendChild(t)}),z.appendChild(H),z.appendChild(k("Table display uses Domma Table \u2014 search, sort, pagination, column selector, and CSV export are all built in. Cards and lists are static.")),G.appendChild(z);const M=document.createElement("div");M.appendChild(C("View \u2014 Pro")),M.appendChild(k("Renders a Collection with full filtering, pagination, and display options. Requires a Pro MongoDB collection."));const V=document.createElement("table");V.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[["slug","Required. The collection slug."],["display","table (default), cards, or list."],["limit","Maximum number of entries to display."],["fields","Comma-separated field keys to show."],["title-field","Field used as the card/list title."],["columns","Grid columns for cards display (2\u20134, default 3)."],["empty","Text shown when there are no entries."]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;width:35%;",n.appendChild(T(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),V.appendChild(t)}),M.appendChild(V),M.appendChild(e("Table display",'[view slug="products" display="table" limit="50" /]',null)),M.appendChild(e("Cards display",'[view slug="team" display="cards" columns="3" title-field="name" /]',null)),G.appendChild(M);const P=document.createElement("div");P.appendChild(C("CTA \u2014 Pro")),P.appendChild(k("Renders an action button that triggers a Pro Action against an entry. Requires the Pro Actions feature."));const K=document.createElement("table");K.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[["action","Required. The action slug."],["entry","Required. The entry id to act on."],["style","Button variant (default: primary)."],["icon","Domma icon name for the button."],["size","Button size (sm, lg)."],["confirm","Confirmation prompt text before running."],["label","Self-closing only: button label text."]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;width:35%;",n.appendChild(T(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),K.appendChild(t)}),P.appendChild(K),P.appendChild(e("Wrapping form",'[cta action="approve" entry="abc123" style="success" confirm="Approve this entry?"]Approve[/cta]',null)),P.appendChild(e("Self-closing",'[cta action="archive" entry="abc123" icon="archive" label="Archive" /]',null)),G.appendChild(P);const F=document.createElement("div");F.appendChild(C("Effects")),F.appendChild(k("Shortcodes for scroll-triggered animations, counters, typewriter text, and more. Requires the Domma Effects plugin."));const me=[['[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"]],le=document.createElement("table");le.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",me.forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(T(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),le.appendChild(t)}),F.appendChild(le),F.appendChild(e("Scribe script \u2014 looping multi-phrase typewriter",`[scribe loop="true" loop-delay="2000" delete-speed="20"]
54
+ [render]We build things that matter.[/render]
55
+ [wait]1200[/wait]
56
+ [undo all="true" /]
57
+ [render]We design for humans.[/render]
58
+ [wait]1200[/wait]
59
+ [undo all="true" /]
60
+ [/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.')),F.appendChild(k("Enable the Domma Effects plugin to activate these shortcodes on your site.")),ae.appendChild(F);const W=document.createElement("div");W.appendChild(C("Tables")),W.appendChild(k("Wrap a standard Markdown (GFM) table with Domma CSS classes and responsive horizontal scrolling."));const Y=document.createElement("table");Y.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(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(T(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),Y.appendChild(t)}),W.appendChild(Y),W.appendChild(e("Striped + bordered table",`[table striped="true" bordered="true" caption="Product Pricing"]
61
+ | Product | Price |
62
+ | ------- | ----: |
63
+ | Widget | $9.99 |
64
+ | Gadget | $14.99 |
65
+ [/table]`,"The inner content must be a valid GFM Markdown table. The shortcode adds Domma CSS classes and wraps in a responsive scroll container.")),O.appendChild(W);const oe=document.createElement("div");oe.appendChild(C("Slideover")),oe.appendChild(k("A trigger button that opens a slide-in panel with Markdown content. No plugin required."));const ie=document.createElement("table");ie.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(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(T(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),ie.appendChild(t)}),oe.appendChild(ie),oe.appendChild(e("Example",`[slideover title="More Info" trigger="Read more" size="md"]
66
+ ## Details
67
+ Markdown content here.
68
+ [/slideover]`,"Nested [card] and [grid] shortcodes work inside the slideover body.")),O.appendChild(oe);const X=document.createElement("div");X.appendChild(C("DConfig \u2014 Declarative Behaviour")),X.appendChild(k("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.")),X.appendChild(e("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.')),X.appendChild(e("Inline shortcode syntax",`[dconfig]
69
+ { "#my-btn": { "events": { "click": { "target": "#panel", "toggleClass": "hidden" } } } }
70
+ [/dconfig]`,null)),ae.appendChild(X);const D=document.createElement("div");D.appendChild(C("Tips"));const N=document.createElement("table");N.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['class="text-center"',"Centre text and inline elements on any shortcode"],['class="mx-auto"',"Centre a block element horizontally"],["[center]...[/center]","Shorthand wrapper for centred content"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(T(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),N.appendChild(t)}),D.appendChild(N),D.appendChild(k("Most shortcodes accept a class attribute for custom styling.")),ne.appendChild(D),o.setContent(se),o.open(),E.tabs(Z)}let de=!1,fe=null,ve=!1;export const pageEditorView={templateUrl:"/admin/js/templates/page-editor.html",async onMount(o){const ge=window.location.hash.match(/#\/pages\/edit(\/.*)/),T=ge?ge[1]:null,C=!!T,se=E.loader(o.get(0),{type:"dots"}),Z=[U.layouts.get().catch(()=>({})),U.settings.get().catch(()=>({}))];C&&Z.push(U.pages.get(T).catch(()=>null));const[ce,J,v]=await Promise.all(Z);se.destroy();const ne=J?.layoutOptions?.spacerSize??40;if(C&&!v){E.toast("Page not found.",{type:"error"}),R.navigate("/pages");return}o.find("#editor-title").text(C?`Edit Page \u2014 ${v.title||T}`:"New Page"),C&&o.find("#page-url-path").val(T),v&&(o.find("#field-title").val(v.title||""),o.find("#field-description").val(v.description||""),o.find("#field-status").val(v.status||"draft"),o.find("#field-sort-order").val(v.sortOrder??99),o.find("#field-show-in-nav").prop("checked",!!v.showInNav),o.find("#field-sidebar").prop("checked",!!v.sidebar),o.find("#field-show-breadcrumbs").prop("checked",v.breadcrumbs!==!1),o.find("#field-category").val(v.category||""),o.find("#field-visibility").val(v.visibility||"public"),o.find("#field-theme").val(v.theme||""),o.find("#field-seo-title").val(v.seo?.title||""),o.find("#field-seo-desc").val(v.seo?.description||""),v.dconfig&&o.find("#field-dconfig").val(JSON.stringify(v.dconfig,null,2)));const Q=await U.pages.tags().catch(()=>[]),O=o.find("#field-tags").get(0),G=O?E.pillbox(O,{data:Q,value:v?.tags||[],creatable:!0,searchable:!0,placeholder:"Add tag\u2026"}):null,ae=o.find("#field-layout").empty();if(Object.entries(ce).forEach(([e,f])=>{const l=(v?.layout||"default")===e?"selected":"";ae.append(`<option value="${e}" ${l}>${f.label||e}</option>`)}),C){const e=o.find("#view-page-btn").get(0);e.href=T,e.style.display="";const f=o.find("#live-preview-tab").get(0);f.style.display=""}if(E.tabs(o.find("#editor-meta-tabs").get(0)),C){const e=o.find("#live-preview-tab").get(0),f=o.find("#live-preview-frame").get(0);let l=!1;e.addEventListener("click",function(){l||(f.src=T,l=!0)})}const q=o.find("#markdown-editor"),ee=o.find("#markdown-preview");v&&q.val(v.content||"");const j=q.get(0),te=document.createElement("div");te.className="editor-line-numbers",j.parentElement.insertBefore(te,j);const pe=()=>{const e=j.value.split(`
71
+ `).length;te.textContent=Array.from({length:e},(f,l)=>l+1).join(`
72
+ `),te.scrollTop=j.scrollTop};pe(),j.addEventListener("input",pe),j.addEventListener("scroll",()=>{te.scrollTop=j.scrollTop});let Ce=null;const _=()=>{clearTimeout(Ce),Ce=setTimeout(async()=>{try{const{html:e}=await U.pages.preview(q.val());ee.html(e,{safe:!1}),I.scan(ee.get(0))}catch{window.marked&&ee.html(marked.parse(q.val()))}},400)},k=ye(q,o.find("#editor-toolbar"),{spacerDefault:ne});k.onLink(async e=>{const l=(await U.pages.list().catch(()=>[])).map(g=>({label:g.title||g.urlPath,value:g.urlPath})),c=document.createElement("div");c.style.cssText="padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem;";const h=document.createElement("label");h.className="form-label",h.textContent="URL";const r=document.createElement("input");r.type="text",r.className="form-input",r.placeholder="/about or https://example.com";const b=e.value.substring(e.selectionStart,e.selectionEnd);b&&b.startsWith("/")&&(r.value=b),c.appendChild(h),c.appendChild(r);const p=document.createElement("label");p.className="form-label",p.textContent="Link text";const m=document.createElement("input");m.type="text",m.className="form-input",m.placeholder="Display text",b&&!b.startsWith("/")&&(m.value=b),c.appendChild(p),c.appendChild(m);const y=document.createElement("button");y.type="button",y.className="btn btn-primary",y.textContent="Insert Link",c.appendChild(y);const s=E.modal({title:"Insert Link",size:"sm"});s.element.appendChild(c),s.open(),requestAnimationFrame(()=>{E.autocomplete(r,{data:l,minChars:1,onSelect:g=>{r.value=g.value,m.value||(m.value=g.label)}}),r.focus()}),y.addEventListener("click",()=>{const g=r.value.trim(),S=m.value.trim();if(!g)return;s.close();const u=`[${S||g}](${g})`,w=e.selectionStart,B=e.selectionEnd;e.value=e.value.substring(0,w)+u+e.value.substring(B),e.selectionStart=e.selectionEnd=w+u.length,e.dispatchEvent(new Event("input",{bubbles:!0})),e.focus()})}),k.onImage(async e=>{const l=(await U.media.list().catch(()=>[])).filter(r=>/\.(png|jpe?g|gif|webp|svg)$/i.test(r.name)),c=document.createElement("div");if(c.className="media-picker-grid",l.length)l.forEach(r=>{const b=document.createElement("div");b.className="media-picker-item",b.dataset.url=r.url;const p=document.createElement("img");p.src=r.url,p.alt=r.name;const m=document.createElement("span");m.textContent=r.name,b.appendChild(p),b.appendChild(m),c.appendChild(b)});else{const r=document.createElement("p");r.className="text-muted p-3",r.textContent="No images uploaded yet.",c.appendChild(r)}const h=E.modal({title:"Insert Image",size:"lg"});h.element.appendChild(c),$(h.element).on("click",".media-picker-item",function(){const r=$(this).data("url"),b=$(this).find("span").text();he(e,`![${b}](${r})`),h.close(),q.get(0).dispatchEvent(new Event("input",{bubbles:!0}))}),h.open()}),k.onCollection(async e=>{const f=await U.collections.list().catch(()=>[]),l=document.createElement("div");l.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:.75rem;";const c="display:block;font-size:.85em;font-weight:600;margin-bottom:.25rem;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;",h="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;",r=document.createElement("div"),b=document.createElement("label");b.style.cssText=c,b.textContent="Collection";const p=document.createElement("select");if(p.style.cssText=h,f.length)f.forEach(D=>{const N=document.createElement("option");N.value=D.slug,N.textContent=D.title||D.slug,p.appendChild(N)});else{const D=document.createElement("option");D.value="",D.textContent="No collections found",p.appendChild(D)}r.appendChild(b),r.appendChild(p),l.appendChild(r);const m=document.createElement("div"),y=document.createElement("label");y.style.cssText=c,y.textContent="Display";const s=document.createElement("select");s.style.cssText=h,["table","cards","list"].forEach(D=>{const N=document.createElement("option");N.value=D,N.textContent=D.charAt(0).toUpperCase()+D.slice(1),s.appendChild(N)}),m.appendChild(y),m.appendChild(s),l.appendChild(m);const g=document.createElement("div");g.style.display="none";const S=document.createElement("label");S.style.cssText=c,S.textContent="Columns (2\u20134)";const u=document.createElement("input");u.type="number",u.min="2",u.max="4",u.value="3",u.style.cssText=h,g.appendChild(S),g.appendChild(u),l.appendChild(g);const w=document.createElement("div"),B=document.createElement("label");B.style.cssText="display:flex;align-items:center;gap:.5rem;font-size:.9em;cursor:pointer;";const x=document.createElement("input");x.type="checkbox",x.checked=!0,B.appendChild(x),B.appendChild(document.createTextNode("Enable search")),w.appendChild(B),l.appendChild(w);const z=document.createElement("div"),L=document.createElement("label");L.style.cssText="display:flex;align-items:center;gap:.5rem;font-size:.9em;cursor:pointer;";const A=document.createElement("input");A.type="checkbox",A.checked=!0,L.appendChild(A),L.appendChild(document.createTextNode("Sortable columns")),z.appendChild(L),l.appendChild(z);const H=document.createElement("div"),M=document.createElement("label");M.style.cssText="display:flex;align-items:center;gap:.5rem;font-size:.9em;cursor:pointer;";const V=document.createElement("input");V.type="checkbox",V.checked=!1,M.appendChild(V),M.appendChild(document.createTextNode("CSV export")),H.appendChild(M),l.appendChild(H);const P=document.createElement("div"),K=document.createElement("label");K.style.cssText=c,K.textContent="Rows per page";const F=document.createElement("input");F.type="number",F.min="5",F.max="100",F.value="25",F.style.cssText=h,P.appendChild(K),P.appendChild(F),l.appendChild(P);const me=document.createElement("div"),le=document.createElement("label");le.style.cssText=c,le.textContent="Limit (optional)";const W=document.createElement("input");W.type="number",W.placeholder="All",W.style.cssText=h,me.appendChild(le),me.appendChild(W),l.appendChild(me);const Y=document.createElement("button");Y.type="button",Y.className="btn btn-primary",Y.textContent="Insert",l.appendChild(Y);const oe=[w,z,H,P],ie=()=>{const D=s.value==="table";oe.forEach(N=>{N.style.display=D?"":"none"}),g.style.display=s.value==="cards"?"":"none"};s.addEventListener("change",ie),ie();const X=E.modal({title:"Insert Collection",size:"sm"});X.element.appendChild(l),X.open(),Y.addEventListener("click",()=>{const D=p.value;if(!D)return;const N=s.value;let i=`[collection slug="${D}" display="${N}"`;N==="cards"&&(i+=` columns="${u.value}"`),N==="table"&&!x.checked&&(i+=' search="false"'),N==="table"&&!A.checked&&(i+=' sortable="false"'),N==="table"&&V.checked&&(i+=' exportable="true"'),N==="table"&&F.value!=="25"&&(i+=` page-size="${F.value}"`);const d=W.value.trim();d&&(i+=` limit="${d}"`),i+=" /]",X.close(),he(e,i),e.dispatchEvent(new Event("input",{bubbles:!0})),e.focus()})}),k.onForm(async e=>{const f=await U.forms.list().catch(()=>[]),l=document.createElement("div");l.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:.75rem;";const c="display:block;font-size:.85em;font-weight:600;margin-bottom:.25rem;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;",h="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;",r=document.createElement("div"),b=document.createElement("label");b.style.cssText=c,b.textContent="Form";const p=document.createElement("select");if(p.style.cssText=h,f.length)f.forEach(s=>{const g=document.createElement("option");g.value=s.slug,g.textContent=s.title||s.slug,p.appendChild(g)});else{const s=document.createElement("option");s.value="",s.textContent="No forms found",p.appendChild(s)}r.appendChild(b),r.appendChild(p),l.appendChild(r);const m=document.createElement("button");m.className="btn btn-primary btn-sm",m.style.cssText="align-self:flex-end;margin-top:.5rem;",m.textContent="Insert Form",l.appendChild(m);const y=E.modal({title:"Insert Form",size:"sm"});y.element.appendChild(l),y.open(),m.addEventListener("click",()=>{const s=p.value;s&&(he(e,`[form slug="${s}" /]`),y.close(),q.get(0).dispatchEvent(new Event("input",{bubbles:!0})))})}),k.onView(async e=>{const f=await U.views.list().catch(()=>[]),l=document.createElement("div");l.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:.75rem;";const c="display:block;font-size:.85em;font-weight:600;margin-bottom:.25rem;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;",h="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;",r=document.createElement("div"),b=document.createElement("label");b.style.cssText=c,b.textContent="View";const p=document.createElement("select");if(p.style.cssText=h,f.length)f.forEach(u=>{const w=document.createElement("option");w.value=u.slug,w.textContent=u.title||u.slug,p.appendChild(w)});else{const u=document.createElement("option");u.value="",u.textContent="No Views configured yet \u2014 create one under Data \u2192 Views",p.appendChild(u)}r.appendChild(b),r.appendChild(p),l.appendChild(r);const m=document.createElement("div"),y=document.createElement("label");y.style.cssText=c,y.textContent="Display";const s=document.createElement("select");s.style.cssText=h,["table","cards","list"].forEach(u=>{const w=document.createElement("option");w.value=u,w.textContent=u.charAt(0).toUpperCase()+u.slice(1),s.appendChild(w)}),m.appendChild(y),m.appendChild(s),l.appendChild(m);const g=document.createElement("button");g.type="button",g.className="btn btn-primary",g.textContent="Insert",l.appendChild(g);const S=E.modal({title:"Insert View",size:"sm"});S.element.appendChild(l),S.open(),g.addEventListener("click",()=>{const u=p.value;if(!u)return;const w=`[view slug="${u}" display="${s.value}" /]`;S.close(),he(e,w),e.dispatchEvent(new Event("input",{bubbles:!0})),e.focus()})}),k.onCta(async e=>{const f=await U.actions.list().catch(()=>[]),l=document.createElement("div");l.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:.75rem;";const c="display:block;font-size:.85em;font-weight:600;margin-bottom:.25rem;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;",h="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;";function r(x,z){const L=document.createElement("div"),A=document.createElement("label");return A.style.cssText=c,A.textContent=x,L.appendChild(A),L.appendChild(z),L}function b(x){const z=document.createElement("select");return z.style.cssText=h,x.forEach(([L,A])=>{const H=document.createElement("option");H.value=L,H.textContent=A,z.appendChild(H)}),z}function p(x,z,L){const A=document.createElement("input");return A.type=x,A.placeholder=z||"",A.value=L||"",A.style.cssText=h,A}const m=b(f.length?f.map(x=>[x.slug,x.title||x.slug]):[["","No Actions configured yet \u2014 create one under Data \u2192 Actions"]]);l.appendChild(r("Action",m));const y=p("text","Button label","Run");if(l.appendChild(r("Label",y)),m.addEventListener("change",()=>{const x=f.find(z=>z.slug===m.value);x?.trigger?.label&&(y.value=x.trigger.label)}),f.length){const x=f[0];x?.trigger?.label&&(y.value=x.trigger.label)}const s=p("text","Paste entry UUID\u2026","");l.appendChild(r("Entry ID",s));const g=b([["primary","Primary"],["secondary","Secondary"],["ghost","Ghost"],["danger","Danger"]]);l.appendChild(r("Style",g));const S=p("text","e.g. check, zap, send (optional)","");l.appendChild(r("Icon",S));const u=p("text","Confirmation message (optional)","");l.appendChild(r("Confirm prompt",u));const w=document.createElement("button");w.type="button",w.className="btn btn-primary",w.textContent="Insert",l.appendChild(w);const B=E.modal({title:"Insert CTA Button",size:"sm"});B.element.appendChild(l),B.open(),w.addEventListener("click",()=>{const x=m.value;if(!x)return;const z=s.value.trim(),L=(y.value.trim()||"Run").replace(/\[\/cta\]/gi,""),A=g.value,H=S.value.trim().replace(/"/g,""),M=u.value.trim().replace(/"/g,""),V=z.replace(/"/g,"");let P=`action="${x}" style="${A}"`;V&&(P+=` entry="${V}"`),H&&(P+=` icon="${H}"`),M&&(P+=` confirm="${M}"`);const K=`[cta ${P}]${L}[/cta]`;B.close(),he(e,K),e.dispatchEvent(new Event("input",{bubbles:!0})),e.focus()})}),k.onHelp(()=>{xe()}),o.find(".editor-view-btn").on("click",function(){const e=$(this).data("mode");o.find(".editor-view-btn").removeClass("active"),$(this).addClass("active"),o.find("#editor-body").removeClass("editor-mode-split editor-mode-write editor-mode-preview").addClass(`editor-mode-${e}`)}),o.find("#fullscreen-btn").on("click",function(){o.find(".editor-card").toggleClass("editor-fullscreen")}),de=!1,fe&&window.removeEventListener("beforeunload",fe),fe=e=>{de&&e.preventDefault()},window.addEventListener("beforeunload",fe),ve||(ve=!0,R.use((e,f,l)=>{const c=window.location.hash.startsWith("#/pages/edit")||window.location.hash==="#/pages/new";if(!de||!c)return l();E.confirm("You have unsaved changes. Leave this page?").then(h=>{h&&(de=!1,l())})})),q.on("input",()=>{de=!0,_()}),_(),o.find("#save-btn").on("click",async()=>{const e=o.find("#page-url-path").val().trim();if(!e){E.toast("URL path is required.",{type:"warning"});return}const f=o.find("#field-dconfig").val().trim();let l=null;if(f)try{l=JSON.parse(f)}catch{E.toast("DConfig JSON is invalid. Please check the format before saving.",{type:"warning"});return}const c={title:o.find("#field-title").val().trim()||"Untitled",description:o.find("#field-description").val().trim(),layout:o.find("#field-layout").val(),status:o.find("#field-status").val(),sortOrder:parseInt(o.find("#field-sort-order").val(),10)||99,showInNav:o.find("#field-show-in-nav").is(":checked"),sidebar:o.find("#field-sidebar").is(":checked"),...o.find("#field-show-breadcrumbs").is(":checked")?{}:{breadcrumbs:!1},tags:G?G.getValue():[],category:o.find("#field-category").val().trim()||null,visibility:o.find("#field-visibility").val()||"public",seo:{title:o.find("#field-seo-title").val().trim(),description:o.find("#field-seo-desc").val().trim()},dconfig:l,...o.find("#field-theme").val()?{theme:o.find("#field-theme").val()}:{}};try{if(o.find("#save-btn").prop("disabled",!0).text("Saving\u2026"),C){const h={frontmatter:c,body:q.val()};e!==T&&(h.newUrlPath=e),await U.pages.update(T,h),E.toast("Page saved successfully.",{type:"success"}),de=!1,e!==T&&R.navigate(`/pages/edit${e}`)}else await U.pages.create({urlPath:e,frontmatter:c,body:q.val()}),E.toast("Page saved successfully.",{type:"success"}),de=!1,R.navigate("/pages")}catch(h){E.toast(`Save failed: ${h.message||"Unknown error"}`,{type:"error"})}finally{o.find("#save-btn").prop("disabled",!1).text("Save")}}),o.find("#cancel-btn").on("click",()=>R.navigate("/pages")),Domma.icons.scan()}};