domma-cms 0.93.0 → 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/admin/css/admin.css +1 -1
- package/admin/js/app.js +2 -2
- package/admin/js/templates/docs/api-actions.html +86 -64
- 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 +68 -38
- 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 +55 -14
- 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/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/doc-pages.js +1 -1
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/plugin-guides.js +5 -0
- package/bin/cli.js +6 -6
- package/package.json +1 -1
- package/plugins/blog/docs/guide.md +205 -0
- package/plugins/blog/plugin.json +1 -1
- package/plugins/feedback/docs/guide.md +95 -0
- package/plugins/feedback/plugin.json +1 -1
- package/plugins/free-tier.lock.json +16 -11
- package/plugins/mail-reader/docs/guide.md +147 -0
- package/plugins/mail-reader/plugin.json +1 -1
- package/plugins/security/docs/guide.md +170 -0
- package/plugins/security/plugin.json +1 -1
- package/plugins/shopping-cart/docs/guide.md +191 -0
- package/plugins/shopping-cart/plugin.json +1 -1
- package/server/routes/api/documentation.js +42 -0
- package/server/server.js +12 -0
- package/server/services/docs.js +13 -2
- package/server/services/pluginGuides.js +255 -0
- package/server/services/plugins.js +8 -0
|
@@ -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
|
|
|
@@ -200,8 +215,10 @@
|
|
|
200
215
|
]
|
|
201
216
|
}</code></pre>
|
|
202
217
|
|
|
203
|
-
<p>Read that JSON like a sentence: <em>"Anyone with the candidate role
|
|
204
|
-
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
|
|
205
222
|
clause: <code>access.roles</code> for the role check, <code>rowLevel.mode: 'owner'</code> so
|
|
206
223
|
candidates can only touch their own entries, <code>transition.from</code> for the status guard.
|
|
207
224
|
Together they're the whole authorisation policy for this one button.</p>
|
|
@@ -210,10 +227,15 @@
|
|
|
210
227
|
|
|
211
228
|
<h2>Step 5 - Who sees what (Visibility + scope)</h2>
|
|
212
229
|
|
|
213
|
-
<p>Pages have a <code>visibility</code>
|
|
214
|
-
It accepts a single role (<
|
|
215
|
-
(<em>"candidates OR employers"</em>). Roles are
|
|
216
|
-
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>
|
|
217
239
|
|
|
218
240
|
<p>For per-user data <em>within</em> a page that mixed-role users share - like a "My applications"
|
|
219
241
|
block on a dashboard that both candidates and employers visit - use <code>scope="mine"</code>:</p>
|
|
@@ -223,16 +245,20 @@
|
|
|
223
245
|
fields="jobId,status,submittedAt"
|
|
224
246
|
empty="You haven't applied yet." /]</code></pre>
|
|
225
247
|
|
|
226
|
-
<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
|
|
227
249
|
request that injects <code>createdBy = current-user-id</code> server-side (the client can never
|
|
228
250
|
tamper with which user's data they see). Anonymous visitors see a sign-in prompt where the block
|
|
229
|
-
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>
|
|
230
255
|
|
|
231
256
|
<p>For cross-collection scoping - <em>"recruiter sees only applications for jobs they posted"</em> -
|
|
232
257
|
use the <code>reference</code> row-access mode in your action's <code>access.rowLevel</code>. The
|
|
233
|
-
platform resolves the reference to check ownership on the target.
|
|
234
|
-
|
|
235
|
-
|
|
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>
|
|
236
262
|
|
|
237
263
|
<hr>
|
|
238
264
|
|
|
@@ -252,12 +278,13 @@
|
|
|
252
278
|
entries (showing the <code>displayField</code>); the page display resolves to the readable label
|
|
253
279
|
everywhere; with a <code>linkTemplate</code> the label becomes a clickable link to the target's
|
|
254
280
|
detail page. Validation refuses to save an entry pointing at a non-existent target. Dangling
|
|
255
|
-
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>
|
|
256
283
|
|
|
257
284
|
<p><strong>Status fields</strong> with <code>options</code> + paired <code>transition</code>-bearing
|
|
258
285
|
actions = a workflow. We covered this in Step 4 but it deserves repeating: the combination of a
|
|
259
286
|
typed status field, a few actions with <code>transition.from</code>, and the <code>transitions</code>
|
|
260
|
-
attribute on
|
|
287
|
+
attribute on an interactive <code>[collection]</code> shortcode gives you a real state machine that's enforced
|
|
261
288
|
everywhere (UI, API, audit) with no JavaScript.</p>
|
|
262
289
|
|
|
263
290
|
<hr>
|
|
@@ -265,7 +292,7 @@
|
|
|
265
292
|
<h2>Step 7 - The Browser tour (advanced reading UI)</h2>
|
|
266
293
|
|
|
267
294
|
<p>We touched on the Browser in Step 2. Here's the rest of what it does for free once you turn it
|
|
268
|
-
on with <code>searchable filterable=... sortable</code
|
|
295
|
+
on with <code>searchable filterable=... sortable</code> (or just <code>paginate</code>):</p>
|
|
269
296
|
|
|
270
297
|
<ul>
|
|
271
298
|
<li><strong>Faceted filter counts</strong> - every filter chip shows the count of entries that
|
|
@@ -282,7 +309,7 @@
|
|
|
282
309
|
<li><strong>URL state sync</strong> - every change writes to the URL query string, so deep-links
|
|
283
310
|
and the browser back button work.</li>
|
|
284
311
|
<li><strong>Relevance sort during search</strong> - when the search box has a term, results
|
|
285
|
-
re-order by match count with a
|
|
312
|
+
re-order by match count with a 3x boost on matches in the title field.</li>
|
|
286
313
|
<li><strong>Keyboard shortcuts</strong> - <code>/</code> focuses search, <code>←</code>/<code>→</code>
|
|
287
314
|
pages.</li>
|
|
288
315
|
<li><strong>Infinite scroll</strong> - <code>pagination="scroll"</code> replaces the pager with
|
|
@@ -304,20 +331,22 @@
|
|
|
304
331
|
<li>Two collections: <code>jobs</code> (employer posts roles) and <code>applications</code> (candidates apply)</li>
|
|
305
332
|
<li><code>applications</code> has a <code>reference</code> field <code>jobId</code> pointing at <code>jobs</code></li>
|
|
306
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>
|
|
307
335
|
<li>A public <code>[collection slug="jobs"]</code> on <code>/jobs</code> with the Browser turned on</li>
|
|
308
|
-
<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>
|
|
309
338
|
<li>A dashboard at <code>/dashboard</code> with <code>visibility: [candidate, employer]</code> and
|
|
310
339
|
two <code>[collection scope="mine"]</code> blocks (one per role)</li>
|
|
311
340
|
<li>Transition actions: <em>start-review</em>, <em>invite-to-interview</em>, <em>make-offer</em>,
|
|
312
341
|
<em>reject</em>, <em>withdraw</em> - each with its own role + state guards</li>
|
|
313
|
-
<li>The candidate dashboard adds <code>transitions</code> and each
|
|
314
|
-
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>
|
|
315
344
|
<li>Recruiters get <code>access.rowLevel: { mode: 'reference', field: 'jobId', targetCollection: 'jobs' }</code>
|
|
316
345
|
on their transition actions so they only ever see applications for jobs they posted</li>
|
|
317
346
|
</ol>
|
|
318
347
|
|
|
319
348
|
<p>That's a complete recruitment platform built in pure JSON + Markdown. The full annotated build
|
|
320
|
-
|
|
349
|
+
is <code>docs/recruitment-recipe.md</code> in the Domma CMS package.</p>
|
|
321
350
|
|
|
322
351
|
<hr>
|
|
323
352
|
|
|
@@ -331,13 +360,14 @@
|
|
|
331
360
|
|
|
332
361
|
<p class="text-muted" style="margin-top:1rem;font-size:.9rem;">
|
|
333
362
|
Recipes are JSON files under <code>server/services/recipes/</code> - copy one as a starting
|
|
334
|
-
point for your own.
|
|
335
|
-
|
|
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.
|
|
336
366
|
</p>
|
|
337
367
|
|
|
338
368
|
<hr>
|
|
339
369
|
<p class="text-muted" style="font-size:.9rem;">
|
|
340
|
-
Next: <a href="#/tutorials/plugin">Writing a Plugin
|
|
370
|
+
Next: <a href="#/tutorials/plugin">Writing a Plugin</a>
|
|
341
371
|
</p>
|
|
342
372
|
|
|
343
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>
|