domma-cms 0.44.0 → 0.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +53 -53
- package/README.md +27 -27
- package/admin/css/admin.css +1 -1
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/index.html +2 -2
- package/admin/js/app.js +1 -1
- package/admin/js/lib/card-builder.js +2 -2
- package/admin/js/lib/crud-tutorial.js +1 -1
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +1 -1
- package/admin/js/lib/image-editor.js +1 -1
- package/admin/js/lib/markdown-toolbar.js +2 -2
- package/admin/js/lib/project-context.js +1 -1
- package/admin/js/lib/project-quick-create.js +1 -1
- package/admin/js/lib/scribe-composer.js +1 -1
- package/admin/js/lib/server-restart.js +1 -1
- package/admin/js/lib/shortcode-context-menu.js +2 -2
- package/admin/js/lib/simple-editor.js +1 -1
- package/admin/js/lib/themes.js +1 -1
- package/admin/js/lib/timeline-builder.js +1 -1
- package/admin/js/templates/action-editor.html +8 -8
- package/admin/js/templates/api-endpoint-editor.html +7 -7
- package/admin/js/templates/api-endpoints.html +1 -1
- package/admin/js/templates/api-reference.html +1 -1
- package/admin/js/templates/block-editor.html +6 -6
- package/admin/js/templates/collection-editor.html +1 -1
- package/admin/js/templates/component-editor.html +2 -2
- package/admin/js/templates/dashboard/cache.html +3 -3
- package/admin/js/templates/dashboard/journeys.html +3 -3
- package/admin/js/templates/dashboard/kpi-strip.html +3 -3
- package/admin/js/templates/dashboard/top-pages.html +1 -1
- package/admin/js/templates/dashboard/traffic-chart.html +1 -1
- package/admin/js/templates/docs/api-actions.html +1 -1
- package/admin/js/templates/docs/api-collections.html +4 -4
- package/admin/js/templates/docs/api-pages.html +2 -2
- package/admin/js/templates/docs/api-plugins.html +1 -1
- package/admin/js/templates/docs/api-settings.html +2 -2
- package/admin/js/templates/docs/api-users.html +1 -1
- package/admin/js/templates/docs/components-howto.html +4 -4
- package/admin/js/templates/docs/components-reference.html +21 -21
- package/admin/js/templates/docs/components-rules.html +14 -14
- package/admin/js/templates/docs/components-walkthrough.html +13 -13
- package/admin/js/templates/docs/tutorial-crud.html +49 -49
- package/admin/js/templates/docs/tutorial-forms.html +4 -4
- package/admin/js/templates/docs/tutorial-plugin.html +10 -10
- package/admin/js/templates/docs/usage-actions.html +6 -6
- package/admin/js/templates/docs/usage-cta-shortcode.html +9 -9
- package/admin/js/templates/docs/usage-dconfig.html +4 -4
- package/admin/js/templates/docs/usage-navigation.html +1 -1
- package/admin/js/templates/docs/usage-pages.html +1 -1
- package/admin/js/templates/docs/usage-shortcodes.html +24 -24
- package/admin/js/templates/docs/usage-users-roles.html +3 -3
- package/admin/js/templates/docs/usage-views.html +4 -4
- package/admin/js/templates/documentation.html +1 -1
- package/admin/js/templates/effects.html +752 -752
- package/admin/js/templates/form-editor.html +5 -5
- package/admin/js/templates/forms.html +17 -17
- package/admin/js/templates/login.html +5 -5
- package/admin/js/templates/menu-editor.html +10 -10
- package/admin/js/templates/my-profile.html +17 -17
- package/admin/js/templates/navigation.html +11 -11
- package/admin/js/templates/notifications.html +1 -1
- package/admin/js/templates/page-editor.html +4 -4
- package/admin/js/templates/pro-docs.html +14 -14
- package/admin/js/templates/role-editor.html +70 -70
- package/admin/js/templates/roles.html +10 -10
- package/admin/js/templates/settings.html +10 -10
- package/admin/js/templates/tutorials.html +3 -3
- package/admin/js/templates/view-editor.html +15 -15
- package/admin/js/views/action-editor.js +1 -1
- package/admin/js/views/actions-list.js +1 -1
- package/admin/js/views/api-endpoint-editor.js +2 -2
- package/admin/js/views/api-endpoints.js +2 -2
- package/admin/js/views/api-tokens.js +3 -3
- package/admin/js/views/blocks.js +2 -2
- package/admin/js/views/collection-editor.js +1 -1
- package/admin/js/views/collection-entries.js +1 -1
- package/admin/js/views/collections.js +1 -1
- package/admin/js/views/component-editor.js +1 -1
- package/admin/js/views/components.js +4 -4
- package/admin/js/views/dashboard/widgets/cache.js +1 -1
- package/admin/js/views/dashboard/widgets/journeys.js +1 -1
- package/admin/js/views/dashboard/widgets/kpi-strip.js +1 -1
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/form-submissions.js +1 -1
- package/admin/js/views/forms.js +1 -1
- package/admin/js/views/layouts.js +1 -1
- package/admin/js/views/login.js +2 -2
- package/admin/js/views/menu-editor.js +3 -3
- package/admin/js/views/menu-locations.js +1 -1
- package/admin/js/views/menus.js +3 -3
- package/admin/js/views/navigation.js +8 -8
- package/admin/js/views/page-editor.js +40 -40
- package/admin/js/views/pages.js +3 -3
- package/admin/js/views/plugin-code.js +2 -2
- package/admin/js/views/plugins.js +1 -1
- package/admin/js/views/project-settings.js +1 -1
- package/admin/js/views/projects.js +1 -1
- package/admin/js/views/roles.js +1 -1
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/users.js +3 -3
- package/admin/js/views/view-editor.js +1 -1
- package/admin/js/views/view-preview.js +1 -1
- package/admin/js/views/views-list.js +1 -1
- package/bin/cli.js +8 -8
- package/bin/lib/config-merge.js +44 -44
- package/bin/update.js +16 -16
- package/config/plugins.json +1 -1
- package/package.json +2 -2
- package/plugins/_template/admin/views/index.js +1 -1
- package/plugins/_template/config.js +1 -1
- package/plugins/_template/plugin.js +1 -1
- package/plugins/analytics/admin/templates/analytics.html +3 -3
- package/plugins/analytics/admin/views/analytics.js +1 -1
- package/plugins/analytics/config.js +1 -1
- package/plugins/analytics/plugin.js +4 -4
- package/plugins/analytics/plugin.json +1 -1
- package/plugins/analytics/public/inject-body.html +2 -2
- package/plugins/analytics/public/inject-head.html +1 -1
- package/plugins/blog/admin/templates/blog.html +4 -4
- package/plugins/blog/admin/views/blog.js +3 -3
- package/plugins/blog/admin/views/categories.js +2 -2
- package/plugins/blog/admin/views/comments.js +4 -4
- package/plugins/blog/admin/views/post-editor.js +1 -1
- package/plugins/blog/plugin.js +2 -2
- package/plugins/blog/plugin.json +4 -4
- package/plugins/contacts/admin/views/contacts.js +8 -8
- package/plugins/contacts/plugin.js +4 -4
- package/plugins/contacts/plugin.json +1 -1
- package/plugins/invoice/admin/templates/editor.html +6 -6
- package/plugins/invoice/admin/templates/index.html +4 -4
- package/plugins/invoice/admin/views/editor.js +3 -3
- package/plugins/invoice/admin/views/index.js +4 -4
- package/plugins/invoice/admin/views/issuers.js +1 -1
- package/plugins/invoice/admin/views/party-view.js +3 -3
- package/plugins/invoice/admin/views/receivers.js +1 -1
- package/plugins/invoice/config.js +1 -1
- package/plugins/invoice/plugin.js +1 -1
- package/plugins/invoice/plugin.json +4 -4
- package/plugins/notes/admin/views/notes.js +4 -4
- package/plugins/notes/plugin.js +1 -1
- package/plugins/notes/plugin.json +1 -1
- package/plugins/site-search/admin/templates/site-search.html +3 -3
- package/plugins/site-search/admin/views/site-search.js +1 -1
- package/plugins/site-search/config.js +1 -1
- package/plugins/site-search/plugin.js +4 -4
- package/plugins/site-search/plugin.json +1 -1
- package/plugins/surveys/admin/templates/results.html +1 -1
- package/plugins/surveys/admin/templates/survey-editor.html +4 -4
- package/plugins/surveys/admin/views/audience.js +6 -6
- package/plugins/surveys/admin/views/results.js +3 -3
- package/plugins/surveys/admin/views/survey-editor.js +1 -1
- package/plugins/surveys/admin/views/surveys.js +2 -2
- package/plugins/surveys/plugin.js +1 -1
- package/plugins/surveys/plugin.json +4 -4
- package/plugins/theme-switcher/admin/views/theme-switcher.js +1 -1
- package/plugins/theme-switcher/plugin.json +3 -3
- package/plugins/theme-switcher/public/inject-body.html +28 -28
- package/plugins/theme-switcher/public/inject-head.html +1 -1
- package/plugins/todo/plugin.js +1 -1
- package/public/css/forms.css +1 -1
- package/public/js/collection-browser.js +1 -1
- package/public/js/collection-context.js +1 -1
- package/public/js/form-logic-engine.js +1 -1
- package/public/js/forms.js +2 -1
- package/public/js/site.js +1 -1
- package/scripts/build.js +14 -14
- package/scripts/create-plugin.js +2 -2
- package/scripts/fresh.js +2 -2
- package/scripts/gen-instance-secret.js +1 -1
- package/scripts/pro.js +12 -12
- package/scripts/reset.js +2 -2
- package/scripts/setup.js +15 -15
- package/scripts/users.js +5 -5
- package/scripts/verify-assets.mjs +6 -6
- package/server/config.js +1 -1
- package/server/middleware/auth.js +253 -253
- package/server/middleware/managerAuth.js +3 -3
- package/server/routes/api/actions.js +4 -4
- package/server/routes/api/api-endpoints.js +7 -7
- package/server/routes/api/api-tokens.js +5 -5
- package/server/routes/api/auth.js +309 -309
- package/server/routes/api/blocks.js +3 -3
- package/server/routes/api/collections.js +18 -18
- package/server/routes/api/dashboard.js +5 -5
- package/server/routes/api/effects.js +2 -2
- package/server/routes/api/endpoints-public.js +5 -5
- package/server/routes/api/forms.js +47 -47
- package/server/routes/api/menu-locations.js +3 -3
- package/server/routes/api/menus.js +7 -7
- package/server/routes/api/navigation.js +42 -42
- package/server/routes/api/notifications.js +2 -2
- package/server/routes/api/pages.js +14 -2
- package/server/routes/api/plugin-marketplace.js +3 -3
- package/server/routes/api/plugins.js +6 -6
- package/server/routes/api/projects.js +9 -9
- package/server/routes/api/scaffold.js +7 -7
- package/server/routes/api/settings.js +6 -6
- package/server/routes/api/sidebar.js +1 -1
- package/server/routes/api/users.js +5 -5
- package/server/routes/api/versions.js +1 -1
- package/server/routes/api/views.js +1 -1
- package/server/routes/docs-public.js +3 -3
- package/server/routes/public.js +14 -14
- package/server/server.js +34 -16
- package/server/services/actions.js +19 -19
- package/server/services/adapters/FileAdapter.js +2 -2
- package/server/services/adapters/MongoAdapter.js +5 -5
- package/server/services/apiEndpoints.js +15 -15
- package/server/services/apiTokens.js +7 -7
- package/server/services/blocks.js +13 -13
- package/server/services/cache/drivers/MemoryDriver.js +3 -3
- package/server/services/cache/drivers/NoneDriver.js +1 -1
- package/server/services/cache/index.js +5 -5
- package/server/services/collections.js +14 -14
- package/server/services/components.js +9 -9
- package/server/services/connectionManager.js +4 -4
- package/server/services/content.js +25 -2
- package/server/services/docs.js +14 -14
- package/server/services/email.js +167 -167
- package/server/services/filterEngine.js +6 -6
- package/server/services/forms.js +18 -10
- package/server/services/health.js +1 -1
- package/server/services/hooks.js +7 -7
- package/server/services/images.js +4 -4
- package/server/services/managerClient.js +1 -1
- package/server/services/markdown.js +79 -79
- package/server/services/menuRender.js +5 -5
- package/server/services/menus-migration.js +3 -3
- package/server/services/menus.js +31 -31
- package/server/services/permissionRegistry.js +1 -1
- package/server/services/pluginFiles.js +5 -5
- package/server/services/pluginInstaller.js +17 -17
- package/server/services/pluginScaffold.js +1 -1
- package/server/services/plugins.js +17 -17
- package/server/services/presetCollections.js +2 -2
- package/server/services/projects.js +35 -26
- package/server/services/recipes/contact-list.json +1 -1
- package/server/services/recipes/onboarding.json +8 -8
- package/server/services/references.js +8 -8
- package/server/services/renderer.js +9 -9
- package/server/services/roles.js +9 -9
- package/server/services/rowAccess.js +10 -10
- package/server/services/scaffolder.js +30 -30
- package/server/services/sidebar-migration.js +10 -10
- package/server/services/sitemap.js +1 -1
- package/server/services/userProfiles.js +199 -199
- package/server/services/userRoles.js +7 -7
- package/server/services/users.js +302 -302
- package/server/services/versions.js +3 -3
- package/server/services/viewPipeline.js +6 -6
- package/server/services/views.js +11 -11
- package/server/templates/page.html +5 -5
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div class="view-header">
|
|
2
|
-
<h1><span data-icon="component"></span> Components
|
|
2
|
+
<h1><span data-icon="component"></span> Components - Rules</h1>
|
|
3
3
|
<a href="#/docs/components" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> Reference</a>
|
|
4
4
|
</div>
|
|
5
5
|
|
|
@@ -16,18 +16,18 @@
|
|
|
16
16
|
</nav>
|
|
17
17
|
|
|
18
18
|
<p class="lead">The constraints the compiler enforces, and the handful of behaviours that commonly
|
|
19
|
-
trip people up. Everything here is validated at save time
|
|
19
|
+
trip people up. Everything here is validated at save time - a component that breaks a rule never
|
|
20
20
|
reaches disk.</p>
|
|
21
21
|
|
|
22
22
|
<hr>
|
|
23
23
|
|
|
24
24
|
<h2>Naming</h2>
|
|
25
25
|
<ul>
|
|
26
|
-
<li>Names must match <code>^[a-z][a-z0-9-]*$</code>
|
|
26
|
+
<li>Names must match <code>^[a-z][a-z0-9-]*$</code> - start with a lowercase letter, then lowercase
|
|
27
27
|
letters, digits and hyphens.</li>
|
|
28
28
|
<li>Valid: <code>counter</code>, <code>copy-button</code>, <code>price-tag-2</code>. Invalid:
|
|
29
29
|
<code>Counter</code>, <code>1counter</code>, <code>my_component</code>, <code>../etc/passwd</code>.</li>
|
|
30
|
-
<li>The name becomes the element tag <code><dm-<em>name</em>></code> and is <strong>permanent</strong>
|
|
30
|
+
<li>The name becomes the element tag <code><dm-<em>name</em>></code> and is <strong>permanent</strong> -
|
|
31
31
|
it can't be changed after creation. To rename, create a new component and delete the old one.</li>
|
|
32
32
|
</ul>
|
|
33
33
|
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
<h2>Props must be a typed JSON object</h2>
|
|
47
47
|
<ul>
|
|
48
48
|
<li>The <code><props></code> body must be valid JSON and an <strong>object</strong> (not an
|
|
49
|
-
array, not a bare value)
|
|
49
|
+
array, not a bare value) - otherwise <code>INVALID_PROPS_JSON</code>.</li>
|
|
50
50
|
<li>Every prop must declare a <code>type</code> that is one of <code>string</code>,
|
|
51
51
|
<code>number</code>, <code>boolean</code>, <code>array</code>, <code>object</code>. Anything else
|
|
52
52
|
(or a missing type) fails validation.</li>
|
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
<h2>The script must <code>export default</code> an object</h2>
|
|
60
60
|
<ul>
|
|
61
61
|
<li>The <code><script></code> body must be exactly one <code>export default { … };</code>
|
|
62
|
-
statement
|
|
62
|
+
statement - the compiler strips that prefix and embeds the object. No <code>export default</code>
|
|
63
63
|
means <code>SCRIPT_MISSING_EXPORT</code>.</li>
|
|
64
|
-
<li>Use an object expression
|
|
64
|
+
<li>Use an object expression - not a class, not multiple exports. An empty
|
|
65
65
|
<code>export default {};</code> is valid for a purely presentational component.</li>
|
|
66
66
|
</ul>
|
|
67
67
|
|
|
@@ -84,13 +84,13 @@
|
|
|
84
84
|
|
|
85
85
|
<div class="alert alert-warning">
|
|
86
86
|
<strong>3. Attribute casing.</strong> On the raw custom element, camelCase props are written as
|
|
87
|
-
<code>kebab-case</code> attributes
|
|
87
|
+
<code>kebab-case</code> attributes - <code>showReset</code> becomes <code>show-reset</code>. The
|
|
88
88
|
<code>[component]</code> shortcode accepts the prop name as written.
|
|
89
89
|
</div>
|
|
90
90
|
|
|
91
91
|
<div class="alert alert-warning">
|
|
92
|
-
<strong>4. Array / object props are JSON in the attribute.</strong> Pass them as a JSON string
|
|
93
|
-
e.g. <code>items='["a","b"]'</code>
|
|
92
|
+
<strong>4. Array / object props are JSON in the attribute.</strong> Pass them as a JSON string -
|
|
93
|
+
e.g. <code>items='["a","b"]'</code> - and the runtime parses them to the declared type.
|
|
94
94
|
</div>
|
|
95
95
|
|
|
96
96
|
<div class="alert alert-warning">
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
|
|
102
102
|
<div class="alert alert-warning">
|
|
103
103
|
<strong>6. Clean up in <code>onUnmount()</code>.</strong> Anything you start in <code>onMount</code>
|
|
104
|
-
|
|
104
|
+
- intervals, timeouts, external listeners - must be torn down in <code>onUnmount</code> so it
|
|
105
105
|
doesn't leak when the element is removed.
|
|
106
106
|
</div>
|
|
107
107
|
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
<li>Components contributed by a plugin show a <span class="badge badge-secondary">plugin</span>
|
|
113
113
|
badge, are served from memory, and <strong>cannot be edited or deleted</strong> from the admin
|
|
114
114
|
(attempting it returns <code>PLUGIN_OWNED</code>).</li>
|
|
115
|
-
<li>You also can't save a disk component whose name a plugin already owns
|
|
115
|
+
<li>You also can't save a disk component whose name a plugin already owns - pick a different name.</li>
|
|
116
116
|
</ul>
|
|
117
117
|
|
|
118
118
|
<hr>
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
<h2>Import / export constraints</h2>
|
|
121
121
|
<ul>
|
|
122
122
|
<li>A bundle must have a numeric <code>format</code> no newer than the CMS supports, plus
|
|
123
|
-
<code>name</code> and <code>source</code> strings
|
|
123
|
+
<code>name</code> and <code>source</code> strings - otherwise <code>INVALID_BUNDLE</code>.</li>
|
|
124
124
|
<li>Importing over an existing name returns <code>CONFLICT</code> unless you confirm the overwrite
|
|
125
125
|
(the Import button prompts you).</li>
|
|
126
126
|
<li>Imported source is compiled like any save, so a broken bundle is rejected with the same errors.</li>
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
<ul>
|
|
133
133
|
<li><code>{{ }}</code> interpolation is HTML-escaped, so prop values can't inject markup.</li>
|
|
134
134
|
<li>The markdown sanitiser keeps a live allowlist of <code>dm-*</code> tags, refreshed whenever you
|
|
135
|
-
save or delete a component
|
|
135
|
+
save or delete a component - a component you just created is immediately usable in page content.
|
|
136
136
|
<code>on*</code> handler attributes and <code>javascript:</code> URLs are still stripped globally.</li>
|
|
137
137
|
<li>Saving recompiles and invalidates the cached module, so edits take effect on the next page load
|
|
138
138
|
without a server restart.</li>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div class="view-header">
|
|
2
|
-
<h1><span data-icon="component"></span> Components
|
|
2
|
+
<h1><span data-icon="component"></span> Components - Walkthrough</h1>
|
|
3
3
|
<a href="#/components/new" class="btn btn-primary btn-sm"><span data-icon="plus"></span> New Component</a>
|
|
4
4
|
</div>
|
|
5
5
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
<hr>
|
|
26
26
|
|
|
27
|
-
<h2>Step 1
|
|
27
|
+
<h2>Step 1 - Create the component</h2>
|
|
28
28
|
<p>Open <a href="#/components">Data → Components</a> → <strong>New Component</strong> and name it
|
|
29
29
|
<code>star-rating</code>. Remember: lowercase + hyphens, and the name is permanent because it
|
|
30
30
|
becomes the element tag <code><dm-star-rating></code>. The four source tabs
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
<hr>
|
|
35
35
|
|
|
36
|
-
<h2>Step 2
|
|
36
|
+
<h2>Step 2 - Declare the props</h2>
|
|
37
37
|
<p>Start with the inputs, because they shape everything else. A rating needs a maximum number of
|
|
38
38
|
stars and a current value. Put this in the <code><props></code> tab:</p>
|
|
39
39
|
<pre class="code-block"><code>{
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
|
|
46
46
|
<hr>
|
|
47
47
|
|
|
48
|
-
<h2>Step 3
|
|
48
|
+
<h2>Step 3 - Write the template</h2>
|
|
49
49
|
<p>We render <code>max</code> stars and mark each as filled if its index is below the current rating.
|
|
50
50
|
Because <code>{{#each}}</code> needs a list, we'll build a <code>stars</code> array in state
|
|
51
51
|
(next step) where each item knows whether it's <code>on</code>. For now, the markup:</p>
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
|
|
62
62
|
<hr>
|
|
63
63
|
|
|
64
|
-
<h2>Step 4
|
|
64
|
+
<h2>Step 4 - Add the script (state + behaviour)</h2>
|
|
65
65
|
<p>Three jobs: build the <code>stars</code> array from <code>value</code>, rebuild it whenever the
|
|
66
66
|
rating changes, and handle clicks. Remember <code>data()</code> can't see props, so we seed state
|
|
67
67
|
in <code>onMount()</code>.</p>
|
|
@@ -102,7 +102,7 @@ export default {
|
|
|
102
102
|
|
|
103
103
|
<hr>
|
|
104
104
|
|
|
105
|
-
<h2>Step 5
|
|
105
|
+
<h2>Step 5 - Style it (scoped)</h2>
|
|
106
106
|
<pre class="code-block"><code><style>
|
|
107
107
|
.dm-stars { display: inline-flex; gap: .15rem; }
|
|
108
108
|
.dm-stars .star {
|
|
@@ -116,15 +116,15 @@ export default {
|
|
|
116
116
|
|
|
117
117
|
<hr>
|
|
118
118
|
|
|
119
|
-
<h2>Step 6
|
|
119
|
+
<h2>Step 6 - Watch the preview, then save</h2>
|
|
120
120
|
<p>As you typed, the editor recompiled and re-mounted the component in the preview iframe. Toggle
|
|
121
121
|
<code>readonly</code> and change <code>value</code> in the <em>Preview props</em> panel to sanity-check
|
|
122
|
-
both modes. When it looks right, hit <strong>Save Component</strong>
|
|
122
|
+
both modes. When it looks right, hit <strong>Save Component</strong> - the source compiles before it
|
|
123
123
|
is written, so a typo surfaces as a clear error rather than a broken page.</p>
|
|
124
124
|
|
|
125
125
|
<hr>
|
|
126
126
|
|
|
127
|
-
<h2>Step 7
|
|
127
|
+
<h2>Step 7 - Use it on a page</h2>
|
|
128
128
|
<p>Display-only, in any Markdown page:</p>
|
|
129
129
|
<pre class="code-block"><code>[component name="star-rating" max="5" value="4" readonly="true" /]</code></pre>
|
|
130
130
|
<p>Interactive, reacting to the custom event with a little page script:</p>
|
|
@@ -137,9 +137,9 @@ document.getElementById('r1').addEventListener('rating-change', (e) => {
|
|
|
137
137
|
|
|
138
138
|
<hr>
|
|
139
139
|
|
|
140
|
-
<h2>Step 8
|
|
140
|
+
<h2>Step 8 - Share it</h2>
|
|
141
141
|
<p>Back on <a href="#/components">Components</a>, the <span data-icon="download"></span> Export button
|
|
142
|
-
gives you <code>star-rating.dmcomponent.json</code>
|
|
142
|
+
gives you <code>star-rating.dmcomponent.json</code> - import that on another Domma site to reuse the
|
|
143
143
|
widget verbatim.</p>
|
|
144
144
|
|
|
145
145
|
<hr>
|
|
@@ -154,8 +154,8 @@ document.getElementById('r1').addEventListener('rating-change', (e) => {
|
|
|
154
154
|
<li><strong>Scoped CSS</strong>, the live preview, and export/import</li>
|
|
155
155
|
</ul>
|
|
156
156
|
|
|
157
|
-
<p>For the rules that keep components well-behaved
|
|
158
|
-
gotcha, boolean coercion, plugin-owned names
|
|
157
|
+
<p>For the rules that keep components well-behaved - naming, the <code>data()</code>-can't-see-props
|
|
158
|
+
gotcha, boolean coercion, plugin-owned names - read <a href="#/docs/components-rules">Rules</a>.</p>
|
|
159
159
|
|
|
160
160
|
<hr>
|
|
161
161
|
<p class="text-muted" style="font-size:.9rem;">
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<div class="docs-body" id="tutorial-crud">
|
|
9
9
|
|
|
10
10
|
<p class="lead">
|
|
11
|
-
A "CRUD app"
|
|
11
|
+
A "CRUD app" - Create, Read, Update, Delete - is the shape of almost every business
|
|
12
12
|
tool you'll ever build. Onboarding, bookings, contacts, tickets, orders, RSVPs,
|
|
13
13
|
classified ads. Domma CMS gives you everything to build one without writing
|
|
14
14
|
JavaScript, but the trick is understanding <em>why</em> the pieces compose the way
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
Domma CMS gives each of those a name:</p>
|
|
27
27
|
|
|
28
28
|
<ul>
|
|
29
|
-
<li><strong>Collection</strong>
|
|
30
|
-
<li><strong>Form</strong>
|
|
31
|
-
<li><strong>Action</strong>
|
|
32
|
-
<li><strong>Page</strong>
|
|
29
|
+
<li><strong>Collection</strong> - the data store. Think "spreadsheet": rows are records, columns are fields with types.</li>
|
|
30
|
+
<li><strong>Form</strong> - how new records get added. It writes to a Collection on submit.</li>
|
|
31
|
+
<li><strong>Action</strong> - a server-side button that changes existing records. "Approve", "Withdraw", "Send invoice".</li>
|
|
32
|
+
<li><strong>Page</strong> - a Markdown page with <em>shortcodes</em> that render forms and lists from your collections.</li>
|
|
33
33
|
</ul>
|
|
34
34
|
|
|
35
35
|
<p>These are deliberately separate. A single Collection might be written to by three different Forms
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
separate is what lets the same data drive multiple experiences.</p>
|
|
39
39
|
|
|
40
40
|
<div class="alert alert-info">
|
|
41
|
-
<strong>Why not a database?</strong> Collections store as flat JSON files on disk by default
|
|
41
|
+
<strong>Why not a database?</strong> Collections store as flat JSON files on disk by default -
|
|
42
42
|
no database to install, no SQL to learn, no migrations. When you outgrow that, switch
|
|
43
43
|
individual collections to MongoDB without changing any of your pages or forms. The
|
|
44
44
|
compatibility layer is the point.
|
|
@@ -46,18 +46,18 @@
|
|
|
46
46
|
|
|
47
47
|
<hr>
|
|
48
48
|
|
|
49
|
-
<h2>Step 1
|
|
49
|
+
<h2>Step 1 - Create the Collection (your data store)</h2>
|
|
50
50
|
|
|
51
51
|
<p>Open <a href="#/collections">Collections</a> → <strong>New collection</strong>. You'll give it a
|
|
52
52
|
slug (the URL-safe name we use in shortcodes), a title, and a list of fields.</p>
|
|
53
53
|
|
|
54
54
|
<p><strong>The slug matters more than you'd think.</strong> It's what every Form, Action, and shortcode
|
|
55
|
-
uses to refer to this collection
|
|
55
|
+
uses to refer to this collection - so pick a noun and stick with it. <code>jobs</code>,
|
|
56
56
|
<code>applications</code>, <code>contacts</code>, <code>events</code>. Don't pluralise inconsistently
|
|
57
|
-
and don't include the word "data" or "collection" in the slug
|
|
57
|
+
and don't include the word "data" or "collection" in the slug - that's redundant.</p>
|
|
58
58
|
|
|
59
59
|
<p><strong>Fields are where the design lives.</strong> Each field has a <em>type</em>, and the type isn't
|
|
60
|
-
just for show
|
|
60
|
+
just for show - it drives every downstream behaviour:</p>
|
|
61
61
|
|
|
62
62
|
<ul>
|
|
63
63
|
<li><code>text</code> renders a text input on forms and is searchable in the Browser</li>
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
<li><code>multiselect</code> becomes a checkbox group AND a filterable tag chip set</li>
|
|
67
67
|
<li><code>date</code> renders a date picker AND gets a from/to range filter</li>
|
|
68
68
|
<li><code>file</code> renders a file upload with mime/size validation</li>
|
|
69
|
-
<li><code>reference</code> stores a link to another collection's entry
|
|
69
|
+
<li><code>reference</code> stores a link to another collection's entry - auto-renders as a populated dropdown</li>
|
|
70
70
|
</ul>
|
|
71
71
|
|
|
72
72
|
<p>Pick the right type at design time and you get the right form input, the right filter UI, and the
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
|
|
81
81
|
<hr>
|
|
82
82
|
|
|
83
|
-
<h2>Step 2
|
|
83
|
+
<h2>Step 2 - Read the data (display it on a page)</h2>
|
|
84
84
|
|
|
85
85
|
<p>You don't need code to list a Collection on a public page. Drop this in any Markdown page:</p>
|
|
86
86
|
|
|
@@ -89,25 +89,25 @@
|
|
|
89
89
|
fields="company,location,salary,type" /]</code></pre>
|
|
90
90
|
|
|
91
91
|
<p>That single shortcode gives you a server-rendered, cached, SEO-friendly grid of cards. The server
|
|
92
|
-
reads the collection at request time, filters it, sorts it, and produces static HTML
|
|
92
|
+
reads the collection at request time, filters it, sorts it, and produces static HTML - every
|
|
93
93
|
visitor gets the same instant render. The page is cached per role, so a million visitors viewing
|
|
94
94
|
a public page hit the cache and never touch the data store.</p>
|
|
95
95
|
|
|
96
96
|
<p><strong>Five display modes are built in:</strong></p>
|
|
97
97
|
|
|
98
98
|
<ul>
|
|
99
|
-
<li><code>display="table"</code>
|
|
100
|
-
<li><code>display="cards"</code>
|
|
101
|
-
<li><code>display="list"</code>
|
|
102
|
-
<li><code>display="accordion"</code>
|
|
103
|
-
<li><code>display="timeline"</code>
|
|
99
|
+
<li><code>display="table"</code> - sortable, paginated, with a search box</li>
|
|
100
|
+
<li><code>display="cards"</code> - visual grid (default 3 columns; configurable)</li>
|
|
101
|
+
<li><code>display="list"</code> - vertical stack with title + meta</li>
|
|
102
|
+
<li><code>display="accordion"</code> - expandable rows, title visible, body hidden until clicked</li>
|
|
103
|
+
<li><code>display="timeline"</code> - chronological with dates and statuses</li>
|
|
104
104
|
</ul>
|
|
105
105
|
|
|
106
106
|
<p>All five display the same data; you pick the right shape for the page. A directory might use
|
|
107
107
|
<code>cards</code>; an admin overview might use <code>table</code>; a history page might use
|
|
108
108
|
<code>timeline</code>.</p>
|
|
109
109
|
|
|
110
|
-
<h3>Make it interactive
|
|
110
|
+
<h3>Make it interactive - flip on the Browser</h3>
|
|
111
111
|
|
|
112
112
|
<p>Adding any of <code>searchable</code>, <code>filterable</code>, or <code>sortable</code> upgrades the
|
|
113
113
|
static list to a full <strong>Collection Browser</strong>:</p>
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
page-size="9"
|
|
120
120
|
empty="No matching jobs." /]</code></pre>
|
|
121
121
|
|
|
122
|
-
<p>Now the page has a search box, a filter rail down the left side, a sort dropdown, and pagination
|
|
122
|
+
<p>Now the page has a search box, a filter rail down the left side, a sort dropdown, and pagination -
|
|
123
123
|
all generated automatically from the schema you wrote in Step 1. The Browser is the moment your
|
|
124
124
|
app starts to feel like an app rather than a brochure.</p>
|
|
125
125
|
|
|
@@ -131,14 +131,14 @@
|
|
|
131
131
|
|
|
132
132
|
<hr>
|
|
133
133
|
|
|
134
|
-
<h2>Step 3
|
|
134
|
+
<h2>Step 3 - Create new records (the Form)</h2>
|
|
135
135
|
|
|
136
|
-
<p>Open <a href="#/forms">Forms</a> → <strong>New form</strong>. The slug here is the form's identity
|
|
136
|
+
<p>Open <a href="#/forms">Forms</a> → <strong>New form</strong>. The slug here is the form's identity -
|
|
137
137
|
what you embed on a page with <code>[form name="..." /]</code>.</p>
|
|
138
138
|
|
|
139
139
|
<p>Form fields can mirror the collection fields exactly, or they can be a subset. <strong>You almost
|
|
140
140
|
always want a subset.</strong> Fields like <code>status</code> shouldn't appear on a public-facing
|
|
141
|
-
submission form
|
|
141
|
+
submission form - the form should set status to <em>"pending"</em> automatically. Fields like
|
|
142
142
|
<code>internalNotes</code> shouldn't be visible to the submitter at all. The form is your
|
|
143
143
|
audience-facing slice of the collection; design it for who's filling it in.</p>
|
|
144
144
|
|
|
@@ -152,13 +152,13 @@
|
|
|
152
152
|
form fires. That's how the "My applications" view later works without any extra config: you ask
|
|
153
153
|
for "entries created by the current user" and the platform already has that data.</p>
|
|
154
154
|
|
|
155
|
-
<p>Anonymous submissions still work
|
|
155
|
+
<p>Anonymous submissions still work - <code>createdBy</code> is null and the action receives an empty
|
|
156
156
|
user. So one form can serve both anonymous contact-us submissions AND authenticated apply-for-this-job
|
|
157
157
|
submissions; the difference shows up in what the action templates can resolve.</p>
|
|
158
158
|
|
|
159
159
|
<hr>
|
|
160
160
|
|
|
161
|
-
<h2>Step 4
|
|
161
|
+
<h2>Step 4 - Change records over time (the Action)</h2>
|
|
162
162
|
|
|
163
163
|
<p>This is where most no-code platforms top out. An Action is a server-side button: visitors click it
|
|
164
164
|
on a page, the server runs a sequence of steps against the entry they clicked on, and the page
|
|
@@ -166,13 +166,13 @@
|
|
|
166
166
|
|
|
167
167
|
<p>Steps include: <code>updateField</code> (set one field to a new value), <code>deleteEntry</code>
|
|
168
168
|
(remove the entry), <code>createInCollection</code> (write a related entry to another collection
|
|
169
|
-
|
|
169
|
+
- this is how "apply for this job" creates an application without losing the job), <code>email</code>
|
|
170
170
|
(send a notification using the entry's fields as template variables), and <code>webhook</code>
|
|
171
171
|
(POST to an external service).</p>
|
|
172
172
|
|
|
173
173
|
<h3>The transition is the magic word</h3>
|
|
174
174
|
|
|
175
|
-
<p>Actions can declare a <strong>transition</strong>
|
|
175
|
+
<p>Actions can declare a <strong>transition</strong> - "this action moves the entry's status from
|
|
176
176
|
<em>pending</em> to <em>reviewing</em>". That single addition unlocks two huge things:</p>
|
|
177
177
|
|
|
178
178
|
<ol>
|
|
@@ -207,15 +207,15 @@
|
|
|
207
207
|
|
|
208
208
|
<hr>
|
|
209
209
|
|
|
210
|
-
<h2>Step 5
|
|
210
|
+
<h2>Step 5 - Who sees what (Visibility + scope)</h2>
|
|
211
211
|
|
|
212
212
|
<p>Pages have a <code>visibility</code> frontmatter field that decides who can load the page at all.
|
|
213
213
|
It accepts a single role (<em>"editor and everyone above"</em>) or an array of roles
|
|
214
|
-
(<em>"candidates OR employers"</em>). Roles are hierarchical
|
|
214
|
+
(<em>"candidates OR employers"</em>). Roles are hierarchical - higher-privilege roles inherit
|
|
215
215
|
access to lower-privilege gated pages without you adding them explicitly.</p>
|
|
216
216
|
|
|
217
|
-
<p>For per-user data <em>within</em> a page that mixed-role users share
|
|
218
|
-
block on a dashboard that both candidates and employers visit
|
|
217
|
+
<p>For per-user data <em>within</em> a page that mixed-role users share - like a "My applications"
|
|
218
|
+
block on a dashboard that both candidates and employers visit - use <code>scope="mine"</code>:</p>
|
|
219
219
|
|
|
220
220
|
<pre class="code-block"><code>[collection slug="applications" scope="mine"
|
|
221
221
|
display="cards" title-field="jobId"
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
tamper with which user's data they see). Anonymous visitors see a sign-in prompt where the block
|
|
228
228
|
would render.</p>
|
|
229
229
|
|
|
230
|
-
<p>For cross-collection scoping
|
|
230
|
+
<p>For cross-collection scoping - <em>"recruiter sees only applications for jobs they posted"</em> -
|
|
231
231
|
use the <code>reference</code> row-access mode in your action's <code>access.rowLevel</code>. The
|
|
232
232
|
platform resolves the reference to check ownership on the target. <a
|
|
233
233
|
href="/docs/configuration.md" target="_blank">Full row-access reference</a> covers all three
|
|
@@ -235,7 +235,7 @@
|
|
|
235
235
|
|
|
236
236
|
<hr>
|
|
237
237
|
|
|
238
|
-
<h2>Step 6
|
|
238
|
+
<h2>Step 6 - Files, references, and "feels like a real app"</h2>
|
|
239
239
|
|
|
240
240
|
<p>Three field types deserve their own mention because they're where Domma stops looking like a CMS
|
|
241
241
|
and starts looking like a development platform:</p>
|
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
to <code>/content/media/</code> with mime + size validation, then stores a reference object
|
|
245
245
|
<code>{url, name, size, mime}</code> on the entry. Image mimes auto-display as thumbnails on the
|
|
246
246
|
page; PDFs and docs become "download" links. The form switches to multipart submission
|
|
247
|
-
automatically the moment any file input has a selection
|
|
247
|
+
automatically the moment any file input has a selection - no extra config.</p>
|
|
248
248
|
|
|
249
249
|
<p><strong>Reference fields</strong> (<code>type: "reference"</code>) store the id of another
|
|
250
250
|
collection's entry. The form picker becomes a populated dropdown of the target collection's
|
|
@@ -261,32 +261,32 @@
|
|
|
261
261
|
|
|
262
262
|
<hr>
|
|
263
263
|
|
|
264
|
-
<h2>Step 7
|
|
264
|
+
<h2>Step 7 - The Browser tour (advanced reading UI)</h2>
|
|
265
265
|
|
|
266
266
|
<p>We touched on the Browser in Step 2. Here's the rest of what it does for free once you turn it
|
|
267
267
|
on with <code>searchable filterable=... sortable</code>:</p>
|
|
268
268
|
|
|
269
269
|
<ul>
|
|
270
|
-
<li><strong>Faceted filter counts</strong>
|
|
270
|
+
<li><strong>Faceted filter counts</strong> - every filter chip shows the count of entries that
|
|
271
271
|
would match if that option were toggled with current other filters. Standard ecommerce-style
|
|
272
272
|
faceted browsing.</li>
|
|
273
|
-
<li><strong>Empty state with "Clear filters"</strong>
|
|
273
|
+
<li><strong>Empty state with "Clear filters"</strong> - when the user narrows to zero results,
|
|
274
274
|
one click resets and gets them un-stuck.</li>
|
|
275
|
-
<li><strong>Saved searches</strong>
|
|
275
|
+
<li><strong>Saved searches</strong> - dropdown in the header lets users name and recall their
|
|
276
276
|
favourite filter combinations.</li>
|
|
277
|
-
<li><strong>CSV export</strong>
|
|
277
|
+
<li><strong>CSV export</strong> - add <code>exportable</code> and a button generates a CSV of the
|
|
278
278
|
current filtered set.</li>
|
|
279
|
-
<li><strong>Mobile filter drawer</strong>
|
|
279
|
+
<li><strong>Mobile filter drawer</strong> - narrow viewports get a "Filters (n)" button that
|
|
280
280
|
slides the rail in from the left as an overlay.</li>
|
|
281
|
-
<li><strong>URL state sync</strong>
|
|
281
|
+
<li><strong>URL state sync</strong> - every change writes to the URL query string, so deep-links
|
|
282
282
|
and the browser back button work.</li>
|
|
283
|
-
<li><strong>Relevance sort during search</strong>
|
|
283
|
+
<li><strong>Relevance sort during search</strong> - when the search box has a term, results
|
|
284
284
|
re-order by match count with a 3× boost on matches in the title field.</li>
|
|
285
|
-
<li><strong>Keyboard shortcuts</strong>
|
|
285
|
+
<li><strong>Keyboard shortcuts</strong> - <code>/</code> focuses search, <code>←</code>/<code>→</code>
|
|
286
286
|
pages.</li>
|
|
287
|
-
<li><strong>Infinite scroll</strong>
|
|
287
|
+
<li><strong>Infinite scroll</strong> - <code>pagination="scroll"</code> replaces the pager with
|
|
288
288
|
a sentinel that loads more on scroll.</li>
|
|
289
|
-
<li><strong>Server-mode for huge datasets</strong>
|
|
289
|
+
<li><strong>Server-mode for huge datasets</strong> - <code>mode="server"</code> makes every
|
|
290
290
|
change round-trip to the API; the storage adapter (Mongo or File) handles the query;
|
|
291
291
|
authoring stays identical.</li>
|
|
292
292
|
</ul>
|
|
@@ -295,7 +295,7 @@
|
|
|
295
295
|
|
|
296
296
|
<hr>
|
|
297
297
|
|
|
298
|
-
<h2>Pulling it together
|
|
298
|
+
<h2>Pulling it together - the worked example</h2>
|
|
299
299
|
|
|
300
300
|
<p>A <strong>job board</strong> uses every primitive in this tutorial:</p>
|
|
301
301
|
|
|
@@ -308,7 +308,7 @@
|
|
|
308
308
|
<li>A dashboard at <code>/dashboard</code> with <code>visibility: [candidate, employer]</code> and
|
|
309
309
|
two <code>[collection scope="mine"]</code> blocks (one per role)</li>
|
|
310
310
|
<li>Transition actions: <em>start-review</em>, <em>invite-to-interview</em>, <em>make-offer</em>,
|
|
311
|
-
<em>reject</em>, <em>withdraw</em>
|
|
311
|
+
<em>reject</em>, <em>withdraw</em> - each with its own role + state guards</li>
|
|
312
312
|
<li>The candidate dashboard adds <code>transitions</code> and each row gets the right buttons
|
|
313
313
|
for its current status</li>
|
|
314
314
|
<li>Recruiters get <code>access.rowLevel: { mode: 'reference', field: 'jobId', targetCollection: 'jobs' }</code>
|
|
@@ -320,7 +320,7 @@
|
|
|
320
320
|
|
|
321
321
|
<hr>
|
|
322
322
|
|
|
323
|
-
<h2>Skip ahead
|
|
323
|
+
<h2>Skip ahead - scaffold a working CRUD system in one click</h2>
|
|
324
324
|
|
|
325
325
|
<p>Reading is one thing; having a real working subsystem to poke at is another. The CMS ships with
|
|
326
326
|
starter <em>recipes</em> that scaffold a complete Collection + Form + Actions in a single
|
|
@@ -329,7 +329,7 @@
|
|
|
329
329
|
<div id="tutorial-scaffolder-mount" style="margin-top:1rem;"></div>
|
|
330
330
|
|
|
331
331
|
<p class="text-muted" style="margin-top:1rem;font-size:.9rem;">
|
|
332
|
-
Recipes are JSON files under <code>server/services/recipes/</code>
|
|
332
|
+
Recipes are JSON files under <code>server/services/recipes/</code> - copy one as a starting
|
|
333
333
|
point for your own. The <a href="/docs/scaffolding.md" target="_blank">scaffolding docs</a>
|
|
334
334
|
cover the recipe format in full.
|
|
335
335
|
</p>
|
|
@@ -52,16 +52,16 @@ Subject Prefix: [Contact Form]</code></pre>
|
|
|
52
52
|
<li>Select the Action from the dropdown and save.</li>
|
|
53
53
|
</ol>
|
|
54
54
|
<p>The Action runs server-side, after the entry is saved. If it fails (e.g. MongoDB is not
|
|
55
|
-
configured), the submission is still stored
|
|
55
|
+
configured), the submission is still stored - the action failure is non-fatal and logged as a
|
|
56
56
|
warning.</p>
|
|
57
57
|
|
|
58
58
|
<h3>4. Success message vs. redirect</h3>
|
|
59
59
|
<p>After a successful submission the visitor sees one of two things:</p>
|
|
60
60
|
<ul>
|
|
61
|
-
<li><strong>Inline success message</strong>
|
|
61
|
+
<li><strong>Inline success message</strong> - the form is replaced by the text set in
|
|
62
62
|
<em>Settings → Success Message</em>. Good for simple acknowledgements.
|
|
63
63
|
</li>
|
|
64
|
-
<li><strong>Page redirect</strong>
|
|
64
|
+
<li><strong>Page redirect</strong> - the visitor is sent to the URL set in
|
|
65
65
|
<em>Settings → Success Redirect URL</em>. Good for registration flows, checkouts, or when you
|
|
66
66
|
want a full thank-you page with additional content. <strong>Takes priority</strong> if both are
|
|
67
67
|
set.
|
|
@@ -80,7 +80,7 @@ Subject Prefix: [Contact Form]</code></pre>
|
|
|
80
80
|
<li>Execute CMS Action (if set)</li>
|
|
81
81
|
<li>Return success response → client redirects or shows message</li>
|
|
82
82
|
</ol>
|
|
83
|
-
<p>Steps 3
|
|
83
|
+
<p>Steps 3-5 are non-fatal: a failure in any of them is logged as a warning but does not prevent the
|
|
84
84
|
submission from being stored or the success response from being returned.</p>
|
|
85
85
|
|
|
86
86
|
<hr>
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
<hr>
|
|
30
30
|
|
|
31
|
-
<h3>1. plugin.json
|
|
31
|
+
<h3>1. plugin.json - the manifest</h3>
|
|
32
32
|
<p>All fields below are <strong>required</strong>. Missing any will cause the plugin to be skipped on
|
|
33
33
|
startup with a warning in the server log.</p>
|
|
34
34
|
<pre class="code-block"><code>{
|
|
@@ -81,10 +81,10 @@
|
|
|
81
81
|
|
|
82
82
|
<hr>
|
|
83
83
|
|
|
84
|
-
<h3>2. plugin.js
|
|
84
|
+
<h3>2. plugin.js - the Fastify plugin</h3>
|
|
85
85
|
<p>This is the server-side entry point. It must export a default <strong>async function</strong> that
|
|
86
86
|
Fastify will call with <code>(fastify, options)</code>.</p>
|
|
87
|
-
<p>The CMS injects auth middleware through <code>options.auth</code>
|
|
87
|
+
<p>The CMS injects auth middleware through <code>options.auth</code> - always destructure from there
|
|
88
88
|
rather than importing directly.</p>
|
|
89
89
|
|
|
90
90
|
<pre class="code-block"><code>import { getPluginSettings, savePluginState } from '../../server/services/plugins.js';
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
export default async function myPlugin(fastify, options) {
|
|
93
93
|
const { authenticate, requireAdmin } = options.auth;
|
|
94
94
|
|
|
95
|
-
// Public endpoint
|
|
95
|
+
// Public endpoint - no auth needed
|
|
96
96
|
fastify.get('/hello', async () => {
|
|
97
97
|
return { message: 'Hello from my plugin!' };
|
|
98
98
|
});
|
|
@@ -109,11 +109,11 @@ export default async function myPlugin(fastify, options) {
|
|
|
109
109
|
}</code></pre>
|
|
110
110
|
|
|
111
111
|
<p>Routes are registered under the prefix <code>/api/plugins/{name}</code> automatically. You do not
|
|
112
|
-
set the prefix yourself
|
|
112
|
+
set the prefix yourself - it is always locked to your plugin's directory name.</p>
|
|
113
113
|
|
|
114
114
|
<hr>
|
|
115
115
|
|
|
116
|
-
<h3>3. config.js
|
|
116
|
+
<h3>3. config.js - settings defaults</h3>
|
|
117
117
|
<p>Export a plain object of default settings. These are merged with any user overrides stored in
|
|
118
118
|
<code>config/plugins.json</code> when <code>getPluginSettings()</code> is called.</p>
|
|
119
119
|
|
|
@@ -156,7 +156,7 @@ export default async function myPlugin(fastify, options) {
|
|
|
156
156
|
}
|
|
157
157
|
}</code></pre>
|
|
158
158
|
|
|
159
|
-
<p>The view file follows the standard Domma view pattern
|
|
159
|
+
<p>The view file follows the standard Domma view pattern - a <code>templateUrl</code> and an
|
|
160
160
|
<code>onMount($container)</code> function:</p>
|
|
161
161
|
|
|
162
162
|
<pre class="code-block"><code>// admin/views/my-view.js
|
|
@@ -205,7 +205,7 @@ export const myPluginView = {
|
|
|
205
205
|
})();
|
|
206
206
|
</script></code></pre>
|
|
207
207
|
|
|
208
|
-
<p>Snippet paths are validated
|
|
208
|
+
<p>Snippet paths are validated - they must stay within the plugin's own directory. Paths containing
|
|
209
209
|
<code>..</code> are blocked.</p>
|
|
210
210
|
|
|
211
211
|
<hr>
|
|
@@ -213,7 +213,7 @@ export const myPluginView = {
|
|
|
213
213
|
<h3>6. Registering and testing</h3>
|
|
214
214
|
<ol>
|
|
215
215
|
<li>Create the <code>plugins/my-plugin/</code> directory with all three required files.</li>
|
|
216
|
-
<li>Restart the server
|
|
216
|
+
<li>Restart the server - you should see <code>[plugins] Loaded N plugins: …, my-plugin</code> in
|
|
217
217
|
the log.
|
|
218
218
|
</li>
|
|
219
219
|
<li>Go to the <a href="#/plugins">Plugins page</a> and enable your plugin.</li>
|
|
@@ -221,7 +221,7 @@ export const myPluginView = {
|
|
|
221
221
|
<li>Verify your endpoint: <code>GET /api/plugins/my-plugin/hello</code></li>
|
|
222
222
|
</ol>
|
|
223
223
|
|
|
224
|
-
<p class="text-muted" style="font-size:.9rem">Tip: use <code>npm run dev</code> during development
|
|
224
|
+
<p class="text-muted" style="font-size:.9rem">Tip: use <code>npm run dev</code> during development -
|
|
225
225
|
the server restarts automatically on file changes.</p>
|
|
226
226
|
|
|
227
227
|
<hr>
|