domma-cms 0.92.1 → 0.94.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 (177) hide show
  1. package/CLAUDE.md +5 -3
  2. package/admin/css/admin.css +1 -1
  3. package/admin/js/app.js +2 -2
  4. package/admin/js/lib/action-editor-arrange.js +1 -1
  5. package/admin/js/lib/api-tokens-arrange.js +2 -2
  6. package/admin/js/lib/block-editor-arrange.js +1 -1
  7. package/admin/js/lib/blocks-arrange.js +1 -1
  8. package/admin/js/lib/collection-entries-arrange.js +1 -1
  9. package/admin/js/lib/components-arrange.js +1 -1
  10. package/admin/js/lib/dashboard-arrange.js +1 -1
  11. package/admin/js/lib/dates.js +1 -0
  12. package/admin/js/lib/forms-arrange.js +1 -1
  13. package/admin/js/lib/media-arrange.js +1 -1
  14. package/admin/js/lib/notifications-arrange.js +1 -1
  15. package/admin/js/lib/pages-arrange.js +1 -1
  16. package/admin/js/lib/related.js +1 -1
  17. package/admin/js/lib/timeline-builder.js +2 -2
  18. package/admin/js/templates/action-editor.html +6 -5
  19. package/admin/js/templates/actions-list.html +1 -1
  20. package/admin/js/templates/contacts.html +1 -1
  21. package/admin/js/templates/docs/api-actions.html +86 -60
  22. package/admin/js/templates/docs/api-authentication.html +159 -123
  23. package/admin/js/templates/docs/api-builder.html +197 -0
  24. package/admin/js/templates/docs/api-collections.html +199 -259
  25. package/admin/js/templates/docs/api-external.html +225 -0
  26. package/admin/js/templates/docs/api-forms.html +268 -0
  27. package/admin/js/templates/docs/api-layouts.html +70 -45
  28. package/admin/js/templates/docs/api-media.html +57 -80
  29. package/admin/js/templates/docs/api-navigation.html +66 -22
  30. package/admin/js/templates/docs/api-pages.html +109 -129
  31. package/admin/js/templates/docs/api-plugins.html +123 -61
  32. package/admin/js/templates/docs/api-scaffold.html +185 -0
  33. package/admin/js/templates/docs/api-settings.html +72 -64
  34. package/admin/js/templates/docs/api-users.html +74 -107
  35. package/admin/js/templates/docs/api-views.html +68 -54
  36. package/admin/js/templates/docs/components-howto.html +20 -17
  37. package/admin/js/templates/docs/components-reference.html +13 -16
  38. package/admin/js/templates/docs/components-rules.html +7 -6
  39. package/admin/js/templates/docs/components-walkthrough.html +19 -19
  40. package/admin/js/templates/docs/tutorial-crud.html +71 -40
  41. package/admin/js/templates/docs/tutorial-forms.html +51 -35
  42. package/admin/js/templates/docs/tutorial-plugin.html +132 -56
  43. package/admin/js/templates/docs/usage-actions.html +61 -15
  44. package/admin/js/templates/docs/usage-collections.html +108 -0
  45. package/admin/js/templates/docs/usage-cta-shortcode.html +14 -3
  46. package/admin/js/templates/docs/usage-dconfig.html +0 -3
  47. package/admin/js/templates/docs/usage-editions.html +213 -0
  48. package/admin/js/templates/docs/usage-media.html +22 -6
  49. package/admin/js/templates/docs/usage-navigation.html +74 -18
  50. package/admin/js/templates/docs/usage-pages.html +60 -20
  51. package/admin/js/templates/docs/usage-plugins.html +89 -17
  52. package/admin/js/templates/docs/usage-shortcodes.html +123 -70
  53. package/admin/js/templates/docs/usage-site-settings.html +50 -18
  54. package/admin/js/templates/docs/usage-tools.html +73 -0
  55. package/admin/js/templates/docs/usage-users-roles.html +99 -20
  56. package/admin/js/templates/docs/usage-views.html +36 -19
  57. package/admin/js/templates/documentation.html +153 -32
  58. package/admin/js/templates/page-editor.html +0 -5
  59. package/admin/js/templates/plugin-guide.html +15 -0
  60. package/admin/js/templates/plugin-guides.html +21 -0
  61. package/admin/js/templates/pro-docs.html +53 -234
  62. package/admin/js/templates/tutorials.html +5 -4
  63. package/admin/js/views/actions-list.js +3 -3
  64. package/admin/js/views/analytics.js +5 -5
  65. package/admin/js/views/api-endpoint-editor.js +2 -2
  66. package/admin/js/views/block-editor.js +4 -4
  67. package/admin/js/views/blocks.js +4 -4
  68. package/admin/js/views/collection-editor.js +4 -4
  69. package/admin/js/views/collection-entries.js +7 -7
  70. package/admin/js/views/component-editor.js +2 -2
  71. package/admin/js/views/contacts.js +22 -20
  72. package/admin/js/views/context-menu-editor.js +5 -5
  73. package/admin/js/views/doc-pages.js +1 -1
  74. package/admin/js/views/form-editor.js +4 -4
  75. package/admin/js/views/form-submissions.js +2 -2
  76. package/admin/js/views/index.js +1 -1
  77. package/admin/js/views/media.js +3 -3
  78. package/admin/js/views/menu-editor.js +13 -13
  79. package/admin/js/views/menu-locations.js +2 -2
  80. package/admin/js/views/my-profile.js +1 -1
  81. package/admin/js/views/page-editor.js +8 -8
  82. package/admin/js/views/plugin-guides.js +5 -0
  83. package/admin/js/views/project-detail.js +2 -2
  84. package/admin/js/views/project-settings.js +1 -1
  85. package/admin/js/views/role-editor.js +4 -4
  86. package/admin/js/views/search.js +2 -2
  87. package/admin/js/views/seo.js +17 -17
  88. package/admin/js/views/settings.js +3 -3
  89. package/admin/js/views/theme.js +3 -3
  90. package/admin/js/views/user-editor.js +1 -1
  91. package/admin/js/views/users.js +2 -2
  92. package/admin/js/views/view-editor.js +1 -1
  93. package/bin/cli.js +13 -13
  94. package/bin/lib/node-version.js +29 -0
  95. package/package.json +1 -1
  96. package/plugins/_lib/admin/mail/compose-window.js +3 -2
  97. package/plugins/_lib/admin/mail/reader-view.js +7 -6
  98. package/plugins/_lib/admin/mail/scheduling.js +4 -2
  99. package/plugins/_lib/admin/mail/templates.js +4 -4
  100. package/plugins/_lib/admin/ui/dates.js +85 -0
  101. package/plugins/blog/CLAUDE.md +31 -22
  102. package/plugins/blog/admin/views/blog.js +3 -2
  103. package/plugins/blog/admin/views/comments.js +2 -1
  104. package/plugins/blog/admin/views/post-editor.js +4 -4
  105. package/plugins/blog/blocks/blog-card-row.html +1 -1
  106. package/plugins/blog/blocks/blog-card.html +2 -2
  107. package/plugins/blog/blocks/blog-post-classic.html +2 -2
  108. package/plugins/blog/blocks/blog-post-essay.html +2 -2
  109. package/plugins/blog/blocks/blog-post-feature.html +2 -2
  110. package/plugins/blog/blocks/blog-post-minimal.html +2 -2
  111. package/plugins/blog/blocks/blog-post-sidebar.html +2 -2
  112. package/plugins/blog/blocks/blog-post-split.html +2 -2
  113. package/plugins/blog/docs/guide.md +205 -0
  114. package/plugins/blog/lib/layouts.js +3 -3
  115. package/plugins/blog/lib/page.js +2 -1
  116. package/plugins/blog/plugin.js +3 -3
  117. package/plugins/blog/plugin.json +4 -4
  118. package/plugins/blog/tests/layouts.test.js +6 -0
  119. package/plugins/feedback/CLAUDE.md +22 -3
  120. package/plugins/feedback/admin/lib/kit.js +6 -7
  121. package/plugins/feedback/admin/views/feedback.js +79 -10
  122. package/plugins/feedback/admin/views/send.js +28 -6
  123. package/plugins/feedback/docs/guide.md +95 -0
  124. package/plugins/feedback/lib/receiver.js +9 -2
  125. package/plugins/feedback/lib/sender.js +3 -2
  126. package/plugins/feedback/plugin.js +54 -6
  127. package/plugins/feedback/plugin.json +4 -4
  128. package/plugins/feedback/tests/api.test.js +74 -2
  129. package/plugins/free-tier.lock.json +49 -44
  130. package/plugins/mail-reader/CLAUDE.md +33 -18
  131. package/plugins/mail-reader/docs/guide.md +147 -0
  132. package/plugins/mail-reader/plugin.json +1 -1
  133. package/plugins/security/CLAUDE.md +4 -1
  134. package/plugins/security/admin/views/security.js +5 -5
  135. package/plugins/security/docs/guide.md +170 -0
  136. package/plugins/security/plugin.js +2 -1
  137. package/plugins/security/plugin.json +2 -1
  138. package/plugins/shopping-cart/CLAUDE.md +7 -1
  139. package/plugins/shopping-cart/admin/lib/kit.js +5 -2
  140. package/plugins/shopping-cart/admin/views/orders.js +4 -4
  141. package/plugins/shopping-cart/admin/views/overview.js +2 -2
  142. package/plugins/shopping-cart/docs/guide.md +191 -0
  143. package/plugins/shopping-cart/lib/render.js +2 -1
  144. package/plugins/shopping-cart/plugin.json +3 -3
  145. package/public/js/collection-browser.js +2 -2
  146. package/public/js/site.js +1 -1
  147. package/scripts/gen-instance-secret.js +3 -1
  148. package/scripts/setup.js +3 -1
  149. package/server/middleware/auth.js +2 -1
  150. package/server/routes/api/actions.js +47 -27
  151. package/server/routes/api/blocks.js +2 -1
  152. package/server/routes/api/collections.js +16 -52
  153. package/server/routes/api/contacts.js +66 -3
  154. package/server/routes/api/documentation.js +42 -0
  155. package/server/routes/api/notifications.js +3 -2
  156. package/server/routes/api/users.js +10 -6
  157. package/server/server.js +16 -1
  158. package/server/services/actions.js +110 -34
  159. package/server/services/adapterRegistry.js +169 -16
  160. package/server/services/adapters/FileAdapter.js +25 -0
  161. package/server/services/adapters/MongoAdapter.js +23 -0
  162. package/server/services/collections.js +104 -1
  163. package/server/services/connectionManager.js +12 -0
  164. package/server/services/dates.js +81 -0
  165. package/server/services/docs.js +13 -2
  166. package/server/services/markdown.js +75 -26
  167. package/server/services/notification-sources.js +6 -5
  168. package/server/services/passwordReset.js +2 -1
  169. package/server/services/permissionRegistry.js +3 -2
  170. package/server/services/pluginGuides.js +255 -0
  171. package/server/services/pluginInstaller.js +54 -13
  172. package/server/services/plugins.js +29 -1
  173. package/server/services/presetCollections.js +31 -5
  174. package/server/services/renderer.js +2 -2
  175. package/server/services/sidebarBadges.js +3 -1
  176. package/server/services/tools.js +4 -2
  177. package/server/templates/page.html +2 -2
