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.
- package/CLAUDE.md +5 -3
- package/admin/css/admin.css +1 -1
- package/admin/js/app.js +2 -2
- package/admin/js/lib/action-editor-arrange.js +1 -1
- package/admin/js/lib/api-tokens-arrange.js +2 -2
- package/admin/js/lib/block-editor-arrange.js +1 -1
- package/admin/js/lib/blocks-arrange.js +1 -1
- package/admin/js/lib/collection-entries-arrange.js +1 -1
- package/admin/js/lib/components-arrange.js +1 -1
- package/admin/js/lib/dashboard-arrange.js +1 -1
- package/admin/js/lib/dates.js +1 -0
- package/admin/js/lib/forms-arrange.js +1 -1
- package/admin/js/lib/media-arrange.js +1 -1
- package/admin/js/lib/notifications-arrange.js +1 -1
- package/admin/js/lib/pages-arrange.js +1 -1
- package/admin/js/lib/related.js +1 -1
- package/admin/js/lib/timeline-builder.js +2 -2
- package/admin/js/templates/action-editor.html +6 -5
- package/admin/js/templates/actions-list.html +1 -1
- package/admin/js/templates/contacts.html +1 -1
- package/admin/js/templates/docs/api-actions.html +86 -60
- package/admin/js/templates/docs/api-authentication.html +159 -123
- package/admin/js/templates/docs/api-builder.html +197 -0
- package/admin/js/templates/docs/api-collections.html +199 -259
- package/admin/js/templates/docs/api-external.html +225 -0
- package/admin/js/templates/docs/api-forms.html +268 -0
- package/admin/js/templates/docs/api-layouts.html +70 -45
- package/admin/js/templates/docs/api-media.html +57 -80
- package/admin/js/templates/docs/api-navigation.html +66 -22
- package/admin/js/templates/docs/api-pages.html +109 -129
- package/admin/js/templates/docs/api-plugins.html +123 -61
- package/admin/js/templates/docs/api-scaffold.html +185 -0
- package/admin/js/templates/docs/api-settings.html +72 -64
- package/admin/js/templates/docs/api-users.html +74 -107
- package/admin/js/templates/docs/api-views.html +68 -54
- package/admin/js/templates/docs/components-howto.html +20 -17
- package/admin/js/templates/docs/components-reference.html +13 -16
- package/admin/js/templates/docs/components-rules.html +7 -6
- package/admin/js/templates/docs/components-walkthrough.html +19 -19
- package/admin/js/templates/docs/tutorial-crud.html +71 -40
- package/admin/js/templates/docs/tutorial-forms.html +51 -35
- package/admin/js/templates/docs/tutorial-plugin.html +132 -56
- package/admin/js/templates/docs/usage-actions.html +61 -15
- package/admin/js/templates/docs/usage-collections.html +108 -0
- package/admin/js/templates/docs/usage-cta-shortcode.html +14 -3
- package/admin/js/templates/docs/usage-dconfig.html +0 -3
- package/admin/js/templates/docs/usage-editions.html +213 -0
- package/admin/js/templates/docs/usage-media.html +22 -6
- package/admin/js/templates/docs/usage-navigation.html +74 -18
- package/admin/js/templates/docs/usage-pages.html +60 -20
- package/admin/js/templates/docs/usage-plugins.html +89 -17
- package/admin/js/templates/docs/usage-shortcodes.html +123 -70
- package/admin/js/templates/docs/usage-site-settings.html +50 -18
- package/admin/js/templates/docs/usage-tools.html +73 -0
- package/admin/js/templates/docs/usage-users-roles.html +99 -20
- package/admin/js/templates/docs/usage-views.html +36 -19
- package/admin/js/templates/documentation.html +153 -32
- package/admin/js/templates/page-editor.html +0 -5
- package/admin/js/templates/plugin-guide.html +15 -0
- package/admin/js/templates/plugin-guides.html +21 -0
- package/admin/js/templates/pro-docs.html +53 -234
- package/admin/js/templates/tutorials.html +5 -4
- package/admin/js/views/actions-list.js +3 -3
- package/admin/js/views/analytics.js +5 -5
- package/admin/js/views/api-endpoint-editor.js +2 -2
- package/admin/js/views/block-editor.js +4 -4
- package/admin/js/views/blocks.js +4 -4
- package/admin/js/views/collection-editor.js +4 -4
- package/admin/js/views/collection-entries.js +7 -7
- package/admin/js/views/component-editor.js +2 -2
- package/admin/js/views/contacts.js +22 -20
- package/admin/js/views/context-menu-editor.js +5 -5
- package/admin/js/views/doc-pages.js +1 -1
- package/admin/js/views/form-editor.js +4 -4
- package/admin/js/views/form-submissions.js +2 -2
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/media.js +3 -3
- package/admin/js/views/menu-editor.js +13 -13
- package/admin/js/views/menu-locations.js +2 -2
- package/admin/js/views/my-profile.js +1 -1
- package/admin/js/views/page-editor.js +8 -8
- package/admin/js/views/plugin-guides.js +5 -0
- package/admin/js/views/project-detail.js +2 -2
- package/admin/js/views/project-settings.js +1 -1
- package/admin/js/views/role-editor.js +4 -4
- package/admin/js/views/search.js +2 -2
- package/admin/js/views/seo.js +17 -17
- package/admin/js/views/settings.js +3 -3
- package/admin/js/views/theme.js +3 -3
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/users.js +2 -2
- package/admin/js/views/view-editor.js +1 -1
- package/bin/cli.js +13 -13
- package/bin/lib/node-version.js +29 -0
- package/package.json +1 -1
- package/plugins/_lib/admin/mail/compose-window.js +3 -2
- package/plugins/_lib/admin/mail/reader-view.js +7 -6
- package/plugins/_lib/admin/mail/scheduling.js +4 -2
- package/plugins/_lib/admin/mail/templates.js +4 -4
- package/plugins/_lib/admin/ui/dates.js +85 -0
- package/plugins/blog/CLAUDE.md +31 -22
- package/plugins/blog/admin/views/blog.js +3 -2
- package/plugins/blog/admin/views/comments.js +2 -1
- package/plugins/blog/admin/views/post-editor.js +4 -4
- package/plugins/blog/blocks/blog-card-row.html +1 -1
- package/plugins/blog/blocks/blog-card.html +2 -2
- package/plugins/blog/blocks/blog-post-classic.html +2 -2
- package/plugins/blog/blocks/blog-post-essay.html +2 -2
- package/plugins/blog/blocks/blog-post-feature.html +2 -2
- package/plugins/blog/blocks/blog-post-minimal.html +2 -2
- package/plugins/blog/blocks/blog-post-sidebar.html +2 -2
- package/plugins/blog/blocks/blog-post-split.html +2 -2
- package/plugins/blog/docs/guide.md +205 -0
- package/plugins/blog/lib/layouts.js +3 -3
- package/plugins/blog/lib/page.js +2 -1
- package/plugins/blog/plugin.js +3 -3
- package/plugins/blog/plugin.json +4 -4
- package/plugins/blog/tests/layouts.test.js +6 -0
- package/plugins/feedback/CLAUDE.md +22 -3
- package/plugins/feedback/admin/lib/kit.js +6 -7
- package/plugins/feedback/admin/views/feedback.js +79 -10
- package/plugins/feedback/admin/views/send.js +28 -6
- package/plugins/feedback/docs/guide.md +95 -0
- package/plugins/feedback/lib/receiver.js +9 -2
- package/plugins/feedback/lib/sender.js +3 -2
- package/plugins/feedback/plugin.js +54 -6
- package/plugins/feedback/plugin.json +4 -4
- package/plugins/feedback/tests/api.test.js +74 -2
- package/plugins/free-tier.lock.json +49 -44
- package/plugins/mail-reader/CLAUDE.md +33 -18
- package/plugins/mail-reader/docs/guide.md +147 -0
- package/plugins/mail-reader/plugin.json +1 -1
- package/plugins/security/CLAUDE.md +4 -1
- package/plugins/security/admin/views/security.js +5 -5
- package/plugins/security/docs/guide.md +170 -0
- package/plugins/security/plugin.js +2 -1
- package/plugins/security/plugin.json +2 -1
- package/plugins/shopping-cart/CLAUDE.md +7 -1
- package/plugins/shopping-cart/admin/lib/kit.js +5 -2
- package/plugins/shopping-cart/admin/views/orders.js +4 -4
- package/plugins/shopping-cart/admin/views/overview.js +2 -2
- package/plugins/shopping-cart/docs/guide.md +191 -0
- package/plugins/shopping-cart/lib/render.js +2 -1
- package/plugins/shopping-cart/plugin.json +3 -3
- package/public/js/collection-browser.js +2 -2
- package/public/js/site.js +1 -1
- package/scripts/gen-instance-secret.js +3 -1
- package/scripts/setup.js +3 -1
- package/server/middleware/auth.js +2 -1
- package/server/routes/api/actions.js +47 -27
- package/server/routes/api/blocks.js +2 -1
- package/server/routes/api/collections.js +16 -52
- package/server/routes/api/contacts.js +66 -3
- package/server/routes/api/documentation.js +42 -0
- package/server/routes/api/notifications.js +3 -2
- package/server/routes/api/users.js +10 -6
- package/server/server.js +16 -1
- package/server/services/actions.js +110 -34
- package/server/services/adapterRegistry.js +169 -16
- package/server/services/adapters/FileAdapter.js +25 -0
- package/server/services/adapters/MongoAdapter.js +23 -0
- package/server/services/collections.js +104 -1
- package/server/services/connectionManager.js +12 -0
- package/server/services/dates.js +81 -0
- package/server/services/docs.js +13 -2
- package/server/services/markdown.js +75 -26
- package/server/services/notification-sources.js +6 -5
- package/server/services/passwordReset.js +2 -1
- package/server/services/permissionRegistry.js +3 -2
- package/server/services/pluginGuides.js +255 -0
- package/server/services/pluginInstaller.js +54 -13
- package/server/services/plugins.js +29 -1
- package/server/services/presetCollections.js +31 -5
- package/server/services/renderer.js +2 -2
- package/server/services/sidebarBadges.js +3 -1
- package/server/services/tools.js +4 -2
- package/server/templates/page.html +2 -2
|
@@ -19,13 +19,14 @@
|
|
|
19
19
|
We'll build one component end-to-end: a <strong>star rating</strong> widget you can drop onto any
|
|
20
20
|
page with <code>[component name="star-rating" max="5" value="3" /]</code>. By the end you'll have
|
|
21
21
|
used every section of a <code>.dmc</code> file, reactive state, an event listener, a custom event,
|
|
22
|
-
and the live preview. Follow along in a real <strong>New
|
|
22
|
+
and the live preview. Follow along in a real <strong>New component</strong> editor.
|
|
23
23
|
</p>
|
|
24
24
|
|
|
25
25
|
<hr>
|
|
26
26
|
|
|
27
27
|
<h2>Step 1 - Create the component</h2>
|
|
28
|
-
<p>Open <a href="#/components">Data
|
|
28
|
+
<p>Open <a href="#/components">Data > Components</a>, click <strong>New component</strong> in the banner
|
|
29
|
+
and name it
|
|
29
30
|
<code>star-rating</code>. Remember: lowercase + hyphens, and the name is permanent because it
|
|
30
31
|
becomes the element tag <code><dm-star-rating></code>. The four source tabs
|
|
31
32
|
(<code><template></code>, <code><props></code>, <code><script></code>,
|
|
@@ -47,13 +48,13 @@
|
|
|
47
48
|
|
|
48
49
|
<h2>Step 3 - Write the template</h2>
|
|
49
50
|
<p>We render <code>max</code> stars and mark each as filled if its index is below the current rating.
|
|
50
|
-
Because <code
|
|
51
|
+
Because <code>{{#each}}</code> needs a list, we'll build a <code>stars</code> array in state
|
|
51
52
|
(next step) where each item knows whether it's <code>on</code>. For now, the markup:</p>
|
|
52
53
|
<pre class="code-block"><code><template>
|
|
53
|
-
<div class="dm-stars" role="img" aria-label="
|
|
54
|
-
|
|
55
|
-
<button class="star
|
|
56
|
-
|
|
54
|
+
<div class="dm-stars" role="img" aria-label="{{value}} of {{max}}">
|
|
55
|
+
{{#each stars}}
|
|
56
|
+
<button class="star {{#if on}}on{{/if}}" data-action="rate" data-index="{{n}}">★</button>
|
|
57
|
+
{{/each}}
|
|
57
58
|
</div>
|
|
58
59
|
</template></code></pre>
|
|
59
60
|
<p>Each star is a button carrying its 1-based position in <code>data-index</code>, so a single click
|
|
@@ -68,7 +69,6 @@
|
|
|
68
69
|
<pre class="code-block"><code><script>
|
|
69
70
|
export default {
|
|
70
71
|
data() { return { stars: [], value: 0 }; },
|
|
71
|
-
|
|
72
72
|
methods: {
|
|
73
73
|
// Build the star list for a given rating.
|
|
74
74
|
render(value) {
|
|
@@ -86,7 +86,6 @@ export default {
|
|
|
86
86
|
}));
|
|
87
87
|
}
|
|
88
88
|
},
|
|
89
|
-
|
|
90
89
|
onMount() {
|
|
91
90
|
this.render(this.props.value);
|
|
92
91
|
this.el.shadowRoot.addEventListener('click', (e) => {
|
|
@@ -119,7 +118,7 @@ export default {
|
|
|
119
118
|
<h2>Step 6 - Watch the preview, then save</h2>
|
|
120
119
|
<p>As you typed, the editor recompiled and re-mounted the component in the preview iframe. Toggle
|
|
121
120
|
<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,
|
|
121
|
+
both modes. When it looks right, press <strong>Save</strong> in the banner (or Ctrl+S) - the source compiles before it
|
|
123
122
|
is written, so a typo surfaces as a clear error rather than a broken page.</p>
|
|
124
123
|
|
|
125
124
|
<hr>
|
|
@@ -127,19 +126,20 @@ export default {
|
|
|
127
126
|
<h2>Step 7 - Use it on a page</h2>
|
|
128
127
|
<p>Display-only, in any Markdown page:</p>
|
|
129
128
|
<pre class="code-block"><code>[component name="star-rating" max="5" value="4" readonly="true" /]</code></pre>
|
|
130
|
-
<p>Interactive,
|
|
131
|
-
<pre class="code-block"><code><dm-star-rating max="5" value="0" id="r1"></dm-star-rating>
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
<p>Interactive, as a raw tag with an id:</p>
|
|
130
|
+
<pre class="code-block"><code><dm-star-rating max="5" value="0" id="r1"></dm-star-rating></code></pre>
|
|
131
|
+
<p>A page's content cannot carry a <code><script></code> - the sanitiser strips it - so the code
|
|
132
|
+
that reacts to <code>rating-change</code> belongs in site-wide JavaScript, such as a plugin's
|
|
133
|
+
<code>inject.bodyEnd</code> snippet, or in another component that wraps this one:</p>
|
|
134
|
+
<pre class="code-block"><code>document.getElementById('r1')?.addEventListener('rating-change', (e) => {
|
|
134
135
|
console.log('User picked', e.detail.value);
|
|
135
|
-
})
|
|
136
|
-
</script></code></pre>
|
|
136
|
+
});</code></pre>
|
|
137
137
|
|
|
138
138
|
<hr>
|
|
139
139
|
|
|
140
140
|
<h2>Step 8 - Share it</h2>
|
|
141
|
-
<p>Back on <a href="#/components">Components</a>, the
|
|
142
|
-
gives you <code>star-rating.dmcomponent.json</code> - import that on another Domma site to reuse the
|
|
141
|
+
<p>Back on <a href="#/components">Components</a>, right-click the row and choose
|
|
142
|
+
<span data-icon="download"></span> <strong>Export .dmcomponent.json</strong> - it 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>
|
|
@@ -147,7 +147,7 @@ document.getElementById('r1').addEventListener('rating-change', (e) => {
|
|
|
147
147
|
<h2>What you used</h2>
|
|
148
148
|
<ul>
|
|
149
149
|
<li><strong>Props</strong> with types + defaults, coerced from attributes</li>
|
|
150
|
-
<li><strong>Template</strong> interpolation, <code
|
|
150
|
+
<li><strong>Template</strong> interpolation, <code>{{#each}}</code> and <code>{{#if}}</code></li>
|
|
151
151
|
<li><strong>State</strong> via <code>data()</code> + <code>this.set()</code>, seeded from props in <code>onMount()</code></li>
|
|
152
152
|
<li>A single delegated <strong>shadow-root listener</strong> and <code>data-*</code> dispatch</li>
|
|
153
153
|
<li>A bubbling <strong>CustomEvent</strong> for page-level integration</li>
|
|
@@ -39,16 +39,17 @@
|
|
|
39
39
|
|
|
40
40
|
<div class="alert alert-info">
|
|
41
41
|
<strong>Why not a database?</strong> Collections store as flat JSON files on disk by default -
|
|
42
|
-
no database to install, no SQL to learn, no migrations. When you outgrow that,
|
|
43
|
-
individual collections to MongoDB
|
|
44
|
-
|
|
42
|
+
no database to install, no SQL to learn, no migrations. When you outgrow that, move
|
|
43
|
+
individual collections to MongoDB (a Pro feature) from the collection's <strong>Storage</strong> tab without
|
|
44
|
+
changing any of your pages or forms. The move keeps every entry's id, data and dates, so references and
|
|
45
|
+
ownership still point at the right rows. The compatibility layer is the point.
|
|
45
46
|
</div>
|
|
46
47
|
|
|
47
48
|
<hr>
|
|
48
49
|
|
|
49
50
|
<h2>Step 1 - Create the Collection (your data store)</h2>
|
|
50
51
|
|
|
51
|
-
<p>Open <a href="#/collections">Collections</a>
|
|
52
|
+
<p>Open <a href="#/collections">Data > Collections</a> > <strong>New collection</strong>. You'll give it a
|
|
52
53
|
slug (the URL-safe name we use in shortcodes), a title, and a list of fields.</p>
|
|
53
54
|
|
|
54
55
|
<p><strong>The slug matters more than you'd think.</strong> It's what every Form, Action, and shortcode
|
|
@@ -60,14 +61,18 @@
|
|
|
60
61
|
just for show - it drives every downstream behaviour:</p>
|
|
61
62
|
|
|
62
63
|
<ul>
|
|
63
|
-
<li><code>
|
|
64
|
+
<li><strong>Text</strong> (<code>string</code>) renders a text input on forms and is searchable in the Browser</li>
|
|
64
65
|
<li><code>number</code> renders a number input, gets a min/max range filter automatically</li>
|
|
65
|
-
<li><code>select</code> with options becomes a dropdown both on forms AND in the filter rail</li>
|
|
66
|
-
<li><code>multiselect</code> becomes a checkbox group AND a filterable tag chip set</li>
|
|
66
|
+
<li><strong>Dropdown</strong> (<code>select</code>) with options becomes a dropdown both on forms AND in the filter rail</li>
|
|
67
67
|
<li><code>date</code> renders a date picker AND gets a from/to range filter</li>
|
|
68
|
+
<li><code>multiselect</code> becomes a checkbox group AND a filterable tag chip set</li>
|
|
68
69
|
<li><code>file</code> renders a file upload with mime/size validation</li>
|
|
69
70
|
<li><code>reference</code> stores a link to another collection's entry - auto-renders as a populated dropdown</li>
|
|
70
71
|
</ul>
|
|
72
|
+
<p>The collection editor's Fields tab offers text, email, phone, number, textarea, dropdown, radio buttons,
|
|
73
|
+
checkbox, checkbox group, date, time, URL and hidden. <code>multiselect</code>, <code>file</code>,
|
|
74
|
+
<code>reference</code> and a few others are supported everywhere else but are set by editing the
|
|
75
|
+
collection's <code>content/collections/<slug>/schema.json</code>.</p>
|
|
71
76
|
|
|
72
77
|
<p>Pick the right type at design time and you get the right form input, the right filter UI, and the
|
|
73
78
|
right validation, all for free. Pick <code>text</code> for everything and you have to recreate all
|
|
@@ -93,7 +98,7 @@
|
|
|
93
98
|
visitor gets the same instant render. The page is cached per role, so a million visitors viewing
|
|
94
99
|
a public page hit the cache and never touch the data store.</p>
|
|
95
100
|
|
|
96
|
-
<p><strong>
|
|
101
|
+
<p><strong>Eight display modes are built in:</strong></p>
|
|
97
102
|
|
|
98
103
|
<ul>
|
|
99
104
|
<li><code>display="table"</code> - sortable, paginated, with a search box</li>
|
|
@@ -101,16 +106,20 @@
|
|
|
101
106
|
<li><code>display="list"</code> - vertical stack with title + meta</li>
|
|
102
107
|
<li><code>display="accordion"</code> - expandable rows, title visible, body hidden until clicked</li>
|
|
103
108
|
<li><code>display="timeline"</code> - chronological with dates and statuses</li>
|
|
109
|
+
<li><code>display="carousel"</code> - one slide per entry</li>
|
|
110
|
+
<li><code>display="listgroup"</code> - a compact list-group row per entry</li>
|
|
111
|
+
<li><code>display="block"</code> - each entry through a reusable block template you design</li>
|
|
104
112
|
</ul>
|
|
105
113
|
|
|
106
|
-
<p>
|
|
114
|
+
<p>Visitors can right-click any of them to filter, sort, copy or export (export follows the collection's
|
|
115
|
+
export setting). All eight display the same data; you pick the right shape for the page. A directory might use
|
|
107
116
|
<code>cards</code>; an admin overview might use <code>table</code>; a history page might use
|
|
108
117
|
<code>timeline</code>.</p>
|
|
109
118
|
|
|
110
119
|
<h3>Make it interactive - flip on the Browser</h3>
|
|
111
120
|
|
|
112
|
-
<p>Adding any of <code>searchable</code>, <code>filterable</code>,
|
|
113
|
-
static list to a full <strong>Collection Browser</strong>:</p>
|
|
121
|
+
<p>Adding any of <code>searchable</code>, <code>filterable</code>, <code>sortable</code> or
|
|
122
|
+
<code>paginate</code> upgrades the static list to a full <strong>Collection Browser</strong>:</p>
|
|
114
123
|
|
|
115
124
|
<pre class="code-block"><code>[collection slug="jobs" display="cards" columns="3"
|
|
116
125
|
searchable
|
|
@@ -133,7 +142,7 @@
|
|
|
133
142
|
|
|
134
143
|
<h2>Step 3 - Create new records (the Form)</h2>
|
|
135
144
|
|
|
136
|
-
<p>Open <a href="#/forms">Forms</a>
|
|
145
|
+
<p>Open <a href="#/forms">Data > Forms</a> > <strong>New form</strong>. The slug here is the form's identity -
|
|
137
146
|
what you embed on a page with <code>[form name="..." /]</code>.</p>
|
|
138
147
|
|
|
139
148
|
<p>Form fields can mirror the collection fields exactly, or they can be a subset. <strong>You almost
|
|
@@ -142,8 +151,11 @@
|
|
|
142
151
|
<code>internalNotes</code> shouldn't be visible to the submitter at all. The form is your
|
|
143
152
|
audience-facing slice of the collection; design it for who's filling it in.</p>
|
|
144
153
|
|
|
145
|
-
<p>
|
|
146
|
-
|
|
154
|
+
<p>A form stores each submission in the collection with the same slug as the form, and creates that
|
|
155
|
+
collection from its fields if it does not exist yet. The other way round, every collection you make in the
|
|
156
|
+
admin gets a matching form of its own - so for <code>jobs</code> you usually open the <code>jobs</code>
|
|
157
|
+
form and trim it, rather than starting a new one. Every submission becomes one new entry in that
|
|
158
|
+
collection. Done.</p>
|
|
147
159
|
|
|
148
160
|
<h3>Identity capture (the small thing that makes everything work)</h3>
|
|
149
161
|
|
|
@@ -160,7 +172,9 @@
|
|
|
160
172
|
|
|
161
173
|
<h2>Step 4 - Change records over time (the Action)</h2>
|
|
162
174
|
|
|
163
|
-
<p>This is where most no-code platforms top out.
|
|
175
|
+
<p>This is where most no-code platforms top out. Actions are defined in <a href="#/actions">Data >
|
|
176
|
+
Actions</a> and are stored in MongoDB, so they need a MongoDB connection (a Pro feature); everything
|
|
177
|
+
before this step works without one. An Action is a server-side button: visitors click it
|
|
164
178
|
on a page, the server runs a sequence of steps against the entry they clicked on, and the page
|
|
165
179
|
refreshes.</p>
|
|
166
180
|
|
|
@@ -168,7 +182,8 @@
|
|
|
168
182
|
(remove the entry), <code>createInCollection</code> (write a related entry to another collection
|
|
169
183
|
- this is how "apply for this job" creates an application without losing the job), <code>email</code>
|
|
170
184
|
(send a notification using the entry's fields as template variables), and <code>webhook</code>
|
|
171
|
-
(POST to an external service)
|
|
185
|
+
(POST to an external service), <code>moveToCollection</code> and <code>notify</code> (a notification in the
|
|
186
|
+
admin bell).</p>
|
|
172
187
|
|
|
173
188
|
<h3>The transition is the magic word</h3>
|
|
174
189
|
|
|
@@ -180,8 +195,9 @@
|
|
|
180
195
|
and the server refuses with HTTP 409. The illegal move can't happen, even if someone copies
|
|
181
196
|
and edits the URL.
|
|
182
197
|
</li>
|
|
183
|
-
<li><strong>Per-row UI.</strong> Add <code>transitions</code> to
|
|
184
|
-
|
|
198
|
+
<li><strong>Per-row UI.</strong> Add <code>transitions</code> to an interactive <code>[collection]</code>
|
|
199
|
+
block (one with <code>searchable</code>, <code>sortable</code>, <code>filterable</code> or
|
|
200
|
+
<code>paginate</code>; <code>scope="mine"</code> too) and each row sprouts buttons for exactly the transitions legally available given that row's
|
|
185
201
|
current status AND the viewer's role. A candidate sees "Withdraw" on their pending application;
|
|
186
202
|
an admin sees "Move to reviewing"; a candidate viewing a rejected application sees nothing
|
|
187
203
|
to click. No client-side conditionals; the rules live entirely in the action definitions.
|
|
@@ -199,8 +215,10 @@
|
|
|
199
215
|
]
|
|
200
216
|
}</code></pre>
|
|
201
217
|
|
|
202
|
-
<p>Read that JSON like a sentence: <em>"Anyone with the candidate role
|
|
203
|
-
application as long as it's currently submitted or reviewing."</em>
|
|
218
|
+
<p>Read that JSON like a sentence: <em>"Anyone with the candidate role (or a more senior one) can
|
|
219
|
+
withdraw their own application as long as it's currently submitted or reviewing."</em> When an action
|
|
220
|
+
lists several roles, any one of them is enough. A role name the site does not have admits nobody but the
|
|
221
|
+
level-0 role, and an action that names no roles is for admins (levels 0 and 1). The platform enforces every
|
|
204
222
|
clause: <code>access.roles</code> for the role check, <code>rowLevel.mode: 'owner'</code> so
|
|
205
223
|
candidates can only touch their own entries, <code>transition.from</code> for the status guard.
|
|
206
224
|
Together they're the whole authorisation policy for this one button.</p>
|
|
@@ -209,10 +227,15 @@
|
|
|
209
227
|
|
|
210
228
|
<h2>Step 5 - Who sees what (Visibility + scope)</h2>
|
|
211
229
|
|
|
212
|
-
<p>Pages have a <code>visibility</code>
|
|
213
|
-
It accepts a single role (<
|
|
214
|
-
(<em>"candidates OR employers"</em>). Roles are
|
|
215
|
-
access to
|
|
230
|
+
<p>Pages have a <code>visibility</code> setting (in the page editor, stored in the frontmatter) that
|
|
231
|
+
decides who can load the page at all. It accepts a single role (<code>candidate</code> means <em>"candidate
|
|
232
|
+
and everyone more senior"</em>) or an array of roles (<em>"candidates OR employers"</em>). Roles are
|
|
233
|
+
a ladder by level - more senior roles inherit access to less senior gated pages without you adding them
|
|
234
|
+
explicitly. To keep a page to exactly one role, prefix it with <code>=</code>: <code>=candidate</code>
|
|
235
|
+
admits only people who hold the candidate role (and the level-0 role), not employers or admins. The same
|
|
236
|
+
rules apply to menu items. Roles are managed in <a href="#/roles">System > Roles</a>; a site starts
|
|
237
|
+
with <code>super-admin</code>, <code>admin</code> and <code>user</code>, and plugins can add their
|
|
238
|
+
own.</p>
|
|
216
239
|
|
|
217
240
|
<p>For per-user data <em>within</em> a page that mixed-role users share - like a "My applications"
|
|
218
241
|
block on a dashboard that both candidates and employers visit - use <code>scope="mine"</code>:</p>
|
|
@@ -222,16 +245,20 @@
|
|
|
222
245
|
fields="jobId,status,submittedAt"
|
|
223
246
|
empty="You haven't applied yet." /]</code></pre>
|
|
224
247
|
|
|
225
|
-
<p>The page itself stays cached per role; the per-user block renders
|
|
248
|
+
<p>The page itself stays cached per role; the per-user block renders in the browser via a small hydration
|
|
226
249
|
request that injects <code>createdBy = current-user-id</code> server-side (the client can never
|
|
227
250
|
tamper with which user's data they see). Anonymous visitors see a sign-in prompt where the block
|
|
228
|
-
would render
|
|
251
|
+
would render. Make the block interactive (add <code>paginate</code>, <code>searchable</code>,
|
|
252
|
+
<code>sortable</code> or <code>filterable</code>) and it becomes a Collection Browser of the viewer's own
|
|
253
|
+
rows, so <code>transitions</code> works on it too; the server refuses a transition on anyone else's
|
|
254
|
+
row.</p>
|
|
229
255
|
|
|
230
256
|
<p>For cross-collection scoping - <em>"recruiter sees only applications for jobs they posted"</em> -
|
|
231
257
|
use the <code>reference</code> row-access mode in your action's <code>access.rowLevel</code>. The
|
|
232
|
-
platform resolves the reference to check ownership on the target.
|
|
233
|
-
|
|
234
|
-
|
|
258
|
+
platform resolves the reference to check ownership on the target. The row-access section of
|
|
259
|
+
<code>docs/configuration.md</code> (in the Domma CMS package) covers all three modes
|
|
260
|
+
(<code>owner</code>, <code>field</code>, <code>reference</code>) with worked examples. Saved
|
|
261
|
+
<a href="#/views">Views</a> apply the same row-level rules per viewer.</p>
|
|
235
262
|
|
|
236
263
|
<hr>
|
|
237
264
|
|
|
@@ -251,12 +278,13 @@
|
|
|
251
278
|
entries (showing the <code>displayField</code>); the page display resolves to the readable label
|
|
252
279
|
everywhere; with a <code>linkTemplate</code> the label becomes a clickable link to the target's
|
|
253
280
|
detail page. Validation refuses to save an entry pointing at a non-existent target. Dangling
|
|
254
|
-
references (target deleted later) render as "<em>id</em> (missing)" instead of crashing the page
|
|
281
|
+
references (target deleted later) render as "<em>id</em> (missing)" instead of crashing the page. Imports
|
|
282
|
+
are checked the same way: an imported entry pointing at a missing target is skipped and reported.</p>
|
|
255
283
|
|
|
256
284
|
<p><strong>Status fields</strong> with <code>options</code> + paired <code>transition</code>-bearing
|
|
257
285
|
actions = a workflow. We covered this in Step 4 but it deserves repeating: the combination of a
|
|
258
286
|
typed status field, a few actions with <code>transition.from</code>, and the <code>transitions</code>
|
|
259
|
-
attribute on
|
|
287
|
+
attribute on an interactive <code>[collection]</code> shortcode gives you a real state machine that's enforced
|
|
260
288
|
everywhere (UI, API, audit) with no JavaScript.</p>
|
|
261
289
|
|
|
262
290
|
<hr>
|
|
@@ -264,7 +292,7 @@
|
|
|
264
292
|
<h2>Step 7 - The Browser tour (advanced reading UI)</h2>
|
|
265
293
|
|
|
266
294
|
<p>We touched on the Browser in Step 2. Here's the rest of what it does for free once you turn it
|
|
267
|
-
on with <code>searchable filterable=... sortable</code
|
|
295
|
+
on with <code>searchable filterable=... sortable</code> (or just <code>paginate</code>):</p>
|
|
268
296
|
|
|
269
297
|
<ul>
|
|
270
298
|
<li><strong>Faceted filter counts</strong> - every filter chip shows the count of entries that
|
|
@@ -281,7 +309,7 @@
|
|
|
281
309
|
<li><strong>URL state sync</strong> - every change writes to the URL query string, so deep-links
|
|
282
310
|
and the browser back button work.</li>
|
|
283
311
|
<li><strong>Relevance sort during search</strong> - when the search box has a term, results
|
|
284
|
-
re-order by match count with a
|
|
312
|
+
re-order by match count with a 3x boost on matches in the title field.</li>
|
|
285
313
|
<li><strong>Keyboard shortcuts</strong> - <code>/</code> focuses search, <code>←</code>/<code>→</code>
|
|
286
314
|
pages.</li>
|
|
287
315
|
<li><strong>Infinite scroll</strong> - <code>pagination="scroll"</code> replaces the pager with
|
|
@@ -303,20 +331,22 @@
|
|
|
303
331
|
<li>Two collections: <code>jobs</code> (employer posts roles) and <code>applications</code> (candidates apply)</li>
|
|
304
332
|
<li><code>applications</code> has a <code>reference</code> field <code>jobId</code> pointing at <code>jobs</code></li>
|
|
305
333
|
<li><code>applications</code> has a <code>file</code> field <code>resume</code> for the candidate's PDF</li>
|
|
334
|
+
<li><code>candidate</code> and <code>employer</code> roles, added in System > Roles</li>
|
|
306
335
|
<li>A public <code>[collection slug="jobs"]</code> on <code>/jobs</code> with the Browser turned on</li>
|
|
307
|
-
<li>An apply form that writes to <code>applications</code> + an action that emails HR
|
|
336
|
+
<li>An apply form that writes to <code>applications</code> + an action that emails HR (actions need
|
|
337
|
+
MongoDB)</li>
|
|
308
338
|
<li>A dashboard at <code>/dashboard</code> with <code>visibility: [candidate, employer]</code> and
|
|
309
339
|
two <code>[collection scope="mine"]</code> blocks (one per role)</li>
|
|
310
340
|
<li>Transition actions: <em>start-review</em>, <em>invite-to-interview</em>, <em>make-offer</em>,
|
|
311
341
|
<em>reject</em>, <em>withdraw</em> - each with its own role + state guards</li>
|
|
312
|
-
<li>The candidate dashboard adds <code>transitions</code> and each
|
|
313
|
-
for its current status</li>
|
|
342
|
+
<li>The candidate dashboard's <code>scope="mine"</code> block adds <code>paginate transitions</code> and each
|
|
343
|
+
row gets the right buttons for its current status</li>
|
|
314
344
|
<li>Recruiters get <code>access.rowLevel: { mode: 'reference', field: 'jobId', targetCollection: 'jobs' }</code>
|
|
315
345
|
on their transition actions so they only ever see applications for jobs they posted</li>
|
|
316
346
|
</ol>
|
|
317
347
|
|
|
318
348
|
<p>That's a complete recruitment platform built in pure JSON + Markdown. The full annotated build
|
|
319
|
-
|
|
349
|
+
is <code>docs/recruitment-recipe.md</code> in the Domma CMS package.</p>
|
|
320
350
|
|
|
321
351
|
<hr>
|
|
322
352
|
|
|
@@ -330,13 +360,14 @@
|
|
|
330
360
|
|
|
331
361
|
<p class="text-muted" style="margin-top:1rem;font-size:.9rem;">
|
|
332
362
|
Recipes are JSON files under <code>server/services/recipes/</code> - copy one as a starting
|
|
333
|
-
point for your own.
|
|
334
|
-
|
|
363
|
+
point for your own. <code>docs/scaffolding.md</code> in the Domma CMS package covers the recipe
|
|
364
|
+
format in full. Applying a recipe whose collection, form, action or menu already exists is refused
|
|
365
|
+
(HTTP 409) rather than overwriting it.
|
|
335
366
|
</p>
|
|
336
367
|
|
|
337
368
|
<hr>
|
|
338
369
|
<p class="text-muted" style="font-size:.9rem;">
|
|
339
|
-
Next: <a href="#/tutorials/plugin">Writing a Plugin
|
|
370
|
+
Next: <a href="#/tutorials/plugin">Writing a Plugin</a>
|
|
340
371
|
</p>
|
|
341
372
|
|
|
342
373
|
</div>
|
|
@@ -7,28 +7,33 @@
|
|
|
7
7
|
<div class="col-12">
|
|
8
8
|
<div class="docs-body">
|
|
9
9
|
|
|
10
|
-
<p>Every form in Domma CMS
|
|
10
|
+
<p>Every form in Domma CMS stores each submission as an entry in a collection first. After that it can do
|
|
11
|
+
up to four more things:</p>
|
|
11
12
|
<ol>
|
|
12
13
|
<li>Send an <strong>email notification</strong> to one or more recipients</li>
|
|
13
|
-
<li>
|
|
14
|
-
<li>
|
|
15
|
-
<li>
|
|
14
|
+
<li>Send the submission to a <strong>webhook URL</strong></li>
|
|
15
|
+
<li>Run a <strong>CMS Action</strong> (needs MongoDB)</li>
|
|
16
|
+
<li>Show an inline <strong>success message</strong> or redirect the visitor to a <strong>success page</strong></li>
|
|
16
17
|
</ol>
|
|
17
|
-
<p>These are
|
|
18
|
-
the <strong>Settings</strong> and
|
|
18
|
+
<p>These are set per form. Open any form in <a href="#/forms">Data > Forms</a>: the success message and
|
|
19
|
+
redirect are on the <strong>Settings</strong> tab; email, webhook, CMS Action and spam protection are on the
|
|
20
|
+
<strong>Actions</strong> tab.</p>
|
|
19
21
|
|
|
20
22
|
<hr>
|
|
21
23
|
|
|
22
24
|
<h3>1. Email notification</h3>
|
|
23
|
-
<p>
|
|
24
|
-
one or more comma-separated recipient addresses.
|
|
25
|
-
<a href="#/settings">Site Settings
|
|
25
|
+
<p>On the <strong>Actions</strong> tab, in the <em>Email Action</em> card, tick <em>Send email on submit</em>
|
|
26
|
+
and enter one or more comma-separated recipient addresses. The email goes through the SMTP settings in
|
|
27
|
+
<a href="#/settings">System > Site Settings > Email</a>; without them nothing is delivered.</p>
|
|
26
28
|
|
|
27
29
|
<pre class="code-block"><code>Recipients: admin@example.com, team@example.com
|
|
28
30
|
Subject Prefix: [Contact Form]</code></pre>
|
|
31
|
+
<p>The subject reads <em><prefix> New submission</em>. With no prefix, the form's title in square
|
|
32
|
+
brackets is used.</p>
|
|
29
33
|
|
|
30
34
|
<h3>2. Webhook</h3>
|
|
31
|
-
<p>
|
|
35
|
+
<p>In the <em>Webhook Action</em> card, tick <em>POST to webhook on submit</em>, enter a URL and choose
|
|
36
|
+
<code>POST</code> or <code>PUT</code>. Domma sends this JSON body:</p>
|
|
32
37
|
<pre class="code-block"><code>{
|
|
33
38
|
"form": "enquiries",
|
|
34
39
|
"data": {
|
|
@@ -37,55 +42,66 @@ Subject Prefix: [Contact Form]</code></pre>
|
|
|
37
42
|
"message": "Hello!"
|
|
38
43
|
}
|
|
39
44
|
}</code></pre>
|
|
40
|
-
<p>Use this to integrate with Zapier, Make, Slack, or any HTTP endpoint
|
|
45
|
+
<p>Use this to integrate with Zapier, Make, Slack, or any HTTP endpoint. The URL is not kept in the editor's
|
|
46
|
+
draft, in case it carries a secret token.</p>
|
|
41
47
|
|
|
42
|
-
<h3>3. CMS Action (
|
|
43
|
-
<p>Actions are reusable workflow steps defined in <a href="#/actions">Actions</a>. A single action
|
|
44
|
-
chain
|
|
45
|
-
webhook, or delete
|
|
48
|
+
<h3>3. CMS Action (needs MongoDB)</h3>
|
|
49
|
+
<p>Actions are reusable workflow steps defined in <a href="#/actions">Data > Actions</a>. A single action
|
|
50
|
+
can chain several steps: update a field, move the entry to another collection, create an entry in another
|
|
51
|
+
collection, send an email, call a webhook, raise a notification, or delete the entry. Actions are stored in
|
|
52
|
+
MongoDB, so they need a MongoDB connection.</p>
|
|
46
53
|
<p>To wire an Action to a form:</p>
|
|
47
54
|
<ol>
|
|
48
|
-
<li>Create an Action in <a href="#/actions">Actions</a> targeting the
|
|
49
|
-
|
|
55
|
+
<li>Create an Action in <a href="#/actions">Data > Actions</a> targeting the collection your form stores
|
|
56
|
+
into.</li>
|
|
57
|
+
<li>Open the form in <a href="#/forms">Forms</a> > <strong>Actions</strong> tab > <strong>CMS
|
|
50
58
|
Action</strong> card.
|
|
51
59
|
</li>
|
|
52
|
-
<li>
|
|
60
|
+
<li>Pick it under <em>Action on Submit</em> and save.</li>
|
|
53
61
|
</ol>
|
|
54
|
-
<p>The Action runs server
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
<p>The Action runs on the server after the entry is saved, as the signed-in visitor if there is one (so
|
|
63
|
+
<code>{{user.id}}</code> and <code>{{user.email}}</code> work in its steps). If it fails - for example
|
|
64
|
+
because MongoDB is not connected - the submission is still stored.</p>
|
|
65
|
+
<p>A field's <strong>triggers</strong> (Fields tab) can also run an action, notify the admins or redirect
|
|
66
|
+
after submit, only when the answers meet the trigger's condition.</p>
|
|
57
67
|
|
|
58
68
|
<h3>4. Success message vs. redirect</h3>
|
|
59
69
|
<p>After a successful submission the visitor sees one of two things:</p>
|
|
60
70
|
<ul>
|
|
61
71
|
<li><strong>Inline success message</strong> - the form is replaced by the text set in
|
|
62
|
-
<em>Settings
|
|
72
|
+
<em>Settings > Success Message</em> (default: "Thank you for your submission."). Good for simple
|
|
73
|
+
acknowledgements.
|
|
63
74
|
</li>
|
|
64
75
|
<li><strong>Page redirect</strong> - the visitor is sent to the URL set in
|
|
65
|
-
<em>Settings
|
|
66
|
-
|
|
67
|
-
|
|
76
|
+
<em>Settings > Success Redirect URL</em>. Good for registration flows or a full thank-you page.
|
|
77
|
+
<strong>Takes priority</strong> if both are set, and a trigger's "Redirect after submit" takes priority
|
|
78
|
+
over it. <code>{{entryId}}</code> in the URL is replaced with the new entry's id.
|
|
68
79
|
</li>
|
|
69
80
|
</ul>
|
|
70
81
|
<p>Example: set <em>Success Redirect URL</em> to <code>/thank-you</code> and create a
|
|
71
|
-
<code>thank-you
|
|
82
|
+
<code>thank-you</code> page in <a href="#/pages">Pages</a> with any content you like.</p>
|
|
72
83
|
|
|
73
84
|
<h3>Execution order</h3>
|
|
74
85
|
<p>On every submission, the pipeline runs in this fixed order:</p>
|
|
75
86
|
<ol>
|
|
76
|
-
<li>
|
|
77
|
-
<li>
|
|
78
|
-
<li>
|
|
79
|
-
<li>
|
|
80
|
-
<li>
|
|
81
|
-
<li>
|
|
87
|
+
<li>Spam checks (honeypot and a too-fast submission are accepted silently and not stored)</li>
|
|
88
|
+
<li>Triggers: a "Block submission" trigger refuses it; an "End the form here" trigger decides what is kept</li>
|
|
89
|
+
<li>Field validation (required fields, conditional logic), then the per-minute rate limit</li>
|
|
90
|
+
<li>Store the entry in the collection - if this fails, the visitor is told and nothing else runs</li>
|
|
91
|
+
<li>Send the email (if enabled)</li>
|
|
92
|
+
<li>Call the webhook (if enabled)</li>
|
|
93
|
+
<li>Run the CMS Action (if set)</li>
|
|
94
|
+
<li>Run trigger events: run an action, notify the admins, redirect</li>
|
|
95
|
+
<li>Return success - the browser redirects or shows the message</li>
|
|
82
96
|
</ol>
|
|
83
|
-
<p>Steps
|
|
84
|
-
|
|
97
|
+
<p>Steps 5-8 cannot lose the submission: a failure is logged and raises a warning in
|
|
98
|
+
<a href="#/system/notifications">Notifications</a> ("Form ...: webhook failed"), but the entry is already
|
|
99
|
+
stored and the visitor still sees success. An "End the form here" trigger set not to record stores nothing,
|
|
100
|
+
so no email, webhook or action runs.</p>
|
|
85
101
|
|
|
86
102
|
<hr>
|
|
87
103
|
<p class="text-muted" style="font-size:.9rem;">
|
|
88
|
-
<a href="#/tutorials"
|
|
104
|
+
<a href="#/tutorials">Back to all tutorials</a>
|
|
89
105
|
</p>
|
|
90
106
|
|
|
91
107
|
</div>
|