domma-cms 0.9.0 → 0.9.5
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/js/templates/block-editor.html +163 -163
- package/admin/js/templates/form-editor.html +245 -245
- package/admin/js/views/action-editor.js +1 -1
- package/admin/js/views/block-editor.js +8 -8
- package/admin/js/views/collection-editor.js +4 -4
- package/admin/js/views/collections.js +1 -1
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/forms.js +1 -1
- package/admin/js/views/navigation.js +14 -14
- package/admin/js/views/page-editor.js +35 -35
- package/admin/js/views/pages.js +5 -5
- package/admin/js/views/plugins.js +19 -10
- package/admin/js/views/view-editor.js +1 -1
- package/config/plugins.json +35 -0
- package/package.json +1 -1
- package/plugins/docs/data/documents/57e003f0-68f2-47dc-9c36-ed4b10ed3deb.json +4 -4
- package/plugins/docs/data/folders.json +3 -3
- package/plugins/docs/data/versions/57e003f0-68f2-47dc-9c36-ed4b10ed3deb/1.json +5 -0
- package/plugins/garage/admin/templates/garage.html +30 -0
- package/plugins/garage/admin/views/garage.js +62 -1
- package/plugins/garage/plugin.json +1 -1
- package/plugins/notes/admin/templates/notes.html +2 -11
- package/plugins/notes/admin/views/notes.js +107 -129
- package/plugins/notes/collections/user-notes/schema.json +2 -1
- package/plugins/notes/plugin.json +1 -1
- package/plugins/site-search/admin/templates/site-search.html +174 -46
- package/plugins/site-search/admin/views/site-search.js +72 -1
- package/plugins/site-search/config.js +6 -1
- package/plugins/site-search/plugin.json +1 -1
- package/plugins/site-search/public/inject-head.html +1 -1
- package/plugins/site-search/public/search.css +1 -1
- package/plugins/site-search/public/search.js +1 -1
- package/plugins/todo/admin/templates/todo.html +2 -8
- package/plugins/todo/admin/views/todo.js +122 -106
- package/plugins/todo/collections/todos/schema.json +2 -1
- package/plugins/todo/plugin.json +1 -1
- package/server/routes/api/media.js +127 -118
- package/server/routes/api/plugins.js +15 -4
- package/server/server.js +288 -285
- package/server/services/blocks.js +6 -3
- package/server/services/collections.js +17 -10
- package/server/services/plugins.js +77 -67
- package/server/services/renderer.js +3 -3
- package/plugins/docs/data/documents/452f49b7-9c93-4a67-874d-27f882891ad2.json +0 -11
|
@@ -1,245 +1,245 @@
|
|
|
1
|
-
<div class="view-header">
|
|
2
|
-
<h1 id="editor-title"><span data-icon="edit-3"></span> Form Editor</h1>
|
|
3
|
-
<div style="display:flex;gap:.5rem;align-items:center;">
|
|
4
|
-
<a href="#/forms" class="btn btn-ghost btn-sm">
|
|
5
|
-
<span data-icon="arrow-left"></span> All Forms
|
|
6
|
-
</a>
|
|
7
|
-
<button id="preview-btn" class="btn btn-ghost btn-sm">
|
|
8
|
-
<span data-icon="eye"></span> Preview
|
|
9
|
-
</button>
|
|
10
|
-
<button id="save-form-btn" class="btn btn-primary">
|
|
11
|
-
<span data-icon="save"></span> Save
|
|
12
|
-
</button>
|
|
13
|
-
</div>
|
|
14
|
-
</div>
|
|
15
|
-
|
|
16
|
-
<div class="tabs" id="editor-tabs">
|
|
17
|
-
<div class="tab-list">
|
|
18
|
-
<button class="tab-item active">Fields</button>
|
|
19
|
-
<button class="tab-item">Form Details</button>
|
|
20
|
-
<button class="tab-item">Settings</button>
|
|
21
|
-
<button class="tab-item">Actions</button>
|
|
22
|
-
</div>
|
|
23
|
-
<div class="tab-content">
|
|
24
|
-
|
|
25
|
-
<!-- Tab 1: Fields -->
|
|
26
|
-
<div class="tab-panel active">
|
|
27
|
-
<div class="card mb-4">
|
|
28
|
-
<div class="card-header" style="display:flex;justify-content:space-between;align-items:center;">
|
|
29
|
-
<h2>Fields</h2>
|
|
30
|
-
<div id="add-element-dropdown" style="position:relative;">
|
|
31
|
-
<button id="add-element-btn" class="btn btn-ghost btn-sm" type="button">
|
|
32
|
-
<span data-icon="plus"></span> Add <span
|
|
33
|
-
style="font-size:.65rem;opacity:.6;margin-left:.1rem;">▾</span>
|
|
34
|
-
</button>
|
|
35
|
-
<div id="add-element-menu"
|
|
36
|
-
style="display:none;position:absolute;right:0;top:calc(100% + .2rem);background:var(--card-bg,#1e1e2e);border:1px solid var(--border-color,#333);border-radius:6px;min-width:140px;z-index:100;box-shadow:0 4px 16px rgba(0,0,0,.3);overflow:hidden;">
|
|
37
|
-
<button id="add-field-btn" type="button"
|
|
38
|
-
style="display:block;width:100%;text-align:left;padding:.5rem .85rem;background:none;border:none;color:inherit;cursor:pointer;font-size:.875rem;border-bottom:1px solid var(--border-color,#333);">
|
|
39
|
-
Field
|
|
40
|
-
</button>
|
|
41
|
-
<button id="add-spacer-btn" type="button"
|
|
42
|
-
style="display:block;width:100%;text-align:left;padding:.5rem .85rem;background:none;border:none;color:inherit;cursor:pointer;font-size:.875rem;border-bottom:1px solid var(--border-color,#333);">
|
|
43
|
-
Spacer
|
|
44
|
-
</button>
|
|
45
|
-
<button id="add-page-break-btn" type="button"
|
|
46
|
-
style="display:block;width:100%;text-align:left;padding:.5rem .85rem;background:none;border:none;color:inherit;cursor:pointer;font-size:.875rem;">
|
|
47
|
-
Page Break
|
|
48
|
-
</button>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
<div class="card-body" style="padding:0;">
|
|
53
|
-
<div id="fields-list" style="padding:1rem;">
|
|
54
|
-
<p class="text-muted" id="fields-empty-msg" style="text-align:center;padding:2rem 0;">No fields yet. Click
|
|
55
|
-
"Add Field" to get started.</p>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
|
|
60
|
-
<div class="card mb-4" id="preview-card" style="display:none;">
|
|
61
|
-
<div class="card-header" style="display:flex;justify-content:space-between;align-items:center;">
|
|
62
|
-
<h2><span data-icon="eye"></span> Preview</h2>
|
|
63
|
-
<span id="preview-test-badge"
|
|
64
|
-
style="display:none;font-size:.75rem;padding:.2rem .6rem;border-radius:999px;background:rgba(99,102,241,.15);color:var(--primary,#6366f1);">Test Mode — submissions are stored</span>
|
|
65
|
-
</div>
|
|
66
|
-
<div class="card-body">
|
|
67
|
-
<div id="preview-container"></div>
|
|
68
|
-
<div id="preview-test-result"
|
|
69
|
-
style="display:none;margin-top:.75rem;padding:.6rem .9rem;border-radius:6px;font-size:.9rem;"></div>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
|
|
74
|
-
<!-- Tab 2: Form Details -->
|
|
75
|
-
<div class="tab-panel">
|
|
76
|
-
<div class="card mb-4">
|
|
77
|
-
<div class="card-header"><h2>Form Details</h2></div>
|
|
78
|
-
<div class="card-body">
|
|
79
|
-
<div class="row mb-3">
|
|
80
|
-
<div class="col-7">
|
|
81
|
-
<label class="form-label">Title</label>
|
|
82
|
-
<input id="field-title" type="text" class="form-input" placeholder="My Form">
|
|
83
|
-
</div>
|
|
84
|
-
<div class="col-5">
|
|
85
|
-
<label class="form-label">Slug</label>
|
|
86
|
-
<input id="field-slug" type="text" class="form-input" placeholder="my-form">
|
|
87
|
-
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">Used in embed: <code>data-form="slug"</code>
|
|
88
|
-
</p>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
<div class="row">
|
|
92
|
-
<div class="col">
|
|
93
|
-
<label class="form-label">Description</label>
|
|
94
|
-
<textarea id="field-description" class="form-input" rows="2"
|
|
95
|
-
placeholder="Optional form description..."></textarea>
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
<!-- Tab 3: Settings -->
|
|
110
|
-
<div class="tab-panel">
|
|
111
|
-
<div class="card mb-4">
|
|
112
|
-
<div class="card-header"><h2>Settings</h2></div>
|
|
113
|
-
<div class="card-body">
|
|
114
|
-
<div class="mb-3">
|
|
115
|
-
<label class="form-label">Submit Button Text</label>
|
|
116
|
-
<input id="setting-submit-text" type="text" class="form-input" placeholder="Submit" value="Submit">
|
|
117
|
-
</div>
|
|
118
|
-
<div class="mb-3">
|
|
119
|
-
<label class="form-label">Success Message</label>
|
|
120
|
-
<textarea id="setting-success-message" class="form-input" rows="3"
|
|
121
|
-
placeholder="Thank you for your submission."></textarea>
|
|
122
|
-
</div>
|
|
123
|
-
<div class="mb-3">
|
|
124
|
-
<label class="form-label">Success Redirect URL</label>
|
|
125
|
-
<input id="setting-success-redirect" type="text" class="form-input"
|
|
126
|
-
placeholder="/thank-you">
|
|
127
|
-
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">
|
|
128
|
-
If set, redirects the visitor to this URL on success. Takes priority over the success message.
|
|
129
|
-
</p>
|
|
130
|
-
</div>
|
|
131
|
-
<div class="mb-3">
|
|
132
|
-
<label class="form-label">Layout</label>
|
|
133
|
-
<select id="setting-layout" class="form-input">
|
|
134
|
-
<option value="stacked">Stacked</option>
|
|
135
|
-
<option value="inline">Inline</option>
|
|
136
|
-
<option value="grid">Grid</option>
|
|
137
|
-
</select>
|
|
138
|
-
</div>
|
|
139
|
-
<div id="columns-group" style="display:none;">
|
|
140
|
-
<label class="form-label">Columns</label>
|
|
141
|
-
<input id="setting-columns" type="number" class="form-input" min="1" max="6" value="2">
|
|
142
|
-
<div class="mt-3">
|
|
143
|
-
<label class="form-label">Submit Button Span</label>
|
|
144
|
-
<select id="setting-submit-span" class="form-input">
|
|
145
|
-
<option value="">Auto (1 column)</option>
|
|
146
|
-
<option value="full">Full width</option>
|
|
147
|
-
</select>
|
|
148
|
-
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">Controls how wide the submit
|
|
149
|
-
button is in grid layout.</p>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
<!-- Tab 4: Actions -->
|
|
157
|
-
<div class="tab-panel">
|
|
158
|
-
<div class="row">
|
|
159
|
-
<div class="col-6">
|
|
160
|
-
<div class="card mb-4">
|
|
161
|
-
<div class="card-header"><h2>Email Action</h2></div>
|
|
162
|
-
<div class="card-body">
|
|
163
|
-
<div class="mb-3">
|
|
164
|
-
<label class="form-label" style="display:flex;align-items:center;gap:.5rem;cursor:pointer;">
|
|
165
|
-
<input id="action-email-enabled" type="checkbox"> Send email on submit
|
|
166
|
-
</label>
|
|
167
|
-
</div>
|
|
168
|
-
<div class="mb-3">
|
|
169
|
-
<label class="form-label">Recipients</label>
|
|
170
|
-
<input id="action-email-recipients" type="text" class="form-input" placeholder="admin@example.com">
|
|
171
|
-
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">Comma-separated. Uses global
|
|
172
|
-
SMTP settings.</p>
|
|
173
|
-
</div>
|
|
174
|
-
<div>
|
|
175
|
-
<label class="form-label">Subject Prefix</label>
|
|
176
|
-
<input id="action-email-subject-prefix" type="text" class="form-input" placeholder="[Form]">
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
181
|
-
<div class="col-6">
|
|
182
|
-
<div class="card mb-4">
|
|
183
|
-
<div class="card-header"><h2>Webhook Action</h2></div>
|
|
184
|
-
<div class="card-body">
|
|
185
|
-
<div class="mb-3">
|
|
186
|
-
<label class="form-label" style="display:flex;align-items:center;gap:.5rem;cursor:pointer;">
|
|
187
|
-
<input id="action-webhook-enabled" type="checkbox"> POST to webhook on submit
|
|
188
|
-
</label>
|
|
189
|
-
</div>
|
|
190
|
-
<div class="mb-3">
|
|
191
|
-
<label class="form-label">URL</label>
|
|
192
|
-
<input id="action-webhook-url" type="url" class="form-input"
|
|
193
|
-
placeholder="https://hooks.example.com/form">
|
|
194
|
-
</div>
|
|
195
|
-
<div>
|
|
196
|
-
<label class="form-label">Method</label>
|
|
197
|
-
<select id="action-webhook-method" class="form-input">
|
|
198
|
-
<option value="POST">POST</option>
|
|
199
|
-
<option value="PUT">PUT</option>
|
|
200
|
-
</select>
|
|
201
|
-
</div>
|
|
202
|
-
</div>
|
|
203
|
-
</div>
|
|
204
|
-
</div>
|
|
205
|
-
<div class="col-6">
|
|
206
|
-
<div class="card mb-4">
|
|
207
|
-
<div class="card-header"><h2>CMS Action</h2></div>
|
|
208
|
-
<div class="card-body">
|
|
209
|
-
<div class="mb-3">
|
|
210
|
-
<label class="form-label">Action on Submit</label>
|
|
211
|
-
<select id="action-cms-slug" class="form-input">
|
|
212
|
-
<option value="">— None —</option>
|
|
213
|
-
</select>
|
|
214
|
-
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">
|
|
215
|
-
Run a CMS Action after the entry is stored. Requires Pro (MongoDB).
|
|
216
|
-
Failures are non-fatal — the submission is always saved first.
|
|
217
|
-
</p>
|
|
218
|
-
</div>
|
|
219
|
-
</div>
|
|
220
|
-
</div>
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
|
-
|
|
224
|
-
<div class="card mb-4">
|
|
225
|
-
<div class="card-header"><h2>Spam Protection</h2></div>
|
|
226
|
-
<div class="card-body">
|
|
227
|
-
<div class="mb-3">
|
|
228
|
-
<label class="form-label" style="display:flex;align-items:center;gap:.5rem;cursor:pointer;">
|
|
229
|
-
<input id="setting-honeypot" type="checkbox" checked> Enable honeypot field
|
|
230
|
-
</label>
|
|
231
|
-
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">Silently discards bot submissions
|
|
232
|
-
that fill a hidden field.</p>
|
|
233
|
-
</div>
|
|
234
|
-
<div>
|
|
235
|
-
<label class="form-label">Rate Limit (per minute)</label>
|
|
236
|
-
<input id="setting-rate-limit" type="number" class="form-input" min="1" max="60" value="3">
|
|
237
|
-
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">Max submissions per IP per
|
|
238
|
-
minute.</p>
|
|
239
|
-
</div>
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
|
-
</div>
|
|
243
|
-
|
|
244
|
-
</div>
|
|
245
|
-
</div>
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1 id="editor-title"><span data-icon="edit-3"></span> Form Editor</h1>
|
|
3
|
+
<div style="display:flex;gap:.5rem;align-items:center;">
|
|
4
|
+
<a href="#/forms" class="btn btn-ghost btn-sm">
|
|
5
|
+
<span data-icon="arrow-left"></span> All Forms
|
|
6
|
+
</a>
|
|
7
|
+
<button id="preview-btn" class="btn btn-ghost btn-sm">
|
|
8
|
+
<span data-icon="eye"></span> Preview
|
|
9
|
+
</button>
|
|
10
|
+
<button id="save-form-btn" class="btn btn-primary">
|
|
11
|
+
<span data-icon="save"></span> Save
|
|
12
|
+
</button>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div class="tabs" id="editor-tabs">
|
|
17
|
+
<div class="tab-list">
|
|
18
|
+
<button class="tab-item active">Fields</button>
|
|
19
|
+
<button class="tab-item">Form Details</button>
|
|
20
|
+
<button class="tab-item">Settings</button>
|
|
21
|
+
<button class="tab-item">Actions</button>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="tab-content">
|
|
24
|
+
|
|
25
|
+
<!-- Tab 1: Fields -->
|
|
26
|
+
<div class="tab-panel active">
|
|
27
|
+
<div class="card mb-4">
|
|
28
|
+
<div class="card-header" style="display:flex;justify-content:space-between;align-items:center;">
|
|
29
|
+
<h2>Fields</h2>
|
|
30
|
+
<div id="add-element-dropdown" style="position:relative;">
|
|
31
|
+
<button id="add-element-btn" class="btn btn-ghost btn-sm" type="button">
|
|
32
|
+
<span data-icon="plus"></span> Add <span
|
|
33
|
+
style="font-size:.65rem;opacity:.6;margin-left:.1rem;">▾</span>
|
|
34
|
+
</button>
|
|
35
|
+
<div id="add-element-menu"
|
|
36
|
+
style="display:none;position:absolute;right:0;top:calc(100% + .2rem);background:var(--card-bg,#1e1e2e);border:1px solid var(--border-color,#333);border-radius:6px;min-width:140px;z-index:100;box-shadow:0 4px 16px rgba(0,0,0,.3);overflow:hidden;">
|
|
37
|
+
<button id="add-field-btn" type="button"
|
|
38
|
+
style="display:block;width:100%;text-align:left;padding:.5rem .85rem;background:none;border:none;color:inherit;cursor:pointer;font-size:.875rem;border-bottom:1px solid var(--border-color,#333);">
|
|
39
|
+
Field
|
|
40
|
+
</button>
|
|
41
|
+
<button id="add-spacer-btn" type="button"
|
|
42
|
+
style="display:block;width:100%;text-align:left;padding:.5rem .85rem;background:none;border:none;color:inherit;cursor:pointer;font-size:.875rem;border-bottom:1px solid var(--border-color,#333);">
|
|
43
|
+
Spacer
|
|
44
|
+
</button>
|
|
45
|
+
<button id="add-page-break-btn" type="button"
|
|
46
|
+
style="display:block;width:100%;text-align:left;padding:.5rem .85rem;background:none;border:none;color:inherit;cursor:pointer;font-size:.875rem;">
|
|
47
|
+
Page Break
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="card-body" style="padding:0;">
|
|
53
|
+
<div id="fields-list" style="padding:1rem;">
|
|
54
|
+
<p class="text-muted" id="fields-empty-msg" style="text-align:center;padding:2rem 0;">No fields yet. Click
|
|
55
|
+
"Add Field" to get started.</p>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div class="card mb-4" id="preview-card" style="display:none;">
|
|
61
|
+
<div class="card-header" style="display:flex;justify-content:space-between;align-items:center;">
|
|
62
|
+
<h2><span data-icon="eye"></span> Preview</h2>
|
|
63
|
+
<span id="preview-test-badge"
|
|
64
|
+
style="display:none;font-size:.75rem;padding:.2rem .6rem;border-radius:999px;background:rgba(99,102,241,.15);color:var(--primary,#6366f1);">Test Mode — submissions are stored</span>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="card-body">
|
|
67
|
+
<div id="preview-container"></div>
|
|
68
|
+
<div id="preview-test-result"
|
|
69
|
+
style="display:none;margin-top:.75rem;padding:.6rem .9rem;border-radius:6px;font-size:.9rem;"></div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<!-- Tab 2: Form Details -->
|
|
75
|
+
<div class="tab-panel">
|
|
76
|
+
<div class="card mb-4">
|
|
77
|
+
<div class="card-header"><h2>Form Details</h2></div>
|
|
78
|
+
<div class="card-body">
|
|
79
|
+
<div class="row mb-3">
|
|
80
|
+
<div class="col-7">
|
|
81
|
+
<label class="form-label">Title</label>
|
|
82
|
+
<input id="field-title" type="text" class="form-input" placeholder="My Form">
|
|
83
|
+
</div>
|
|
84
|
+
<div class="col-5">
|
|
85
|
+
<label class="form-label">Slug</label>
|
|
86
|
+
<input id="field-slug" type="text" class="form-input" placeholder="my-form">
|
|
87
|
+
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">Used in embed: <code>data-form="slug"</code>
|
|
88
|
+
</p>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="row">
|
|
92
|
+
<div class="col">
|
|
93
|
+
<label class="form-label">Description</label>
|
|
94
|
+
<textarea id="field-description" class="form-input" rows="2"
|
|
95
|
+
placeholder="Optional form description..."></textarea>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="row mt-3">
|
|
99
|
+
<div class="col-auto">
|
|
100
|
+
<label class="form-check-label" title="Included in fresh installs via the seed script">
|
|
101
|
+
<input id="field-bundled" type="checkbox" class="form-check"> Bundled
|
|
102
|
+
</label>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<!-- Tab 3: Settings -->
|
|
110
|
+
<div class="tab-panel">
|
|
111
|
+
<div class="card mb-4">
|
|
112
|
+
<div class="card-header"><h2>Settings</h2></div>
|
|
113
|
+
<div class="card-body">
|
|
114
|
+
<div class="mb-3">
|
|
115
|
+
<label class="form-label">Submit Button Text</label>
|
|
116
|
+
<input id="setting-submit-text" type="text" class="form-input" placeholder="Submit" value="Submit">
|
|
117
|
+
</div>
|
|
118
|
+
<div class="mb-3">
|
|
119
|
+
<label class="form-label">Success Message</label>
|
|
120
|
+
<textarea id="setting-success-message" class="form-input" rows="3"
|
|
121
|
+
placeholder="Thank you for your submission."></textarea>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="mb-3">
|
|
124
|
+
<label class="form-label">Success Redirect URL</label>
|
|
125
|
+
<input id="setting-success-redirect" type="text" class="form-input"
|
|
126
|
+
placeholder="/thank-you">
|
|
127
|
+
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">
|
|
128
|
+
If set, redirects the visitor to this URL on success. Takes priority over the success message.
|
|
129
|
+
</p>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="mb-3">
|
|
132
|
+
<label class="form-label">Layout</label>
|
|
133
|
+
<select id="setting-layout" class="form-input">
|
|
134
|
+
<option value="stacked">Stacked</option>
|
|
135
|
+
<option value="inline">Inline</option>
|
|
136
|
+
<option value="grid">Grid</option>
|
|
137
|
+
</select>
|
|
138
|
+
</div>
|
|
139
|
+
<div id="columns-group" style="display:none;">
|
|
140
|
+
<label class="form-label">Columns</label>
|
|
141
|
+
<input id="setting-columns" type="number" class="form-input" min="1" max="6" value="2">
|
|
142
|
+
<div class="mt-3">
|
|
143
|
+
<label class="form-label">Submit Button Span</label>
|
|
144
|
+
<select id="setting-submit-span" class="form-input">
|
|
145
|
+
<option value="">Auto (1 column)</option>
|
|
146
|
+
<option value="full">Full width</option>
|
|
147
|
+
</select>
|
|
148
|
+
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">Controls how wide the submit
|
|
149
|
+
button is in grid layout.</p>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<!-- Tab 4: Actions -->
|
|
157
|
+
<div class="tab-panel">
|
|
158
|
+
<div class="row">
|
|
159
|
+
<div class="col-6">
|
|
160
|
+
<div class="card mb-4">
|
|
161
|
+
<div class="card-header"><h2>Email Action</h2></div>
|
|
162
|
+
<div class="card-body">
|
|
163
|
+
<div class="mb-3">
|
|
164
|
+
<label class="form-label" style="display:flex;align-items:center;gap:.5rem;cursor:pointer;">
|
|
165
|
+
<input id="action-email-enabled" type="checkbox"> Send email on submit
|
|
166
|
+
</label>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="mb-3">
|
|
169
|
+
<label class="form-label">Recipients</label>
|
|
170
|
+
<input id="action-email-recipients" type="text" class="form-input" placeholder="admin@example.com">
|
|
171
|
+
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">Comma-separated. Uses global
|
|
172
|
+
SMTP settings.</p>
|
|
173
|
+
</div>
|
|
174
|
+
<div>
|
|
175
|
+
<label class="form-label">Subject Prefix</label>
|
|
176
|
+
<input id="action-email-subject-prefix" type="text" class="form-input" placeholder="[Form]">
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
<div class="col-6">
|
|
182
|
+
<div class="card mb-4">
|
|
183
|
+
<div class="card-header"><h2>Webhook Action</h2></div>
|
|
184
|
+
<div class="card-body">
|
|
185
|
+
<div class="mb-3">
|
|
186
|
+
<label class="form-label" style="display:flex;align-items:center;gap:.5rem;cursor:pointer;">
|
|
187
|
+
<input id="action-webhook-enabled" type="checkbox"> POST to webhook on submit
|
|
188
|
+
</label>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="mb-3">
|
|
191
|
+
<label class="form-label">URL</label>
|
|
192
|
+
<input id="action-webhook-url" type="url" class="form-input"
|
|
193
|
+
placeholder="https://hooks.example.com/form">
|
|
194
|
+
</div>
|
|
195
|
+
<div>
|
|
196
|
+
<label class="form-label">Method</label>
|
|
197
|
+
<select id="action-webhook-method" class="form-input">
|
|
198
|
+
<option value="POST">POST</option>
|
|
199
|
+
<option value="PUT">PUT</option>
|
|
200
|
+
</select>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
<div class="col-6">
|
|
206
|
+
<div class="card mb-4">
|
|
207
|
+
<div class="card-header"><h2>CMS Action</h2></div>
|
|
208
|
+
<div class="card-body">
|
|
209
|
+
<div class="mb-3">
|
|
210
|
+
<label class="form-label">Action on Submit</label>
|
|
211
|
+
<select id="action-cms-slug" class="form-input">
|
|
212
|
+
<option value="">— None —</option>
|
|
213
|
+
</select>
|
|
214
|
+
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">
|
|
215
|
+
Run a CMS Action after the entry is stored. Requires Pro (MongoDB).
|
|
216
|
+
Failures are non-fatal — the submission is always saved first.
|
|
217
|
+
</p>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div class="card mb-4">
|
|
225
|
+
<div class="card-header"><h2>Spam Protection</h2></div>
|
|
226
|
+
<div class="card-body">
|
|
227
|
+
<div class="mb-3">
|
|
228
|
+
<label class="form-label" style="display:flex;align-items:center;gap:.5rem;cursor:pointer;">
|
|
229
|
+
<input id="setting-honeypot" type="checkbox" checked> Enable honeypot field
|
|
230
|
+
</label>
|
|
231
|
+
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">Silently discards bot submissions
|
|
232
|
+
that fill a hidden field.</p>
|
|
233
|
+
</div>
|
|
234
|
+
<div>
|
|
235
|
+
<label class="form-label">Rate Limit (per minute)</label>
|
|
236
|
+
<input id="setting-rate-limit" type="number" class="form-input" min="1" max="60" value="3">
|
|
237
|
+
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">Max submissions per IP per
|
|
238
|
+
minute.</p>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as S}from"../api.js";let _=null;const z={deleteEntry:[],moveToCollection:[{name:"targetCollection",label:"Target Collection Slug",placeholder:"e.g. archived-applications"}],webhook:[{name:"url",label:"URL",placeholder:"https://hooks.example.com/notify"},{name:"method",label:"Method",placeholder:"POST"},{name:"body",label:"Body (JSON)",placeholder:'{"email": "{{entry.data.email}}"}',multiline:!0}],email:[{name:"to",label:"To",placeholder:"{{entry.data.email}}"},{name:"subject",label:"Subject",placeholder:"Your application update"},{name:"template",label:"Body",placeholder:"Your application has been approved.",multiline:!0}]};export const actionEditorView={templateUrl:"/admin/js/templates/action-editor.html",async onMount(e){_=null;const o=window.location.hash.match(/\/actions\/edit\/([^/?#]+)/);o&&(_=o[1]),E.tabs(e.find("#action-editor-tabs").get(0)),await D(e),await J(e),_&&(e.find("#action-editor-title").text("Edit Action"),await P(e,_)),e.find("#add-step-btn").off("click").on("click",()=>{const n=e.find("#add-step-type").val()||"updateField";B(e,{type:n,config:{}})}),e.find("#save-action-btn").off("click").on("click",async()=>{await K(e)}),U(e),Domma.icons.scan()}};async function D(e){const t=e.find("#action-collection").get(0);if(t)try{(await S.collections.list()).forEach(n=>{const i=document.createElement("option");i.value=n.slug,i.textContent=`${n.title} (${n.slug})`,t.appendChild(i)})}catch{}}async function J(e){const t=e.find("#action-roles-checkboxes").get(0);if(!t)return;["admin","manager","editor","subscriber"].forEach(n=>{const i=document.createElement("label");i.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const l=document.createElement("input");l.type="checkbox",l.value=n,l.dataset.role=n,l.className="action-role-cb",l.checked=n==="admin",i.appendChild(l),i.appendChild(document.createTextNode(n)),t.appendChild(i)})}async function P(e,t){try{const o=await S.actions.get(t);if(!o){E.toast("Action not found.",{type:"error"}),R.navigate("/actions");return}j(e,o)}catch(o){E.toast(o.message||"Failed to load action.",{type:"error"}),R.navigate("/actions")}}function U(e){const t=e.find("#action-rowlevel-enabled").get(0),o=e.find("#action-rowlevel-config").get(0),n=e.find("#action-rowlevel-mode").get(0),i=e.find("#action-rowlevel-field-group").get(0);t&&(t.addEventListener("change",()=>{o&&(o.style.display=t.checked?"flex":"none")}),n&&n.addEventListener("change",()=>{i&&(i.style.display=n.value==="field"?"":"none")}))}function j(e,t){e.find("#action-title").val(t.title||""),e.find("#action-slug").val(t.slug||""),e.find("#action-description").val(t.description||""),e.find("#action-bundled").prop("checked",!!t.bundled),e.find("#action-collection").val(t.collection||""),e.find("#action-trigger-type").val(t.trigger?.type||"manual"),e.find("#action-trigger-label").val(t.trigger?.label||"Run"),e.find("#action-trigger-icon").val(t.trigger?.icon||"zap"),e.find("#action-trigger-confirm").val(t.trigger?.confirmMessage||"");const o=t.access?.roles||["admin"];e.find(".action-role-cb").each(function(){this.checked=o.includes(this.value)});const n=t.access?.rowLevel;n&&(e.find("#action-rowlevel-enabled").prop("checked",!0),e.find("#action-rowlevel-config").css("display","flex"),e.find("#action-rowlevel-mode").val(n.mode||"owner"),e.find("#action-rowlevel-userkey").val(n.userKey||"id"),n.mode==="field"&&(e.find("#action-rowlevel-field-group").css("display",""),e.find("#action-rowlevel-field").val(n.field||"")));const i=e.find("#action-steps-list").get(0);if(i){const l=i.querySelector(".steps-empty-placeholder");for(l&&l.remove();i.firstChild;)i.removeChild(i.firstChild);const a=document.createElement("p");a.className="text-muted steps-empty-placeholder",a.textContent="No steps yet. Add a step to define what this action does.",a.style.cssText="text-align:center;padding:2rem 0;",i.appendChild(a)}(t.steps||[]).forEach(l=>B(e,l))}function B(e,t){const o=e.find("#action-steps-list").get(0);if(!o)return;const n=o.querySelector(".steps-empty-placeholder");n&&n.remove();const i=z[t.type]||[],l=document.createElement("div");l.className="card mb-2 step-card",l.dataset.stepType=t.type;const a=document.createElement("div");a.className="card-header",a.style.cssText="display:flex;align-items:center;gap:.5rem;";const s=document.createElement("code");s.textContent=t.type,s.style.cssText="flex:1;font-size:.85rem;";const p=document.createElement("button");p.type="button",p.className="btn btn-sm btn-danger";const g=document.createElement("span");g.setAttribute("data-icon","trash-2"),p.appendChild(g),p.addEventListener("click",()=>{if(l.remove(),!o.querySelector(".step-card")){const d=document.createElement("p");d.className="text-muted steps-empty-placeholder",d.textContent="No steps yet. Add a step to define what this action does.",d.style.cssText="text-align:center;padding:2rem 0;",o.appendChild(d)}}),a.appendChild(s),a.appendChild(p);const u=document.createElement("div");if(u.className="card-body",i.length===0){const d=document.createElement("p");d.className="text-muted",d.textContent=t.type==="deleteEntry"?"This step deletes the entry. No configuration required.":"No additional configuration required.",d.style.margin="0",u.appendChild(d)}t.type==="updateField"?G(u,t,e):i.forEach(d=>{const h=document.createElement("div");h.style.cssText="margin-bottom:.75rem;";const b=document.createElement("label");b.className="form-label",b.textContent=d.label,h.appendChild(b);let m;d.multiline?(m=document.createElement("textarea"),m.rows=3,m.style.cssText="font-family:monospace;font-size:.8rem;resize:vertical;",m.value=typeof t.config?.[d.name]=="object"?JSON.stringify(t.config[d.name],null,2):t.config?.[d.name]??""):(m=document.createElement("input"),m.type="text",m.value=t.config?.[d.name]??""),m.className=`form-input step-field-${d.name}`,m.placeholder=d.placeholder||"",m.dataset.field=d.name,h.appendChild(m),u.appendChild(h)}),l.appendChild(a),l.appendChild(u),o.appendChild(l),Domma.icons.scan(l)}async function G(e,t,o){const n=o.find("#action-collection").val(),i=document.createElement("div");i.style.cssText="margin-bottom:.75rem;";const l=document.createElement("label");l.className="form-label",l.textContent="Field",i.appendChild(l);const a=document.createElement("select");a.className="form-input step-field-field",a.dataset.field="field";const s=document.createElement("input");s.type="text",s.className="form-input mt-2 step-field-field-custom",s.placeholder="Field name, e.g. status",s.style.display="none";const p=document.createElement("div");p.style.cssText="margin-bottom:.75rem;";const g=document.createElement("label");g.className="form-label",g.textContent="New Value",p.appendChild(g);const u=document.createElement("div");p.appendChild(u);const d=document.createElement("button");d.type="button",d.className="btn btn-ghost btn-sm mt-2",d.style.cssText="font-size:.75rem;padding:.2rem .5rem;",d.textContent="Template Variables";const h=document.createElement("div");h.style.cssText="display:none;background:var(--dm-surface-2,#1e1e2e);border-radius:.4rem;padding:.75rem;margin-top:.5rem;font-size:.8rem;line-height:1.7;";const b=document.createElement("strong");b.textContent="Available template variables:",h.appendChild(b);const m=document.createElement("table");m.style.cssText="width:100%;border-collapse:collapse;margin-top:.4rem;",[["{{now}}","Current ISO timestamp"],["{{user.id}}","Executing user's ID"],["{{user.name}}","Executing user's name"],["{{user.email}}","Executing user's email"],["{{entry.id}}","Entry's unique ID"],["{{entry.data.fieldName}}","Any field value from the entry"],["{{env.CMS_PUBLIC_*}}","Public environment variables"]].forEach(([r,f])=>{const x=document.createElement("tr"),w=document.createElement("td");w.style.cssText="padding:.2rem .5rem .2rem 0;opacity:.7;white-space:nowrap;";const T=document.createElement("code");T.textContent=r,w.appendChild(T);const c=document.createElement("td");c.textContent=f,x.appendChild(w),x.appendChild(c),m.appendChild(x)}),h.appendChild(m),d.addEventListener("click",()=>{const r=h.style.display!=="none";h.style.display=r?"none":"",d.textContent=r?"Template Variables":"Hide Variables"}),p.appendChild(d),p.appendChild(h),e.appendChild(i),e.appendChild(p);let k=[];if(n)try{k=(await S.collections.get(n)).fields||[]}catch{}const F=document.createElement("option");F.value="",F.textContent=k.length?"\u2014 select a field \u2014":"\u2014 no fields available \u2014",a.appendChild(F),k.forEach(r=>{const f=document.createElement("option");f.value=r.name,f.textContent=`${r.label} (${r.name})`,f.dataset.fieldType=r.type,f.dataset.fieldOptions=r.type==="select"?JSON.stringify(r.options||[]):"",a.appendChild(f)});const L=document.createElement("option");L.value="__custom__",L.textContent="\u2014 enter manually \u2014",a.appendChild(L);const C=t.config?.field||"",I=C&&[...a.options].find(r=>r.value===C);I?a.value=C:C&&(a.value="__custom__",s.value=C,s.style.display=""),i.appendChild(a),i.appendChild(s);function V(r,f){u.textContent="";const x=r?[...a.options].find(c=>c.value===r):null,w=x?.dataset.fieldType==="select",T=w?JSON.parse(x.dataset.fieldOptions||"[]"):[];if(w&&T.length){const c=document.createElement("select");c.className="form-input step-field-value",c.dataset.field="value";const O=document.createElement("option");O.value="",O.textContent="\u2014 select a value \u2014",c.appendChild(O),T.forEach(v=>{const N=typeof v=="string"?v:v.value??"",M=typeof v=="string"?v:v.label||v.value||N;if(!N||N==="undefined")return;const A=document.createElement("option");A.value=N,A.textContent=M,c.appendChild(A)});const q=document.createElement("option");q.value="__custom__",q.textContent="\u2014 enter manually \u2014",c.appendChild(q);const y=document.createElement("input");y.type="text",y.className="form-input mt-2",y.placeholder="e.g. approved or {{now}}",y.style.display="none",f&&[...c.options].find(v=>v.value===f&&v.value!=="__custom__")?c.value=f:f&&(c.value="__custom__",y.value=f,y.style.display=""),c.addEventListener("change",()=>{const v=c.value==="__custom__";y.style.display=v?"":"none",v||(y.value="")}),u.appendChild(c),u.appendChild(y)}else{const c=document.createElement("input");c.type="text",c.className="form-input step-field-value",c.dataset.field="value",c.placeholder="e.g. approved or {{now}}",c.value=f||"",u.appendChild(c)}}V(I?C:null,t.config?.value||""),a.addEventListener("change",()=>{const r=a.value==="__custom__";s.style.display=r?"":"none",r||(s.value=""),V(r?null:a.value,"")})}function H(e){const t=[];return e.find(".step-card").each(function(){const o=this.dataset.stepType,n={};if(o==="updateField"){let l=this.querySelector(".step-field-field")?.value?.trim()||"";l==="__custom__"&&(l=this.querySelector(".step-field-field-custom")?.value?.trim()||""),n.field=l;const a=this.querySelector(".step-field-value");let s=a?.value?.trim()||"";s==="__custom__"&&(s=a?.nextElementSibling?.value?.trim()||""),n.value=s}else(z[o]||[]).forEach(l=>{const a=this.querySelector(`.step-field-${l.name}`);if(!a)return;const s=a.value.trim();if(l.multiline&&s)try{n[l.name]=JSON.parse(s)}catch{n[l.name]=s}else n[l.name]=s});t.push({type:o,config:n})}),t}async function K(e){const t=e.find("#action-title").val().trim();if(!t){E.toast("Title is required.",{type:"warning"});return}const o=e.find("#action-collection").val();if(!o){E.toast("Target collection is required (General tab).",{type:"warning"});return}const n=[];e.find(".action-role-cb:checked").each(function(){n.push(this.value)});const i=e.find("#action-rowlevel-enabled").is(":checked");let l=null;if(i){const p=e.find("#action-rowlevel-mode").val()||"owner",g=e.find("#action-rowlevel-userkey").val()||"id";if(l={mode:p,userKey:g},p==="field"){const u=e.find("#action-rowlevel-field").val().trim();if(!u){E.toast("Field name is required for Field Match mode.",{type:"warning"});return}l.field=u}}const a={title:t,slug:e.find("#action-slug").val().trim()||void 0,description:e.find("#action-description").val().trim(),...e.find("#action-bundled").is(":checked")?{bundled:!0}:{},collection:o,trigger:{type:e.find("#action-trigger-type").val()||"manual",label:e.find("#action-trigger-label").val().trim()||"Run",icon:e.find("#action-trigger-icon").val().trim()||"zap",confirmMessage:e.find("#action-trigger-confirm").val().trim()||null},steps:H(e),access:{roles:n,rowLevel:l}},s=e.find("#save-action-btn").get(0);s&&(s.disabled=!0);try{if(_)await S.actions.update(_,a),E.toast("Action updated.",{type:"success"});else{const p=await S.actions.create(a);E.toast("Action created.",{type:"success"}),R.navigate(`/actions/edit/${p.slug}`)}}catch(p){E.toast(p.message||"Failed to save action.",{type:"error"})}finally{s&&(s.disabled=!1)}}
|
|
1
|
+
import{api as T}from"../api.js";let _=null;const $={deleteEntry:[],moveToCollection:[{name:"targetCollection",label:"Target Collection Slug",placeholder:"e.g. archived-applications"}],webhook:[{name:"url",label:"URL",placeholder:"https://hooks.example.com/notify"},{name:"method",label:"Method",placeholder:"POST"},{name:"body",label:"Body (JSON)",placeholder:'{"email": "{{entry.data.email}}"}',multiline:!0}],email:[{name:"to",label:"To",placeholder:"{{entry.data.email}}"},{name:"subject",label:"Subject",placeholder:"Your application update"},{name:"template",label:"Body",placeholder:"Your application has been approved.",multiline:!0}]};export const actionEditorView={templateUrl:"/admin/js/templates/action-editor.html",async onMount(e){_=null;const t=window.location.hash.match(/\/actions\/edit\/([^/?#]+)/);t&&(_=t[1]),E.tabs(e.find("#action-editor-tabs").get(0)),await j(e),await D(e),_&&(e.find("#action-editor-title").text("Edit Action"),await I(e,_)),e.find("#add-step-btn").off("click").on("click",()=>{const l=e.find("#add-step-type").val()||"updateField";J(e,{type:l,config:{}})}),e.find("#save-action-btn").off("click").on("click",async()=>{await G(e)}),B(e),Domma.icons.scan()}};async function j(e){const t=e.find("#action-collection").get(0);if(t)try{(await T.collections.list()).forEach(l=>{const i=document.createElement("option");i.value=l.slug,i.textContent=`${l.title} (${l.slug})`,t.appendChild(i)})}catch{}}async function D(e){const t=e.find("#action-roles-checkboxes").get(0);t&&["admin","manager","editor","subscriber"].forEach(l=>{const i=document.createElement("label");i.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const n=document.createElement("input");n.type="checkbox",n.value=l,n.dataset.role=l,n.className="action-role-cb",n.checked=l==="admin",i.appendChild(n),i.appendChild(document.createTextNode(l)),t.appendChild(i)})}async function I(e,t){try{const l=await T.actions.get(t);if(!l){E.toast("Action not found.",{type:"error"}),R.navigate("/actions");return}P(e,l)}catch(l){E.toast(l.message||"Failed to load action.",{type:"error"}),R.navigate("/actions")}}function B(e){const t=e.find("#action-rowlevel-enabled").get(0),l=e.find("#action-rowlevel-config").get(0),i=e.find("#action-rowlevel-mode").get(0),n=e.find("#action-rowlevel-field-group").get(0);t&&(t.addEventListener("change",()=>{l&&(l.style.display=t.checked?"flex":"none")}),i&&i.addEventListener("change",()=>{n&&(n.style.display=i.value==="field"?"":"none")}))}function P(e,t){e.find("#action-title").val(t.title||""),e.find("#action-slug").val(t.slug||""),e.find("#action-description").val(t.description||""),e.find("#action-collection").val(t.collection||""),e.find("#action-bundled").prop("checked",!!t.bundled),e.find("#action-trigger-type").val(t.trigger?.type||"manual"),e.find("#action-trigger-label").val(t.trigger?.label||"Run"),e.find("#action-trigger-icon").val(t.trigger?.icon||"zap"),e.find("#action-trigger-confirm").val(t.trigger?.confirmMessage||"");const l=t.access?.roles||["admin"];e.find(".action-role-cb").each(function(){this.checked=l.includes(this.value)});const i=t.access?.rowLevel;i&&(e.find("#action-rowlevel-enabled").prop("checked",!0),e.find("#action-rowlevel-config").css("display","flex"),e.find("#action-rowlevel-mode").val(i.mode||"owner"),e.find("#action-rowlevel-userkey").val(i.userKey||"id"),i.mode==="field"&&(e.find("#action-rowlevel-field-group").css("display",""),e.find("#action-rowlevel-field").val(i.field||"")));const n=e.find("#action-steps-list").get(0);if(n){const c=n.querySelector(".steps-empty-placeholder");for(c&&c.remove();n.firstChild;)n.removeChild(n.firstChild);const a=document.createElement("p");a.className="text-muted steps-empty-placeholder",a.textContent="No steps yet. Add a step to define what this action does.",a.style.cssText="text-align:center;padding:2rem 0;",n.appendChild(a)}(t.steps||[]).forEach(c=>J(e,c))}function J(e,t){const l=e.find("#action-steps-list").get(0);if(!l)return;const i=l.querySelector(".steps-empty-placeholder");i&&i.remove();const n=$[t.type]||[],c=document.createElement("div");c.className="card mb-2 step-card",c.dataset.stepType=t.type;const a=document.createElement("div");a.className="card-header",a.style.cssText="display:flex;align-items:center;gap:.5rem;";const p=document.createElement("code");p.textContent=t.type,p.style.cssText="flex:1;font-size:.85rem;";const r=document.createElement("button");r.type="button",r.className="btn btn-sm btn-danger";const v=document.createElement("span");v.setAttribute("data-icon","trash-2"),r.appendChild(v),r.addEventListener("click",()=>{if(c.remove(),!l.querySelector(".step-card")){const o=document.createElement("p");o.className="text-muted steps-empty-placeholder",o.textContent="No steps yet. Add a step to define what this action does.",o.style.cssText="text-align:center;padding:2rem 0;",l.appendChild(o)}}),a.appendChild(p),a.appendChild(r);const u=document.createElement("div");if(u.className="card-body",n.length===0){const o=document.createElement("p");o.className="text-muted",o.textContent=t.type==="deleteEntry"?"This step deletes the entry. No configuration required.":"No additional configuration required.",o.style.margin="0",u.appendChild(o)}t.type==="updateField"?U(u,t,e):n.forEach(o=>{const y=document.createElement("div");y.style.cssText="margin-bottom:.75rem;";const b=document.createElement("label");b.className="form-label",b.textContent=o.label,y.appendChild(b);let m;o.multiline?(m=document.createElement("textarea"),m.rows=3,m.style.cssText="font-family:monospace;font-size:.8rem;resize:vertical;",m.value=typeof t.config?.[o.name]=="object"?JSON.stringify(t.config[o.name],null,2):t.config?.[o.name]??""):(m=document.createElement("input"),m.type="text",m.value=t.config?.[o.name]??""),m.className=`form-input step-field-${o.name}`,m.placeholder=o.placeholder||"",m.dataset.field=o.name,y.appendChild(m),u.appendChild(y)}),c.appendChild(a),c.appendChild(u),l.appendChild(c),Domma.icons.scan(c)}async function U(e,t,l){const i=l.find("#action-collection").val(),n=document.createElement("div");n.style.cssText="margin-bottom:.75rem;";const c=document.createElement("label");c.className="form-label",c.textContent="Field",n.appendChild(c);const a=document.createElement("select");a.className="form-input step-field-field",a.dataset.field="field";const p=document.createElement("input");p.type="text",p.className="form-input mt-2 step-field-field-custom",p.placeholder="Field name, e.g. status",p.style.display="none";const r=document.createElement("div");r.style.cssText="margin-bottom:.75rem;";const v=document.createElement("label");v.className="form-label",v.textContent="New Value",r.appendChild(v);const u=document.createElement("div");r.appendChild(u);const o=document.createElement("button");o.type="button",o.className="btn btn-ghost btn-sm mt-2",o.style.cssText="font-size:.75rem;padding:.2rem .5rem;",o.textContent="Template Variables";const y=document.createElement("div");y.style.cssText="display:none;background:var(--dm-surface-2,#1e1e2e);border-radius:.4rem;padding:.75rem;margin-top:.5rem;font-size:.8rem;line-height:1.7;";const b=document.createElement("strong");b.textContent="Available template variables:",y.appendChild(b);const m=document.createElement("table");m.style.cssText="width:100%;border-collapse:collapse;margin-top:.4rem;",[["{{now}}","Current ISO timestamp"],["{{user.id}}","Executing user's ID"],["{{user.name}}","Executing user's name"],["{{user.email}}","Executing user's email"],["{{entry.id}}","Entry's unique ID"],["{{entry.data.fieldName}}","Any field value from the entry"],["{{env.CMS_PUBLIC_*}}","Public environment variables"]].forEach(([d,f])=>{const x=document.createElement("tr"),w=document.createElement("td");w.style.cssText="padding:.2rem .5rem .2rem 0;opacity:.7;white-space:nowrap;";const N=document.createElement("code");N.textContent=d,w.appendChild(N);const s=document.createElement("td");s.textContent=f,x.appendChild(w),x.appendChild(s),m.appendChild(x)}),y.appendChild(m),o.addEventListener("click",()=>{const d=y.style.display!=="none";y.style.display=d?"none":"",o.textContent=d?"Template Variables":"Hide Variables"}),r.appendChild(o),r.appendChild(y),e.appendChild(n),e.appendChild(r);let S=[];if(i)try{S=(await T.collections.get(i)).fields||[]}catch{}const q=document.createElement("option");q.value="",q.textContent=S.length?"\u2014 select a field \u2014":"\u2014 no fields available \u2014",a.appendChild(q),S.forEach(d=>{const f=document.createElement("option");f.value=d.name,f.textContent=`${d.label} (${d.name})`,f.dataset.fieldType=d.type,f.dataset.fieldOptions=d.type==="select"?JSON.stringify(d.options||[]):"",a.appendChild(f)});const L=document.createElement("option");L.value="__custom__",L.textContent="\u2014 enter manually \u2014",a.appendChild(L);const C=t.config?.field||"",z=C&&[...a.options].find(d=>d.value===C);z?a.value=C:C&&(a.value="__custom__",p.value=C,p.style.display=""),n.appendChild(a),n.appendChild(p);function M(d,f){u.textContent="";const x=d?[...a.options].find(s=>s.value===d):null,w=x?.dataset.fieldType==="select",N=w?JSON.parse(x.dataset.fieldOptions||"[]"):[];if(w&&N.length){const s=document.createElement("select");s.className="form-input step-field-value",s.dataset.field="value";const A=document.createElement("option");A.value="",A.textContent="\u2014 select a value \u2014",s.appendChild(A),N.forEach(h=>{const k=typeof h=="string"?h:h.value??"",V=typeof h=="string"?h:h.label||h.value||k;if(!k||k==="undefined")return;const O=document.createElement("option");O.value=k,O.textContent=V,s.appendChild(O)});const F=document.createElement("option");F.value="__custom__",F.textContent="\u2014 enter manually \u2014",s.appendChild(F);const g=document.createElement("input");g.type="text",g.className="form-input mt-2",g.placeholder="e.g. approved or {{now}}",g.style.display="none",f&&[...s.options].find(h=>h.value===f&&h.value!=="__custom__")?s.value=f:f&&(s.value="__custom__",g.value=f,g.style.display=""),s.addEventListener("change",()=>{const h=s.value==="__custom__";g.style.display=h?"":"none",h||(g.value="")}),u.appendChild(s),u.appendChild(g)}else{const s=document.createElement("input");s.type="text",s.className="form-input step-field-value",s.dataset.field="value",s.placeholder="e.g. approved or {{now}}",s.value=f||"",u.appendChild(s)}}M(z?C:null,t.config?.value||""),a.addEventListener("change",()=>{const d=a.value==="__custom__";p.style.display=d?"":"none",d||(p.value=""),M(d?null:a.value,"")})}function K(e){const t=[];return e.find(".step-card").each(function(){const l=this.dataset.stepType,i={};if(l==="updateField"){let n=this.querySelector(".step-field-field")?.value?.trim()||"";n==="__custom__"&&(n=this.querySelector(".step-field-field-custom")?.value?.trim()||""),i.field=n;const c=this.querySelector(".step-field-value");let a=c?.value?.trim()||"";a==="__custom__"&&(a=c?.nextElementSibling?.value?.trim()||""),i.value=a}else($[l]||[]).forEach(n=>{const c=this.querySelector(`.step-field-${n.name}`);if(!c)return;const a=c.value.trim();if(n.multiline&&a)try{i[n.name]=JSON.parse(a)}catch{i[n.name]=a}else i[n.name]=a});t.push({type:l,config:i})}),t}async function G(e){const t=e.find("#action-title").val().trim();if(!t){E.toast("Title is required.",{type:"warning"});return}const l=e.find("#action-collection").val();if(!l){E.toast("Target collection is required (General tab).",{type:"warning"});return}const i=[];e.find(".action-role-cb:checked").each(function(){i.push(this.value)});const n=e.find("#action-rowlevel-enabled").is(":checked");let c=null;if(n){const r=e.find("#action-rowlevel-mode").val()||"owner",v=e.find("#action-rowlevel-userkey").val()||"id";if(c={mode:r,userKey:v},r==="field"){const u=e.find("#action-rowlevel-field").val().trim();if(!u){E.toast("Field name is required for Field Match mode.",{type:"warning"});return}c.field=u}}const a={title:t,slug:e.find("#action-slug").val().trim()||void 0,description:e.find("#action-description").val().trim(),collection:l,...e.find("#action-bundled").is(":checked")?{bundled:!0}:{},trigger:{type:e.find("#action-trigger-type").val()||"manual",label:e.find("#action-trigger-label").val().trim()||"Run",icon:e.find("#action-trigger-icon").val().trim()||"zap",confirmMessage:e.find("#action-trigger-confirm").val().trim()||null},steps:K(e),access:{roles:i,rowLevel:c}},p=e.find("#save-action-btn").get(0);p&&(p.disabled=!0);try{if(_)await T.actions.update(_,a),E.toast("Action updated.",{type:"success"});else{const r=await T.actions.create(a);E.toast("Action created.",{type:"success"}),R.navigate(`/actions/edit/${r.slug}`)}}catch(r){E.toast(r.message||"Failed to save action.",{type:"error"})}finally{p&&(p.disabled=!1)}}
|