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.
Files changed (44) hide show
  1. package/admin/js/templates/block-editor.html +163 -163
  2. package/admin/js/templates/form-editor.html +245 -245
  3. package/admin/js/views/action-editor.js +1 -1
  4. package/admin/js/views/block-editor.js +8 -8
  5. package/admin/js/views/collection-editor.js +4 -4
  6. package/admin/js/views/collections.js +1 -1
  7. package/admin/js/views/form-editor.js +7 -7
  8. package/admin/js/views/forms.js +1 -1
  9. package/admin/js/views/navigation.js +14 -14
  10. package/admin/js/views/page-editor.js +35 -35
  11. package/admin/js/views/pages.js +5 -5
  12. package/admin/js/views/plugins.js +19 -10
  13. package/admin/js/views/view-editor.js +1 -1
  14. package/config/plugins.json +35 -0
  15. package/package.json +1 -1
  16. package/plugins/docs/data/documents/57e003f0-68f2-47dc-9c36-ed4b10ed3deb.json +4 -4
  17. package/plugins/docs/data/folders.json +3 -3
  18. package/plugins/docs/data/versions/57e003f0-68f2-47dc-9c36-ed4b10ed3deb/1.json +5 -0
  19. package/plugins/garage/admin/templates/garage.html +30 -0
  20. package/plugins/garage/admin/views/garage.js +62 -1
  21. package/plugins/garage/plugin.json +1 -1
  22. package/plugins/notes/admin/templates/notes.html +2 -11
  23. package/plugins/notes/admin/views/notes.js +107 -129
  24. package/plugins/notes/collections/user-notes/schema.json +2 -1
  25. package/plugins/notes/plugin.json +1 -1
  26. package/plugins/site-search/admin/templates/site-search.html +174 -46
  27. package/plugins/site-search/admin/views/site-search.js +72 -1
  28. package/plugins/site-search/config.js +6 -1
  29. package/plugins/site-search/plugin.json +1 -1
  30. package/plugins/site-search/public/inject-head.html +1 -1
  31. package/plugins/site-search/public/search.css +1 -1
  32. package/plugins/site-search/public/search.js +1 -1
  33. package/plugins/todo/admin/templates/todo.html +2 -8
  34. package/plugins/todo/admin/views/todo.js +122 -106
  35. package/plugins/todo/collections/todos/schema.json +2 -1
  36. package/plugins/todo/plugin.json +1 -1
  37. package/server/routes/api/media.js +127 -118
  38. package/server/routes/api/plugins.js +15 -4
  39. package/server/server.js +288 -285
  40. package/server/services/blocks.js +6 -3
  41. package/server/services/collections.js +17 -10
  42. package/server/services/plugins.js +77 -67
  43. package/server/services/renderer.js +3 -3
  44. 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
- <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
+ <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)}}