@@ -8,40 +8,51 @@
8
8
  <div class="docs-body">
9
9
 
10
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>
11
+ three required files and optional <code>admin/</code>, <code>public/</code> and <code>docs/</code>
12
+ subdirectories.</p>
13
+ <p>The quickest start is <strong>New plugin</strong> in the banner of <a href="#/plugins">System &gt;
14
+ Plugins</a> (the Marketplace screen). It copies the built-in template into <code>plugins/&lt;slug&gt;/</code>
15
+ with a server entry, config defaults, a wired admin view and a <code>CLAUDE.md</code>, switched on. Restart
16
+ the server to activate it. The rest of this page explains what those files do.</p>
12
17
 
13
18
  <h3>Directory structure</h3>
14
19
  <pre class="code-block"><code>plugins/
15
20
  my-plugin/
16
- plugin.json ← manifest (required)
17
- plugin.js ← Fastify plugin (required)
18
- config.js ← settings defaults (required)
21
+ plugin.json - manifest (required)
22
+ plugin.js - Fastify plugin (required)
23
+ config.js - settings defaults (required)
19
24
  admin/
20
25
  views/
21
- my-view.js ← admin SPA view (optional)
26
+ index.js - admin view (optional)
22
27
  templates/
23
- my-view.html ← view template (optional)
28
+ index.html - view template (optional)
29
+ css/
30
+ index.css - admin styles (optional)
24
31
  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>
