domma-cms 0.25.15 → 0.30.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 (67) hide show
  1. package/admin/js/app.js +2 -2
  2. package/admin/js/lib/sidebar-renderer.js +4 -4
  3. package/admin/js/templates/api-reference.html +68 -1233
  4. package/admin/js/templates/docs/api-actions.html +146 -0
  5. package/admin/js/templates/docs/api-authentication.html +202 -0
  6. package/admin/js/templates/docs/api-collections.html +347 -0
  7. package/admin/js/templates/docs/api-layouts.html +123 -0
  8. package/admin/js/templates/docs/api-media.html +158 -0
  9. package/admin/js/templates/docs/api-navigation.html +99 -0
  10. package/admin/js/templates/docs/api-pages.html +214 -0
  11. package/admin/js/templates/docs/api-plugins.html +136 -0
  12. package/admin/js/templates/docs/api-settings.html +148 -0
  13. package/admin/js/templates/docs/api-users.html +189 -0
  14. package/admin/js/templates/docs/api-views.html +142 -0
  15. package/admin/js/templates/docs/components-howto.html +175 -0
  16. package/admin/js/templates/docs/components-reference.html +259 -0
  17. package/admin/js/templates/docs/components-rules.html +167 -0
  18. package/admin/js/templates/docs/components-walkthrough.html +167 -0
  19. package/admin/js/templates/docs/tutorial-crud.html +344 -0
  20. package/admin/js/templates/docs/tutorial-forms.html +93 -0
  21. package/admin/js/templates/docs/tutorial-plugin.html +234 -0
  22. package/admin/js/templates/docs/usage-actions.html +122 -0
  23. package/admin/js/templates/docs/usage-cta-shortcode.html +124 -0
  24. package/admin/js/templates/docs/usage-dconfig.html +148 -0
  25. package/admin/js/templates/docs/usage-media.html +25 -0
  26. package/admin/js/templates/docs/usage-navigation.html +31 -0
  27. package/admin/js/templates/docs/usage-pages.html +68 -0
  28. package/admin/js/templates/docs/usage-plugins.html +36 -0
  29. package/admin/js/templates/docs/usage-shortcodes.html +810 -0
  30. package/admin/js/templates/docs/usage-site-settings.html +52 -0
  31. package/admin/js/templates/docs/usage-users-roles.html +84 -0
  32. package/admin/js/templates/docs/usage-views.html +105 -0
  33. package/admin/js/templates/documentation.html +60 -1522
  34. package/admin/js/templates/effects.html +752 -752
  35. package/admin/js/templates/forms.html +17 -17
  36. package/admin/js/templates/my-profile.html +17 -17
  37. package/admin/js/templates/project-settings.html +7 -0
  38. package/admin/js/templates/role-editor.html +70 -70
  39. package/admin/js/templates/roles.html +10 -10
  40. package/admin/js/templates/tutorials.html +46 -659
  41. package/admin/js/views/api-reference.js +1 -1
  42. package/admin/js/views/doc-pages.js +1 -0
  43. package/admin/js/views/documentation.js +1 -1
  44. package/admin/js/views/index.js +1 -1
  45. package/admin/js/views/project-settings.js +1 -1
  46. package/admin/js/views/projects.js +3 -7
  47. package/admin/js/views/tutorials.js +1 -1
  48. package/bin/lib/config-merge.js +44 -44
  49. package/config/connections.json.bak +9 -0
  50. package/config/menus/admin-sidebar.json +76 -6
  51. package/package.json +1 -1
  52. package/public/js/site.js +1 -1
  53. package/scripts/migrate-docs.js +280 -0
  54. package/server/middleware/auth.js +253 -253
  55. package/server/routes/api/auth.js +309 -309
  56. package/server/routes/api/collections.js +10 -1
  57. package/server/routes/api/navigation.js +42 -42
  58. package/server/routes/api/projects.js +9 -1
  59. package/server/routes/api/settings.js +141 -141
  60. package/server/routes/public.js +9 -0
  61. package/server/server.js +6 -2
  62. package/server/services/email.js +167 -167
  63. package/server/services/presetCollections.js +1 -0
  64. package/server/services/projects.js +72 -3
  65. package/server/services/sidebar-migration.js +145 -3
  66. package/server/services/userProfiles.js +199 -199
  67. package/server/services/users.js +302 -302