32
+ inject-head.html - injected into &lt;head&gt; on every page (optional)
33
+ inject-body.html - injected before &lt;/body&gt; on every page (optional)
34
+ docs/
35
+ guide.md - user guide under Documentation &gt; Plugins (optional)
36
+ data/ - plugin data store (optional, never served)</code></pre>
37
+ <p>Only <code>admin/</code> and <code>public/</code> are served over HTTP; <code>plugin.js</code>,
38
+ <code>config.js</code> and <code>data/</code> never are.</p>
28
39
 
29
40
  <hr>
30
41
 
31
42
  <h3>1. plugin.json - the manifest</h3>
32
43
  <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>
44
+ startup with a warning in the server log. <code>name</code> must match the folder name.</p>
34
45
  <pre class="code-block"><code>{
35
46
  "name": "my-plugin",
36
47
  "displayName": "My Plugin",
37
48
  "version": "1.0.0",
38
- "description": "A short description shown on the Plugins page.",
49
+ "description": "A short description shown in the Marketplace.",
39
50
  "author": "Your Name",
40
51
  "date": "2026-03-01",
41
52
  "icon": "star"
42
53
  }</code></pre>
43
54
 
44
- <p>Optional fields:</p>
55
+ <p>Common optional fields:</p>
45
56
  <table class="table table-sm">
46
57
  <thead>
47
58
  <tr>
@@ -56,6 +67,12 @@
56
67
  <td>string</td>
57
68
  <td>Path (relative to plugin root) to an HTML snippet injected into <code>&lt;head&gt;</code>.</td>
58
69
  </tr>
70
+ <tr>
71
+ <td><code>inject.headLate</code></td>
72
+ <td>string</td>
73
+ <td>A snippet injected at the end of <code>&lt;head&gt;</code>, after the site stylesheets - for CSS
74
+ that must win.</td>
75
+ </tr>
59
76
  <tr>
60
77
  <td><code>inject.bodyEnd</code></td>
61
78
  <td>string</td>
@@ -76,6 +93,44 @@
76
93
  <td>object</td>
77
94
  <td>View modules to dynamically import into the admin SPA.</td>
78
95
  </tr>
96
+ <tr>
97
+ <td><code>admin.css</code></td>
98
+ <td>array</td>
99
+ <td>Admin stylesheets loaded with the plugin's views.</td>
100
+ </tr>
101
+ <tr>
102
+ <td><code>permissions</code></td>
103
+ <td>array</td>
104
+ <td>Permission resources the plugin guards its routes with. They appear in the role editor;
105
+ <code>grant</code> gives existing roles them once.</td>
106
+ </tr>
107
+ <tr>
108
+ <td><code>requires</code></td>
109
+ <td>array</td>
110
+ <td>Built-in Tools (<code>contacts</code>, <code>notes</code>, <code>todo</code>, <code>analytics</code>,
111
+ <code>seo</code>) or plugins this one cannot work without. It is not loaded while one is off.</td>
112
+ </tr>
113
+ <tr>
114
+ <td><code>uses</code></td>
115
+ <td>object</td>
116
+ <td><code>{"contacts": "Invite a contact group"}</code> - Tools or plugins it works without but uses
117
+ when they are on. The text is what the admin is told they lose.</td>
118
+ </tr>
119
+ <tr>
120
+ <td><code>supersedes</code></td>
121
+ <td>array</td>
122
+ <td>Plugins (or built-in Tools) this one replaces outright, such as a Pro edition of a free plugin.</td>
123
+ </tr>
124
+ <tr>
125
+ <td><code>settingsSchema</code></td>
126
+ <td>array</td>
127
+ <td>Labels, types and help for the Marketplace <strong>Configure</strong> form.</td>
128
+ </tr>
129
+ <tr>
130
+ <td><code>minCmsVersion</code></td>
131
+ <td>string</td>
132
+ <td>The oldest Domma CMS the plugin works on. Install and update are refused on an older CMS.</td>
133
+ </tr>
79
134
  </tbody>
80
135
  </table>
81
136
 
@@ -84,38 +139,48 @@
84
139
  <h3>2. plugin.js - the Fastify plugin</h3>
85
140
  <p>This is the server-side entry point. It must export a default <strong>async function</strong> that
86
141
  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>
142
+ <p>The CMS hands you auth middleware in <code>options.auth</code> (<code>authenticate</code>,
143
+ <code>requireAdmin</code>, <code>requirePermission</code>, <code>requireRole</code>,
144
+ <code>requireVisibility</code>), extension points in <code>options.hooks</code> (shortcodes, transforms,
145
+ sidebar items, notifications, <code>registerComponent</code> and more) and the merged settings in
146
+ <code>options.settings</code>. Always take them from there rather than importing the middleware directly.</p>
89
147
 
90
148
  <pre class="code-block"><code>import { getPluginSettings, savePluginState } from '../../server/services/plugins.js';
91
-
92
149
  export default async function myPlugin(fastify, options) {
93
150
  const { authenticate, requireAdmin } = options.auth;
94
-
95
151
  // Public endpoint - no auth needed
96
152
  fastify.get('/hello', async () => {
97
153
  return { message: 'Hello from my plugin!' };
98
154
  });
99
-
100
- // Admin-only endpoint
155
+ // Admin-only endpoint (role levels 0 and 1)
101
156
  fastify.get('/settings', { preHandler: [authenticate, requireAdmin] }, async () => {
102
157
  return getPluginSettings('my-plugin');
103
158
  });
104
-
105
159
  fastify.put('/settings', { preHandler: [authenticate, requireAdmin] }, async (request) => {
106
160
  savePluginState('my-plugin', { settings: request.body });
107
161
  return { ok: true };
108
162
  });
163
+ }
164
+ // Optional: runs once each time the plugin is switched on, not at install.
165
+ export async function onEnable({ fastify, services, hooks }) {
166
+ // Create collections, pages or forms here if needed.
167
+ }
168
+ // Optional: runs when it is switched off, and before an uninstall (uninstall: true).
169
+ export async function onDisable({ fastify, services, hooks, uninstall }) {
109
170
  }</code></pre>
110
171
 
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>
172
+ <p>Routes are registered under the prefix <code>/api/plugins/{name}</code> automatically - write
173
+ <code>'/hello'</code>, not the full path. The prefix is always locked to your plugin's directory name.</p>
174
+ <p>For your own screens, prefer <code>requirePermission('my-plugin', 'read')</code> with a permission
175
+ declared in <code>plugin.json</code> over <code>requireAdmin</code>: the site owner can then give it to any
176
+ role in <a href="#/roles">System &gt; Roles</a>.</p>
113
177
 
114
178
  <hr>
115
179
 
116
180
  <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>
181
+ <p>Export a plain object of default settings. These are merged with any overrides stored in
182
+ <code>config/plugins.json</code> when <code>getPluginSettings()</code> is called. The site owner changes
183
+ them with <strong>Configure</strong> on the plugin in the Marketplace.</p>
119
184
 
120
185
  <pre class="code-block"><code>export default {
121
186
  greeting: 'Hello, world!',
@@ -150,58 +215,55 @@ export default async function myPlugin(fastify, options) {
150
215
  ],
151
216
  "views": {
152
217
  "plugin-my-plugin": {
153
- "entry": "my-plugin/admin/views/my-view.js",
218
+ "entry": "my-plugin/admin/views/index.js",
154
219
  "exportName": "myPluginView"
155
220
  }
156
221
  }
157
222
  }</code></pre>
158
223
 
159
224
  <p>The view file follows the standard Domma view pattern - a <code>templateUrl</code> and an
160
- <code>onMount($container)</code> function:</p>
225
+ <code>onMount($container)</code> function. Call your API with <code>H.get</code> / <code>H.post</code>,
226
+ which add the sign-in header and refresh the token for you - do not hand-build a Bearer header with
227
+ <code>fetch()</code>:</p>
161
228
 
162
- <pre class="code-block"><code>// admin/views/my-view.js
229
+ <pre class="code-block"><code>// admin/views/index.js
163
230
  export const myPluginView = {
164
- templateUrl: '/plugins/my-plugin/admin/templates/my-view.html',
165
-
231
+ templateUrl: '/plugins/my-plugin/admin/templates/index.html',
166
232
  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
-
233
+ const res = await H.get('/api/plugins/my-plugin/settings');
234
+ const settings = res.data ?? res;
172
235
  $container.find('#greeting').text(settings.greeting);
173
- Domma.icons.scan();
236
+ I.scan();
174
237
  }
175
238
  };</code></pre>