@@ -0,0 +1,234 @@
1
+ <div class="view-header">
2
+ <h1><span data-icon="document"></span> Writing a Plugin</h1>
3
+ <a href="#/tutorials" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> All tutorials</a>
4
+ </div>
5
+
6
+ <div class="row">
7
+ <div class="col-12">
8
+ <div class="docs-body">
9
+
10
+ <p>Plugins live in the <code>plugins/</code> directory. Each plugin is a self-contained folder with
11
+ three required files and optional <code>admin/</code> and <code>public/</code> subdirectories.</p>
12
+
13
+ <h3>Directory structure</h3>
14
+ <pre class="code-block"><code>plugins/
15
+ my-plugin/
16
+ plugin.json ← manifest (required)
17
+ plugin.js ← Fastify plugin (required)
18
+ config.js ← settings defaults (required)
19
+ admin/
20
+ views/
21
+ my-view.js ← admin SPA view (optional)
22
+ templates/
23
+ my-view.html ← view template (optional)
24
+ public/
25
+ inject-head.html ← injected into &lt;head&gt; on every page (optional)
26
+ inject-body.html ← injected before &lt;/body&gt; on every page (optional)
27
+ data/ ← plugin data store (optional, not publicly served)</code></pre>
28
+
29
+ <hr>
30
+
31
+ <h3>1. plugin.json — the manifest</h3>
32
+ <p>All fields below are <strong>required</strong>. Missing any will cause the plugin to be skipped on
33
+ startup with a warning in the server log.</p>
34
+ <pre class="code-block"><code>{
35
+ "name": "my-plugin",
36
+ "displayName": "My Plugin",
37
+ "version": "1.0.0",
38
+ "description": "A short description shown on the Plugins page.",
39
+ "author": "Your Name",
40
+ "date": "2026-03-01",
41
+ "icon": "star"
42
+ }</code></pre>
43
+
44
+ <p>Optional fields:</p>
45
+ <table class="table table-sm">
46
+ <thead>
47
+ <tr>
48
+ <th>Field</th>
49
+ <th>Type</th>
50
+ <th>Description</th>
51
+ </tr>
52
+ </thead>
53
+ <tbody>
54
+ <tr>
55
+ <td><code>inject.head</code></td>
56
+ <td>string</td>
57
+ <td>Path (relative to plugin root) to an HTML snippet injected into <code>&lt;head&gt;</code>.</td>
58
+ </tr>
59
+ <tr>
60
+ <td><code>inject.bodyEnd</code></td>
61
+ <td>string</td>
62
+ <td>Path to an HTML snippet injected before <code>&lt;/body&gt;</code>.</td>
63
+ </tr>
64
+ <tr>
65
+ <td><code>admin.sidebar</code></td>
66
+ <td>array</td>
67
+ <td>Sidebar items to add to the admin panel.</td>
68
+ </tr>
69
+ <tr>
70
+ <td><code>admin.routes</code></td>
71
+ <td>array</td>
72
+ <td>SPA routes to register in the admin router.</td>
73
+ </tr>
74
+ <tr>
75
+ <td><code>admin.views</code></td>
76
+ <td>object</td>
77
+ <td>View modules to dynamically import into the admin SPA.</td>
78
+ </tr>
79
+ </tbody>
80
+ </table>
81
+
82
+ <hr>
83
+
84
+ <h3>2. plugin.js — the Fastify plugin</h3>
85
+ <p>This is the server-side entry point. It must export a default <strong>async function</strong> that
86
+ Fastify will call with <code>(fastify, options)</code>.</p>
87
+ <p>The CMS injects auth middleware through <code>options.auth</code> — always destructure from there
88
+ rather than importing directly.</p>
89
+
90
+ <pre class="code-block"><code>import { getPluginSettings, savePluginState } from '../../server/services/plugins.js';
91
+
92
+ export default async function myPlugin(fastify, options) {
93
+ const { authenticate, requireAdmin } = options.auth;
94
+
95
+ // Public endpoint — no auth needed
96
+ fastify.get('/hello', async () => {
97
+ return { message: 'Hello from my plugin!' };
98
+ });
99
+
100
+ // Admin-only endpoint
101
+ fastify.get('/settings', { preHandler: [authenticate, requireAdmin] }, async () => {
102
+ return getPluginSettings('my-plugin');
103
+ });
104
+
105
+ fastify.put('/settings', { preHandler: [authenticate, requireAdmin] }, async (request) => {
106
+ savePluginState('my-plugin', { settings: request.body });
107
+ return { ok: true };
108
+ });
109
+ }</code></pre>
110
+
111
+ <p>Routes are registered under the prefix <code>/api/plugins/{name}</code> automatically. You do not
112
+ set the prefix yourself — it is always locked to your plugin's directory name.</p>
113
+
114
+ <hr>
115
+
116
+ <h3>3. config.js — settings defaults</h3>
117
+ <p>Export a plain object of default settings. These are merged with any user overrides stored in
118
+ <code>config/plugins.json</code> when <code>getPluginSettings()</code> is called.</p>
119
+
120
+ <pre class="code-block"><code>export default {
121
+ greeting: 'Hello, world!',
122
+ enableFeature: true,
123
+ maxItems: 10
124
+ };</code></pre>
125
+
126
+ <p><code>config.js</code> is only loaded for <strong>enabled</strong> plugins. Side-effect code here
127
+ will not run for disabled plugins.</p>
128
+
129
+ <hr>
130
+
131
+ <h3>4. Admin views (optional)</h3>
132
+ <p>To add a page to the admin panel, declare the route and view in <code>plugin.json</code>:</p>
133
+
134
+ <pre class="code-block"><code>"admin": {
135
+ "sidebar": [
136
+ {
137
+ "id": "my-plugin",
138
+ "text": "My Plugin",
139
+ "icon": "star",
140
+ "url": "#/plugins/my-plugin",
141
+ "section": "#/plugins/my-plugin"
142
+ }
143
+ ],
144
+ "routes": [
145
+ {
146
+ "path": "/plugins/my-plugin",
147
+ "view": "plugin-my-plugin",
148
+ "title": "My Plugin - Domma CMS"
149
+ }
150
+ ],
151
+ "views": {
152
+ "plugin-my-plugin": {
153
+ "entry": "my-plugin/admin/views/my-view.js",
154
+ "exportName": "myPluginView"
155
+ }
156
+ }
157
+ }</code></pre>
158
+
159
+ <p>The view file follows the standard Domma view pattern — a <code>templateUrl</code> and an
160
+ <code>onMount($container)</code> function:</p>
161
+
162
+ <pre class="code-block"><code>// admin/views/my-view.js
163
+ export const myPluginView = {
164
+ templateUrl: '/plugins/my-plugin/admin/templates/my-view.html',
165
+
166
+ async onMount($container) {
167
+ const res = await fetch('/api/plugins/my-plugin/settings', {
168
+ headers: { 'Authorization': 'Bearer ' + (S.get('auth_token') || '') }
169
+ });
170
+ const settings = await res.json();
171
+
172
+ $container.find('#greeting').text(settings.greeting);
173
+ Domma.icons.scan();
174
+ }
175
+ };</code></pre>
176
+
177
+ <p>The template is a plain HTML fragment (no <code>&lt;html&gt;</code> wrapper). Use the same card and
178
+ form patterns as the rest of the admin panel:</p>
179
+
180
+ <pre class="code-block"><code>&lt;!-- admin/templates/my-view.html --&gt;
181
+ &lt;div class="view-header"&gt;
182
+ &lt;h1&gt;&lt;span data-icon="star"&gt;&lt;/span&gt; My Plugin&lt;/h1&gt;
183
+ &lt;/div&gt;
184
+
185
+ &lt;div class="card"&gt;
186
+ &lt;div class="card-body"&gt;
187
+ &lt;p id="greeting"&gt;Loading…&lt;/p&gt;
188
+ &lt;/div&gt;
189
+ &lt;/div&gt;</code></pre>
190
+
191
+ <hr>
192
+
193
+ <h3>5. Injection snippets (optional)</h3>
194
+ <p>HTML snippets declared in <code>inject.head</code> and <code>inject.bodyEnd</code> are read from
195
+ the plugin's <code>public/</code> directory and inserted into every public page. Use this for
196
+ analytics scripts, stylesheets, or widgets.</p>
197
+
198
+ <pre class="code-block"><code>&lt;!-- public/inject-body.html --&gt;
199
+ &lt;script&gt;
200
+ (function () {
201
+ // This runs on every public page
202
+ fetch('/api/plugins/my-plugin/hello')
203
+ .then(r => r.json())
204
+ .then(d => console.log(d.message));
205
+ })();
206
+ &lt;/script&gt;</code></pre>
207
+
208
+ <p>Snippet paths are validated — they must stay within the plugin's own directory. Paths containing
209
+ <code>..</code> are blocked.</p>
210
+
211
+ <hr>
212
+
213
+ <h3>6. Registering and testing</h3>
214
+ <ol>
215
+ <li>Create the <code>plugins/my-plugin/</code> directory with all three required files.</li>
216
+ <li>Restart the server — you should see <code>[plugins] Loaded N plugins: …, my-plugin</code> in
217
+ the log.
218
+ </li>
219
+ <li>Go to the <a href="#/plugins">Plugins page</a> and enable your plugin.</li>
220
+ <li>Restart the server again to register the routes.</li>
221
+ <li>Verify your endpoint: <code>GET /api/plugins/my-plugin/hello</code></li>
222
+ </ol>
223
+
224
+ <p class="text-muted" style="font-size:.9rem">Tip: use <code>npm run dev</code> during development —
225
+ the server restarts automatically on file changes.</p>
226
+
227
+ <hr>
228
+ <p class="text-muted" style="font-size:.9rem;">
229
+ Next: <a href="#/tutorials/forms">Form Follow-Up →</a>
230
+ </p>
231
+
232
+ </div>
233
+ </div>
234
+ </div>
@@ -0,0 +1,122 @@
1
+ <div class="view-header">
2
+ <h1><span data-icon="book"></span> Actions</h1>
3
+ <a href="#/documentation" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> All usage topics</a>
4
+ </div>
5
+
6
+ <div class="row">
7
+ <div class="col-12">
8
+ <div class="docs-body">
9
+
10
+ <p>Actions are admin-designed sequential workflow operations triggered against individual collection
11
+ entries. When an action is assigned to a collection, a trigger button appears per row in the
12
+ entry list. Actions require a MongoDB connection (pro mode).</p>
13
+
14
+ <h3>Creating an Action</h3>
15
+ <ol>
16
+ <li>Navigate to <strong>Data → Actions</strong> and click <strong>New Action</strong>.</li>
17
+ <li><strong>General tab</strong> — enter a title and select the target collection.</li>
18
+ <li><strong>Trigger tab</strong> — configure the button label, icon, and optional confirmation
19
+ message.
20
+ </li>
21
+ <li><strong>Steps tab</strong> — add steps in order. Each step runs sequentially; if one fails the
22
+ action stops.
23
+ </li>
24
+ <li><strong>Access tab</strong> — select which roles can trigger this action.</li>
25
+ <li>Click <strong>Save Action</strong>. The button will appear on the collection's entry list.
26
+ </li>
27
+ </ol>
28
+
29
+ <h3>Phase 1 Step Types</h3>
30
+ <table class="table table-sm">
31
+ <thead>
32
+ <tr>
33
+ <th>Step</th>
34
+ <th>What it does</th>
35
+ <th>Config fields</th>
36
+ </tr>
37
+ </thead>
38
+ <tbody>
39
+ <tr>
40
+ <td><code>updateField</code></td>
41
+ <td>Set a field on the entry to a new value</td>
42
+ <td><code>field</code>, <code>value</code></td>
43
+ </tr>
44
+ <tr>
45
+ <td><code>deleteEntry</code></td>
46
+ <td>Permanently delete the entry</td>
47
+ <td>None</td>
48
+ </tr>
49
+ <tr>
50
+ <td><code>moveToCollection</code></td>
51
+ <td>Create the entry in a target collection then delete it from the source</td>
52
+ <td><code>targetCollection</code></td>
53
+ </tr>
54
+ <tr>
55
+ <td><code>webhook</code></td>
56
+ <td>HTTP request to an external URL</td>
57
+ <td><code>url</code>, <code>method</code>, <code>body</code> (JSON)</td>
58
+ </tr>
59
+ <tr>
60
+ <td><code>email</code></td>
61
+ <td>Send an email via the configured SMTP transport</td>
62
+ <td><code>to</code>, <code>subject</code>, <code>template</code></td>
63
+ </tr>
64
+ </tbody>
65
+ </table>
66
+
67
+ <h3>Template Variables</h3>
68
+ <p>Step config fields support <code>{{variable}}</code> interpolation:</p>
69
+ <table class="table table-sm">
70
+ <thead>
71
+ <tr>
72
+ <th>Variable</th>
73
+ <th>Resolves to</th>
74
+ </tr>
75
+ </thead>
76
+ <tbody>
77
+ <tr>
78
+ <td><code>{{entry.data.fieldName}}</code></td>
79
+ <td>A field value from the current entry</td>
80
+ </tr>
81
+ <tr>
82
+ <td><code>{{entry.id}}</code></td>
83
+ <td>The entry's ID</td>
84
+ </tr>
85
+ <tr>
86
+ <td><code>{{now}}</code></td>
87
+ <td>Current timestamp (ISO 8601)</td>
88
+ </tr>
89
+ <tr>
90
+ <td><code>{{user.name}}</code></td>
91
+ <td>Name of the user who triggered the action</td>
92
+ </tr>
93
+ <tr>
94
+ <td><code>{{user.email}}</code></td>
95
+ <td>Email of the triggering user</td>
96
+ </tr>
97
+ <tr>
98
+ <td><code>{{env.CMS_PUBLIC_*}}</code></td>
99
+ <td>Environment variables prefixed <code>CMS_PUBLIC_</code> only</td>
100
+ </tr>
101
+ </tbody>
102
+ </table>
103
+ <p><strong>Example</strong> — approve an application and notify by email:</p>
104
+ <pre class="code-block"><code>Step 1: updateField field=status value=approved
105
+ Step 2: updateField field=approvedAt value={{now}}
106
+ Step 3: email to={{entry.data.email}}
107
+ subject=Your application has been approved
108
+ template=Congratulations {{entry.data.name}}, your application is approved.</code></pre>
109
+
110
+ <h3>Partial Execution</h3>
111
+ <p>Actions are <strong>not transactional</strong>. If a step fails, the action stops and returns the
112
+ number of steps completed so far (<code>stepsCompleted</code>). Steps that already ran are
113
+ <strong>not rolled back</strong>. Design step order with this in mind — put irreversible steps
114
+ (delete, email) last.</p>
115
+
116
+ <h3>After a deleteEntry step</h3>
117
+ <p>If an action contains a <code>deleteEntry</code> step, subsequent steps will fail because the entry
118
+ no longer exists. Place <code>deleteEntry</code> as the last step.</p>
119
+
120
+ </div>
121
+ </div>
122
+ </div>
@@ -0,0 +1,124 @@
1
+ <div class="view-header">
2
+ <h1><span data-icon="book"></span> CTA Shortcode</h1>
3
+ <a href="#/documentation" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> All usage topics</a>
4
+ </div>
5
+
6
+ <div class="row">
7
+ <div class="col-12">
8
+ <div class="docs-body">
9
+
10
+ <p>The <code>[cta]</code> shortcode places an action-trigger button in any public page. Clicking it
11
+ calls
12
+ <code>POST /api/actions/:slug/public</code> with the entry ID, using the logged-in user's JWT.
13
+ If the user is not logged in, a warning toast is shown instead.</p>
14
+
15
+ <h3>Syntax &amp; Attributes</h3>
16
+ <p><strong>Wrapping form:</strong></p>
17
+ <pre class="code-block"><code>[cta action="slug" entry="entry-id" icon="check" confirm="Are you sure?"]Button label[/cta]</code></pre>
18
+ <p><strong>Self-closing form:</strong></p>
19
+ <pre class="code-block"><code>[cta action="slug" entry="entry-id" label="Button label" /]</code></pre>
20
+ <table class="table table-sm" style="margin-top:1rem;">
21
+ <thead>
22
+ <tr>
23
+ <th>Attribute</th>
24
+ <th>Required</th>
25
+ <th>Default</th>
26
+ <th>Description</th>
27
+ </tr>
28
+ </thead>
29
+ <tbody>
30
+ <tr>
31
+ <td><code>action</code></td>
32
+ <td>Yes</td>
33
+ <td>—</td>
34
+ <td>Action slug</td>
35
+ </tr>
36
+ <tr>
37
+ <td><code>entry</code></td>
38
+ <td>Yes</td>
39
+ <td>—</td>
40
+ <td>Entry UUID to act on</td>
41
+ </tr>
42
+ <tr>
43
+ <td><code>label</code></td>
44
+ <td>No</td>
45
+ <td><code>"Run"</code></td>
46
+ <td>Button text (self-closing only)</td>
47
+ </tr>
48
+ <tr>
49
+ <td><code>style</code></td>
50
+ <td>No</td>
51
+ <td><code>"primary"</code></td>
52
+ <td>primary · secondary · ghost · danger</td>
53
+ </tr>
54
+ <tr>
55
+ <td><code>icon</code></td>
56
+ <td>No</td>
57
+ <td>—</td>
58
+ <td>Domma icon name</td>
59
+ </tr>
60
+ <tr>
61
+ <td><code>size</code></td>
62
+ <td>No</td>
63
+ <td>—</td>
64
+ <td>sm · md · lg</td>
65
+ </tr>
66
+ <tr>
67
+ <td><code>confirm</code></td>
68
+ <td>No</td>
69
+ <td>—</td>
70
+ <td>Confirmation prompt before executing</td>
71
+ </tr>
72
+ </tbody>
73
+ </table>
74
+
75
+ <h3>Collection Integration</h3>
76
+ <p>Add per-entry buttons to any <code>[collection]</code> shortcode:</p>
77
+ <pre class="code-block"><code>[collection slug="applications" display="cards" title-field="name"
78
+ cta="approve-application"
79
+ cta-label="Approve"
80
+ cta-icon="check"
81
+ cta-style="primary"
82
+ cta-confirm="Approve this application?" /]</code></pre>
83
+ <table class="table table-sm" style="margin-top:1rem;">
84
+ <thead>
85
+ <tr>
86
+ <th>Attribute</th>
87
+ <th>Default</th>
88
+ <th>Description</th>
89
+ </tr>
90
+ </thead>
91
+ <tbody>
92
+ <tr>
93
+ <td><code>cta</code></td>
94
+ <td>—</td>
95
+ <td>Action slug — enables per-entry buttons</td>
96
+ </tr>
97
+ <tr>
98
+ <td><code>cta-label</code></td>
99
+ <td><code>"Run"</code></td>
100
+ <td>Button label</td>
101
+ </tr>
102
+ <tr>
103
+ <td><code>cta-icon</code></td>
104
+ <td>—</td>
105
+ <td>Domma icon name</td>
106
+ </tr>
107
+ <tr>
108
+ <td><code>cta-style</code></td>
109
+ <td><code>"primary"</code></td>
110
+ <td>Button variant</td>
111
+ </tr>
112
+ <tr>
113
+ <td><code>cta-confirm</code></td>
114
+ <td>—</td>
115
+ <td>Confirmation prompt</td>
116
+ </tr>
117
+ </tbody>
118
+ </table>
119
+ <p>All three display modes support CTA buttons: <strong>cards</strong> (card footer),
120
+ <strong>list</strong> (inline), <strong>table</strong> (dedicated column).</p>
121
+
122
+ </div>
123
+ </div>
124
+ </div>
@@ -0,0 +1,148 @@
1
+ <div class="view-header">
2
+ <h1><span data-icon="book"></span> DConfig</h1>
3
+ <a href="#/documentation" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> All usage topics</a>
4
+ </div>
5
+
6
+ <div class="row">
7
+ <div class="col-12">
8
+ <div class="docs-body">
9
+
10
+ <p>DConfig lets you wire up declarative behaviour on any public page — no JavaScript required. Define
11
+ event
12
+ handlers and class toggles in JSON; the page runtime applies them automatically on load.</p>
13
+
14
+ <p>DConfig can be set in two ways, and both are merged at runtime (inline shortcode wins on selector
15
+ conflict):</p>
16
+ <ul>
17
+ <li><strong>Page editor</strong> — the <em>DConfig</em> collapsible section accepts a JSON blob
18
+ stored in page
19
+ frontmatter and injected as <code>window.__CMS_DCONFIG__</code>.
20
+ </li>
21
+ <li><strong>Inline shortcode</strong> — place one or more <code>[dconfig]…[/dconfig]</code> blocks
22
+ directly in
23
+ the page body Markdown.
24
+ </li>
25
+ </ul>
26
+
27
+ <h3>Schema</h3>
28
+ <pre class="code-block"><code>{
29
+ "#selector": {
30
+ "events": {
31
+ "eventName": {
32
+ "target": "#target-selector",
33
+ "action": "value"
34
+ }
35
+ }
36
+ }
37
+ }</code></pre>
38
+
39
+ <p>Multiple selectors and multiple event types can appear in a single config block.</p>
40
+
41
+ <h3>Supported events</h3>
42
+ <table class="table table-sm">
43
+ <thead>
44
+ <tr>
45
+ <th>Event</th>
46
+ <th>Description</th>
47
+ </tr>
48
+ </thead>
49
+ <tbody>
50
+ <tr>
51
+ <td><code>click</code></td>
52
+ <td>Fires when the selector element is clicked.</td>
53
+ </tr>
54
+ </tbody>
55
+ </table>
56
+
57
+ <h3>Supported actions</h3>
58
+ <table class="table table-sm">
59
+ <thead>
60
+ <tr>
61
+ <th>Action key</th>
62
+ <th>Value type</th>
63
+ <th>Description</th>
64
+ </tr>
65
+ </thead>
66
+ <tbody>
67
+ <tr>
68
+ <td><code>toggleClass</code></td>
69
+ <td><code>string</code></td>
70
+ <td>Adds the class if absent, removes it if present, on the <code>target</code> element.
71
+ Typical use:
72
+ show/hide content with a <code>hidden</code> class.
73
+ </td>
74
+ </tr>
75
+ </tbody>
76
+ </table>
77
+
78
+ <p><em>Additional actions (addClass, removeClass, setAttribute, scroll-to) are planned for future
79
+ releases.</em></p>
80
+
81
+ <h3>Targeting shortcode elements with <code>id</code></h3>
82
+ <p>All shortcodes support an <code>id</code> attribute, which is written directly onto the generated
83
+ element.
84
+ Use this instead of raw HTML when you want DConfig to target a card, column, or grid:</p>
85
+ <pre class="code-block"><code>[card id="my-panel" title="Hidden Details"]
86
+ Content goes here.
87
+ [/card]
88
+
89
+ [dconfig]
90
+ {
91
+ "#my-btn": {
92
+ "events": {
93
+ "click": { "target": "#my-panel", "toggleClass": "hidden" }
94
+ }
95
+ }
96
+ }
97
+ [/dconfig]</code></pre>
98
+ <p>Supported on: <code>[card]</code>, <code>[col]</code>, <code>[row]</code>, <code>[grid]</code>.</p>
99
+
100
+ <h3>Using the <code>[dconfig]</code> shortcode</h3>
101
+ <pre class="code-block"><code>[dconfig]
102
+ {
103
+ "#my-btn": {
104
+ "events": {
105
+ "click": { "target": "#my-panel", "toggleClass": "hidden" }
106
+ }
107
+ }
108
+ }
109
+ [/dconfig]
110
+
111
+ &lt;button id="my-btn" class="btn btn-primary"&gt;Toggle&lt;/button&gt;
112
+
113
+ &lt;div id="my-panel" class="card hidden"&gt;
114
+ &lt;div class="card-body"&gt;Hidden content.&lt;/div&gt;
115
+ &lt;/div&gt;</code></pre>
116
+
117
+ <h3>Multiple selectors</h3>
118
+ <p>Any number of selectors can appear in one block. Selectors are matched with
119
+ <code>document.querySelector</code> — use IDs, classes, or attribute selectors.</p>
120
+ <pre class="code-block"><code>[dconfig]
121
+ {
122
+ "#show-btn": {
123
+ "events": {
124
+ "click": { "target": "#panel", "toggleClass": "hidden" }
125
+ }
126
+ },
127
+ "#hide-btn": {
128
+ "events": {
129
+ "click": { "target": "#panel", "toggleClass": "hidden" }
130
+ }
131
+ }
132
+ }
133
+ [/dconfig]</code></pre>
134
+
135
+ <h3>Using the page editor DConfig section</h3>
136
+ <p>The same JSON (without the shortcode tags) can be pasted directly into the <em>DConfig</em> card in
137
+ the page
138
+ editor. This is useful when the config should apply to the whole page without appearing in the
139
+ body content.
140
+ Inline <code>[dconfig]</code> shortcodes are applied last and will win on any shared selector key.
141
+ </p>
142
+
143
+ <p>See live demos on the <a href="/resources/interactive" target="_blank">Interactive Resources
144
+ page</a>.</p>
145
+
146
+ </div>
147
+ </div>
148
+ </div>
@@ -0,0 +1,25 @@
1
+ <div class="view-header">
2
+ <h1><span data-icon="book"></span> Media</h1>
3
+ <a href="#/documentation" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> All usage topics</a>
4
+ </div>
5
+
6
+ <div class="row">
7
+ <div class="col-12">
8
+ <div class="docs-body">
9
+
10
+ <p>Media files are stored in <code>content/media/</code> and served publicly at <code>/media/{filename}</code>.
11
+ </p>
12
+
13
+ <h3>Using media in pages</h3>
14
+ <p>Reference uploaded files by their public URL in Markdown:</p>
15
+ <pre class="code-block"><code>![Alt text](/media/my-image.jpg)
16
+
17
+ &lt;img src="/media/my-image.jpg" alt="Alt text"&gt;</code></pre>
18
+
19
+ <h3>Upload limits</h3>
20
+ <p>The maximum file size is configured in <code>config/server.json</code> under <code>uploads.maxFileSize</code>
21
+ (bytes). Default is 10 MB.</p>
22
+
23
+ </div>
24
+ </div>
25
+ </div>