176
239
 
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;
240
+ <p>The template is a plain HTML fragment (no <code>&lt;html&gt;</code> wrapper) using Domma classes
241
+ (<code>card</code>, <code>btn</code>, <code>form-input</code>). The admin already wraps every plugin view in
242
+ a banner with the plugin's name, version and licence, so do not draw your own page heading:</p>
184
243
 
244
+ <pre class="code-block"><code>&lt;!-- admin/templates/index.html --&gt;
185
245
  &lt;div class="card"&gt;
186
246
  &lt;div class="card-body"&gt;
187
- &lt;p id="greeting"&gt;Loading…&lt;/p&gt;
247
+ &lt;p id="greeting"&gt;Loading...&lt;/p&gt;
188
248
  &lt;/div&gt;
189
249
  &lt;/div&gt;</code></pre>
250
+ <p>Files under <code>admin/</code> are fetched by the browser, so a hard refresh of the admin picks up a
251
+ change without a restart.</p>
190
252
 
191
253
  <hr>
192
254
 
193
255
  <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>
256
+ <p>HTML snippets declared in <code>inject.head</code>, <code>inject.headLate</code> and
257
+ <code>inject.bodyEnd</code> are read from the plugin's folder and inserted into every public page. Use
258
+ this for analytics scripts, stylesheets, or widgets. They are read once at start-up.</p>
197
259
 
198
260
  <pre class="code-block"><code>&lt;!-- public/inject-body.html --&gt;
199
261
  &lt;script&gt;
200
262
  (function () {
201
263
  // This runs on every public page
202
264
  fetch('/api/plugins/my-plugin/hello')
203
- .then(r => r.json())
204
- .then(d => console.log(d.message));
265
+ .then(r =&gt; r.json())
266
+ .then(d =&gt; console.log(d.message));
205
267
  })();
206
268
  &lt;/script&gt;</code></pre>
207
269
 
@@ -210,23 +272,37 @@ export const myPluginView = {
210
272
 
211
273
  <hr>
212
274
 
213
- <h3>6. Registering and testing</h3>
275
+ <h3>6. User guide (optional)</h3>
276
+ <p>Markdown files in the plugin's <code>docs/</code> folder become pages under <strong>Documentation &gt;
277
+ Plugins</strong> while the plugin is loaded, shown only to users who can use the plugin.
278
+ <code>guide.md</code> is the first page; further files become tabs. Start with <code>##</code> sections,
279
+ not a top-level heading, and write for site owners.</p>
280
+
281
+ <hr>
282
+
283
+ <h3>7. Switching it on and testing</h3>
214
284
  <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>
285
+ <li>Create the <code>plugins/my-plugin/</code> directory with all three required files (or use
286
+ <strong>New plugin</strong>, which does this and switches it on).</li>
287
+ <li>Open <a href="#/plugins">System &gt; Plugins</a>. A plugin you made by hand is listed, switched off.
288
+ Right-click it (or use its menu) and choose <strong>Switch on</strong>. Its <code>onEnable</code> runs
289
+ now.</li>
290
+ <li>The server restarts by itself to load the plugin's routes and screens, where a supervisor (the
291
+ fleet manager or pm2) will bring it back and <code>restartOnPluginToggle</code> is not
292
+ <code>false</code> in <code>config/server.json</code>. Otherwise restart it yourself. The log then
293
+ shows <code>[plugins] Loaded N plugins: ..., my-plugin</code>.</li>
221
294
  <li>Verify your endpoint: <code>GET /api/plugins/my-plugin/hello</code></li>
222
295
  </ol>
223
296
 
224
297
  <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>
298
+ the server restarts automatically on file changes. Server-side files (<code>plugin.js</code>,
299
+ <code>config.js</code>, <code>plugin.json</code>, snippets) only apply after a restart; the in-admin code
300
+ editor (<strong>View source</strong> on the plugin) has a Restart server button. For the full reference see
301
+ <code>docs/plugin-development.md</code> in the Domma CMS package.</p>
226
302
 
227
303
  <hr>
228
304
  <p class="text-muted" style="font-size:.9rem;">
229
- Next: <a href="#/tutorials/forms">Form Follow-Up →</a>
305
+ Next: <a href="#/tutorials/forms">Form Follow-Up</a>
230
306
  </p>
231
307
 
232
308
  </div>
@@ -9,24 +9,27 @@
9
9
 
10
10
  <p>Actions are admin-designed sequential workflow operations triggered against individual collection
11
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>
12
+ entry list. Actions need a MongoDB connection, which is a Pro feature: without one, Data &gt; Actions
13
+ says so and nothing can be run.</p>
13
14
 
14
15
  <h3>Creating an Action</h3>
15
16
  <ol>
16
17
  <li>Navigate to <strong>Data → Actions</strong> and click <strong>New Action</strong>.</li>
17
18
  <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.
19
+ <li><strong>Trigger tab</strong> - the button label, icon and optional confirmation message.
20
+ Manual (a button) is the only trigger. An optional <strong>transition</strong> makes the
21
+ action a workflow step - see below.
20
22
  </li>
21
23
  <li><strong>Steps tab</strong> - add steps in order. Each step runs sequentially; if one fails the
22
24
  action stops.
23
25
  </li>
24
- <li><strong>Access tab</strong> - select which roles can trigger this action.</li>
26
+ <li><strong>Access tab</strong> - which roles can run the action, and an optional row-level
27
+ rule. See "Who can run an action" below.</li>
25
28
  <li>Click <strong>Save Action</strong>. The button will appear on the collection's entry list.
26
29
  </li>
27
30
  </ol>
28
31
 
29
- <h3>Phase 1 Step Types</h3>
32
+ <h3>Step Types</h3>
30
33
  <table class="table table-sm">
31
34
  <thead>
32
35
  <tr>
@@ -51,6 +54,11 @@
51
54
  <td>Create the entry in a target collection then delete it from the source</td>
52
55
  <td><code>targetCollection</code></td>
53
56
  </tr>
57
+ <tr>
58
+ <td><code>createInCollection</code></td>
59
+ <td>Create a new entry in another collection, leaving the source entry as it is (a log line, a related record)</td>
60
+ <td><code>targetCollection</code>, <code>data</code> (JSON object of field values), <code>createdBy</code> (optional; defaults to the person running the action)</td>
61
+ </tr>
54
62
  <tr>
55
63
  <td><code>webhook</code></td>
56
64
  <td>HTTP request to an external URL</td>
@@ -70,7 +78,7 @@
70
78
  </table>
71
79
 
72
80
  <h3>Template Variables</h3>
73
- <p>Step config fields support <code>{{variable}}</code> interpolation:</p>
81
+ <p>Step config fields support <code>&#123;&#123;variable&#125;&#125;</code> interpolation:</p>
74
82
  <table class="table table-sm">
75
83
  <thead>
76
84
  <tr>
@@ -80,43 +88,81 @@
80
88
  </thead>
81
89
  <tbody>
82
90
  <tr>
83
- <td><code>{{entry.data.fieldName}}</code></td>
91
+ <td><code>&#123;&#123;entry.data.fieldName&#125;&#125;</code></td>
84
92
  <td>A field value from the current entry</td>
85
93
  </tr>
86
94
  <tr>
87
- <td><code>{{entry.id}}</code></td>
95
+ <td><code>&#123;&#123;entry.id&#125;&#125;</code></td>
88
96
  <td>The entry's ID</td>
89
97
  </tr>
90
98
  <tr>
91
- <td><code>{{now}}</code></td>
99
+ <td><code>&#123;&#123;now&#125;&#125;</code></td>
92
100
  <td>Current timestamp (ISO 8601)</td>
93
101
  </tr>
94
102
  <tr>
95
- <td><code>{{user.name}}</code></td>
103
+ <td><code>&#123;&#123;user.id&#125;&#125;</code></td>
104
+ <td>ID of the user who triggered the action</td>
105
+ </tr>
106
+ <tr>
107
+ <td><code>&#123;&#123;user.name&#125;&#125;</code></td>
96
108
  <td>Name of the user who triggered the action</td>
97
109
  </tr>
98
110
  <tr>
99
- <td><code>{{user.email}}</code></td>
111
+ <td><code>&#123;&#123;user.email&#125;&#125;</code></td>
100
112
  <td>Email of the triggering user</td>
101
113
  </tr>
102
114
  <tr>
103
- <td><code>{{env.CMS_PUBLIC_*}}</code></td>
115
+ <td><code>&#123;&#123;user.role&#125;&#125;</code></td>
116
+ <td>Primary role of the triggering user</td>
117
+ </tr>
118
+ <tr>
119
+ <td><code>&#123;&#123;env.CMS_PUBLIC_*&#125;&#125;</code></td>
104
120
  <td>Environment variables prefixed <code>CMS_PUBLIC_</code> only</td>
105
121
  </tr>
106
122
  </tbody>
107
123
  </table>
108
124
  <p><strong>Example</strong> - approve an application and notify by email:</p>
109
125
  <pre class="code-block"><code>Step 1: updateField field=status value=approved
110
- Step 2: updateField field=approvedAt value={{now}}
111
- Step 3: email to={{entry.data.email}}
126
+ Step 2: updateField field=approvedAt value=&#123;&#123;now&#125;&#125;
127
+ Step 3: email to=&#123;&#123;entry.data.email&#125;&#125;
112
128
  subject=Your application has been approved
113
- template=Congratulations {{entry.data.name}}, your application is approved.</code></pre>
129
+ template=Congratulations &#123;&#123;entry.data.name&#125;&#125;, your application is approved.</code></pre>
130
+
131
+ <h3>Who can run an action</h3>
132
+ <ul>
133
+ <li><strong>Any listed role is enough.</strong> Ticking <code>admin</code> and <code>hr</code>
134
+ lets holders of either run it, and everyone more senior than either - the same ladder as
135
+ page visibility. All the roles a user holds count, not just their primary one.</li>
136
+ <li><strong>No roles ticked</strong> - admins only (role levels 0 and 1).</li>
137
+ <li><strong>A role the site does not have</strong> (deleted or renamed) admits only the level-0
138
+ role. The editor keeps it and flags it so you can fix it.</li>
139
+ <li><strong>Row-level rule</strong> - Owner (entries the user created) or Field match (entries
140
+ that name them in a field), checked on the server every time the action runs. The level-0
141
+ role is never limited by it.</li>
142
+ </ul>
143
+
144
+ <h3>Workflows: transitions</h3>
145
+ <p>Give an action a transition - a state field, the <strong>From</strong> values it may start
146
+ from, and the <strong>To</strong> value - and it becomes a step in a workflow. It is offered
147
+ only while the entry's field holds one of the From values; your steps (usually an Update a
148
+ field step) do the actual change.</p>
149
+ <p>On the public site, an interactive <code>[collection]</code> with the <code>transitions</code>
150
+ attribute shows each row the buttons that apply to it right now, for the person looking -
151
+ roles and the row-level rule included. Add <code>scope="mine"</code> for a "my entries" page
152
+ where people move their own entries on:</p>
153
+ <pre class="code-block"><code>[collection slug="applications" scope="mine" display="cards" title-field="jobTitle" paginate transitions /]</code></pre>
154
+ <p>Running an action whose transition no longer applies (the entry has moved on) is refused with
155
+ 409 and changes nothing. From a <code>scope="mine"</code> block, a row the person did not
156
+ create is refused with 403. For an action meant only for people's own entries, also give it
157
+ an Owner row-level rule, so it is refused however it is called.</p>
114
158
 
115
159
  <h3>Partial Execution</h3>
116
160
  <p>Actions are <strong>not transactional</strong>. If a step fails, the action stops and returns the
117
161
  number of steps completed so far (<code>stepsCompleted</code>). Steps that already ran are
118
162
  <strong>not rolled back</strong>. Design step order with this in mind - put irreversible steps
119
163
  (delete, email) last.</p>
164
+ <p>See also <a href="#/docs/usage/cta-shortcode">CTA Shortcode</a> for action buttons on pages, and
165
+ the <a href="#/docs/api/actions">Actions API</a>.</p>
120
166
 
121
167
  <h3>After a deleteEntry step</h3>
122
168
  <p>If an action contains a <code>deleteEntry</code> step, subsequent steps will fail because the entry
@@ -0,0 +1,108 @@
1
+ <div class="view-header">
2
+ <h1><span data-icon="book"></span> Collections &amp; Forms</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>A collection is a set of entries that share the same fields - products, bookings, team members,
11
+ enquiries. Collections are at <strong>Data &gt; Collections</strong>. Their entries are edited in
12
+ the admin, shown on pages with <code>[collection]</code>, filled in by visitors through forms,
13
+ and can be read by other systems through the API if you allow it.</p>
14
+
15
+ <h3>Creating a collection</h3>
16
+ <p>Choose <strong>New collection</strong>. The editor has four tabs:</p>
17
+ <ul>
18
+ <li><strong>Settings</strong> - title, slug (its id, fixed once created), project and the
19
+ layout of the entry form (stacked, or a grid where each field can span columns).</li>
20
+ <li><strong>Fields</strong> - what every entry holds. The editor offers text, email, phone,
21
+ number, textarea, dropdown, radio buttons, single checkbox, checkbox group, date, time,
22
+ URL and hidden fields. Reference (a link to an entry in another collection), file and
23
+ multi-select fields exist too, but are added by editing the collection's
24
+ <code>schema.json</code>.</li>
25
+ <li><strong>API &amp; Export</strong> - what the public API may do (read, create, update,
26
+ delete) and who may do it, which fields a read returns, and whether visitors may export
27
+ what a <code>[collection]</code> display shows.</li>
28
+ <li><strong>Storage</strong> - files on this server (the default) or MongoDB (Pro, when the
29
+ site has a connection).</li>
30
+ </ul>
31
+ <p>Every collection made in the admin gets a matching form, so visitors can add entries with
32
+ <code>[form name="the-slug" /]</code>.</p>
33
+
34
+ <h3>Working with entries</h3>
35
+ <p>Click a collection to see its entries. <strong>Add entry</strong>, <strong>Import</strong> and
36
+ <strong>Export</strong> are in the banner. Click an entry to edit it; right-click it to see
37
+ all of it, duplicate it, copy its id, run one of the collection's Actions or delete it, and
38
+ right-click a value to filter by it. Dates show as dd/mm/yyyy.</p>
39
+ <ul>
40
+ <li><strong>Export</strong> downloads the entries as JSON or CSV.</li>
41
+ <li><strong>Import</strong> takes a JSON array of entries and adds them (nothing is removed).
42
+ Each entry is checked like any other save: one that fails the field rules or points a
43
+ reference at an entry that does not exist is skipped and reported. Fields the collection
44
+ does not have are stored as given. For backups, restores and
45
+ clones of whole collections, see the Data Transfer plugin in the Marketplace.</li>
46
+ </ul>
47
+
48
+ <h3>Moving between files and MongoDB</h3>
49
+ <p>Changing a collection's storage on the Storage tab asks whether to move its entries when you
50
+ save. A move copies each entry exactly as it is - same id, data, created and updated dates and
51
+ owner - so references, links and "my entries" pages keep working. It refuses, changing
52
+ nothing, if the target already holds an entry with one of the same ids, or if MongoDB is not
53
+ reachable. The old copy is set aside, never deleted (<code>data.json</code> becomes
54
+ <code>data.json.bak</code>; a MongoDB collection is renamed). System collections (roles, user
55
+ profiles, projects, notifications, API tokens and endpoints) always stay on files.</p>
56
+
57
+ <h3>Showing entries on a page</h3>
58
+ <pre class="code-block"><code>[collection slug="team" display="cards" columns="3" title-field="name" /]
59
+ [collection slug="jobs" display="cards" searchable filterable="location,type" sortable where_status="open" /]
60
+ [collection slug="applications" scope="mine" display="cards" paginate transitions /]</code></pre>
61
+ <ul>
62
+ <li>Eight displays: table, cards, list, accordion, timeline, carousel, listgroup and block
63
+ (your own block template per entry).</li>
64
+ <li><code>searchable</code>, <code>sortable</code>, <code>filterable</code> or
65
+ <code>paginate</code> turn a display into the interactive <strong>Collection
66
+ Browser</strong>: search, sort, a filter rail and pages.</li>
67
+ <li><code>scope="mine"</code> shows signed-in visitors only the entries they created - a "my
68
+ applications" or "my bookings" page. It needs no public API access.</li>
69
+ <li><code>transitions</code> on an interactive block gives each row the workflow buttons that
70
+ apply to it now, and works with <code>scope="mine"</code>. See
71
+ <a href="#/docs/usage/actions">Actions</a>.</li>
72
+ </ul>
73
+ <p>Visitors can right-click any collection display to filter, sort, group, copy, print or export
74
+ it. The full attribute list is in <a href="#/docs/usage/shortcodes">Shortcodes</a>; for
75
+ joins, totals and per-user results, build a <a href="#/docs/usage/views">View</a>.</p>
76
+
77
+ <h3>Who can see entries</h3>
78
+ <ul>
79
+ <li>In the admin, the <code>collections</code> permission decides who can view, add, edit and
80
+ delete entries.</li>
81
+ <li>On pages, a <code>[collection]</code> shows its entries to anyone who can open the page -
82
+ gate the page, or use <code>scope="mine"</code> or a View with a row-level rule.</li>
83
+ <li>Through the API, the API &amp; Export tab decides: Public, Token (an API token), or signed-in
84
+ users with a role and above. A role the site does not have admits only the level-0
85
+ role.</li>
86
+ </ul>
87
+
88
+ <h3>Forms</h3>
89
+ <p>Forms are at <strong>Data &gt; Forms</strong>. A form stores what visitors send in a
90
+ collection, and can then email someone, call a webhook, run an Action and show a message or
91
+ redirect. Fields can show, hide or require other fields, and triggers can react to answers
92
+ (a banner, blocking the submit, jumping a step). Submissions are listed under the form.
93
+ Visitors' submissions are kept out of the site's Git repository. See
94
+ <a href="#/tutorials/forms">Form Follow-Up</a> and the <a href="#/docs/api/forms">Forms
95
+ API</a>.</p>
96
+
97
+ <h3>See also</h3>
98
+ <ul>
99
+ <li><a href="#/docs/api/collections">Collections API</a>,
100
+ <a href="#/docs/api/external">External API &amp; tokens</a> and
101
+ <a href="#/docs/api/builder">API Builder</a></li>
102
+ <li><a href="#/tutorials/crud">Building a CRUD App</a> - collections, forms, actions and pages
103
+ together</li>
104
+ </ul>
105
+
106
+ </div>
107
+ </div>
108
+ </div>
@@ -10,7 +10,9 @@
10
10
  <p>The <code>[cta]</code> shortcode places an action-trigger button in any public page. Clicking it
11
11
  calls
12
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>
13
+ If the user is not logged in, a warning toast is shown instead. The action decides who may run
14
+ it (its roles and row-level rule, checked on the server), and Actions need a MongoDB
15
+ connection - see <a href="#/docs/usage/actions">Actions</a>.</p>
14
16
 
15
17
  <h3>Syntax &amp; Attributes</h3>
16
18
  <p><strong>Wrapping form:</strong></p>
@@ -116,8 +118,17 @@
116
118
  </tr>
117
119
  </tbody>
118
120
  </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
+ <p>CTA buttons appear on the <strong>cards</strong> (card footer), <strong>list</strong> (inline),
122
+ <strong>table</strong> (a column of its own) and <strong>block</strong> displays, and on each
123
+ row of an interactive Collection Browser. On a <code>scope="mine"</code> block the server
124
+ refuses a row the visitor did not create.</p>
125
+ <p>A saved View takes the same buttons with <code>action="slug"</code> and the same
126
+ <code>cta-label</code>, <code>cta-style</code>, <code>cta-icon</code> and
127
+ <code>cta-confirm</code> attributes:</p>
128
+ <pre class="code-block"><code>[view slug="pending-applications" display="cards" action="approve-application" cta-label="Approve" /]</code></pre>
129
+ <p>For buttons that change with each row's state (Submit, Withdraw, Approve), use
130
+ <code>transitions</code> on an interactive <code>[collection]</code> instead - see
131
+ <a href="#/docs/usage/actions">Actions</a>.</p>
121
132
 
122
133
  </div>
123
134
  </div>
@@ -85,7 +85,6 @@
85
85
  <pre class="code-block"><code>[card id="my-panel" title="Hidden Details"]
86
86
  Content goes here.
87
87
  [/card]
88
-
89
88
  [dconfig]
90
89
  {
91
90
  "#my-btn": {
@@ -107,9 +106,7 @@ Content goes here.
107
106
  }
108
107
  }
109
108
  [/dconfig]
110
-
111
109
  &lt;button id="my-btn" class="btn btn-primary"&gt;Toggle&lt;/button&gt;
112
-
113
110
  &lt;div id="my-panel" class="card hidden"&gt;
114
111
  &lt;div class="card-body"&gt;Hidden content.&lt;/div&gt;
115
112
  &lt;/div&gt;</code></pre>