domma-cms 0.76.0 → 0.77.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CLAUDE.md +1 -1
  2. package/admin/js/api.js +1 -1
  3. package/admin/js/app.js +2 -2
  4. package/admin/js/lib/action-editor-arrange.js +1 -0
  5. package/admin/js/lib/api-endpoint-editor-arrange.js +2 -0
  6. package/admin/js/lib/block-editor-arrange.js +2 -0
  7. package/admin/js/lib/collection-editor-arrange.js +3 -0
  8. package/admin/js/lib/collection-entries-arrange.js +1 -0
  9. package/admin/js/lib/component-editor-arrange.js +27 -0
  10. package/admin/js/lib/context-menu-editor-arrange.js +1 -0
  11. package/admin/js/lib/dashboard-arrange.js +1 -0
  12. package/admin/js/lib/form-editor-arrange.js +1 -0
  13. package/admin/js/lib/form-submissions-arrange.js +2 -0
  14. package/admin/js/lib/menu-editor-arrange.js +2 -0
  15. package/admin/js/lib/page-editor-arrange.js +1 -0
  16. package/admin/js/lib/plugin-code-arrange.js +1 -0
  17. package/admin/js/lib/project-page-arrange.js +1 -0
  18. package/admin/js/lib/role-editor-arrange.js +3 -0
  19. package/admin/js/lib/user-editor-arrange.js +1 -0
  20. package/admin/js/lib/view-editor-arrange.js +14 -0
  21. package/admin/js/templates/action-editor.html +223 -147
  22. package/admin/js/templates/api-endpoint-editor.html +172 -117
  23. package/admin/js/templates/block-editor.html +194 -203
  24. package/admin/js/templates/collection-editor.html +191 -113
  25. package/admin/js/templates/collection-entries.html +99 -36
  26. package/admin/js/templates/component-editor.html +128 -126
  27. package/admin/js/templates/context-menu-editor.html +208 -148
  28. package/admin/js/templates/dashboard.html +184 -22
  29. package/admin/js/templates/form-editor.html +41 -26
  30. package/admin/js/templates/form-submissions.html +78 -26
  31. package/admin/js/templates/menu-editor.html +57 -17
  32. package/admin/js/templates/menu-locations.html +54 -11
  33. package/admin/js/templates/page-editor.html +47 -26
  34. package/admin/js/templates/plugin-code.html +91 -73
  35. package/admin/js/templates/project-detail.html +83 -145
  36. package/admin/js/templates/project-settings.html +88 -53
  37. package/admin/js/templates/role-editor.html +158 -83
  38. package/admin/js/templates/user-editor.html +150 -14
  39. package/admin/js/templates/view-editor.html +203 -180
  40. package/admin/js/templates/view-preview.html +45 -45
  41. package/admin/js/views/action-editor.js +7 -1
  42. package/admin/js/views/api-endpoint-editor.js +2 -3
  43. package/admin/js/views/block-editor.js +14 -8
  44. package/admin/js/views/collection-editor.js +64 -5
  45. package/admin/js/views/collection-entries.js +50 -1
  46. package/admin/js/views/component-editor.js +2 -28
  47. package/admin/js/views/context-menu-editor.js +54 -55
  48. package/admin/js/views/dashboard.js +7 -1
  49. package/admin/js/views/form-editor.js +7 -7
  50. package/admin/js/views/form-submissions.js +11 -1
  51. package/admin/js/views/index.js +1 -1
  52. package/admin/js/views/login.js +2 -2
  53. package/admin/js/views/menu-editor.js +21 -43
  54. package/admin/js/views/menu-locations.js +16 -1
  55. package/admin/js/views/page-editor.js +26 -26
  56. package/admin/js/views/plugin-code.js +23 -4
  57. package/admin/js/views/project-detail.js +9 -1
  58. package/admin/js/views/project-settings.js +1 -1
  59. package/admin/js/views/role-editor.js +11 -3
  60. package/admin/js/views/user-editor.js +1 -1
  61. package/admin/js/views/view-editor.js +1 -1
  62. package/admin/js/views/view-preview.js +1 -1
  63. package/package.json +1 -1
  64. package/server/routes/api/dashboard.js +32 -4
  65. package/server/routes/api/forms.js +104 -13
  66. package/server/routes/api/users.js +12 -2
  67. package/server/routes/api/views.js +55 -1
  68. package/server/services/components.js +9 -0
  69. package/server/services/users.js +2 -2
  70. package/server/services/views.js +14 -0
  71. package/admin/js/templates/dashboard/activity-feed.html +0 -3
  72. package/admin/js/templates/dashboard/cache.html +0 -32
  73. package/admin/js/templates/dashboard/health-detail.html +0 -2
  74. package/admin/js/templates/dashboard/journeys.html +0 -17
  75. package/admin/js/templates/dashboard/kpi-strip.html +0 -34
  76. package/admin/js/templates/dashboard/spike-feed.html +0 -3
  77. package/admin/js/templates/dashboard/top-pages.html +0 -3
  78. package/admin/js/templates/dashboard/traffic-chart.html +0 -3
  79. package/admin/js/views/block-editor-enhance.js +0 -1
  80. package/admin/js/views/dashboard/lib/escape.js +0 -1
  81. package/admin/js/views/dashboard/widgets/activity-feed.js +0 -1
  82. package/admin/js/views/dashboard/widgets/cache.js +0 -1
  83. package/admin/js/views/dashboard/widgets/health-detail.js +0 -1
  84. package/admin/js/views/dashboard/widgets/journeys.js +0 -1
  85. package/admin/js/views/dashboard/widgets/kpi-strip.js +0 -1
  86. package/admin/js/views/dashboard/widgets/spike-feed.js +0 -6
  87. package/admin/js/views/dashboard/widgets/top-pages.js +0 -1
  88. package/admin/js/views/dashboard/widgets/traffic-chart.js +0 -1
@@ -1,183 +1,259 @@
1
- <div class="view-header">
2
- <h1><span data-icon="zap"></span> <span id="action-editor-title">New Action</span></h1>
3
- <div style="display:flex;gap:.5rem;">
4
- <a href="#/actions" class="btn btn-ghost btn-sm">
5
- <span data-icon="arrow-left"></span> All Actions
6
- </a>
7
- <button id="save-action-btn" class="btn btn-primary">
8
- <span data-icon="save"></span> Save Action
9
- </button>
10
- </div>
11
- </div>
1
+ <!-- Data › Actions › an action. Bound with M.applyBindings - see admin/js/views/action-editor.js, which also
2
+ draws the step cards and the template-variable table (a templateUrl goes through _.render, eating any
3
+ {{…}}). No page heading: the Tool banner is the heading; Save, All
4
+ actions and Delete are banner actions. -->
5
+ <div class="aex">
6
+ <p class="aex-note" data-bind-hidden="!loading.value">Loading…</p>
12
7
 
13
- <div class="tabs" id="action-editor-tabs">
14
- <div class="tab-list">
15
- <button class="tab-item active">General</button>
16
- <button class="tab-item">Trigger</button>
17
- <button class="tab-item">Steps</button>
18
- <button class="tab-item">Access</button>
8
+ <!-- File mode: actions live in MongoDB. A state of the site, explained - not an error. -->
9
+ <div class="aex-card" data-bind-hidden="!needsMongo.value">
10
+ <div class="aex-card-title"><span data-icon="database" data-icon-size="16"></span> Actions need MongoDB</div>
11
+ <p class="aex-note">Actions are a Pro feature: their settings are kept in MongoDB, and this site has no MongoDB
12
+ connection. Add a "default" connection (pro mode) and they can be made and edited here. Nothing else on
13
+ the site is affected.</p>
14
+ <p class="aex-note"><a href="#/actions">Back to Actions</a></p>
19
15
  </div>
20
- <div class="tab-content">
21
16
 
22
- <!-- General tab -->
23
- <div class="tab-panel active">
24
- <div class="card">
25
- <div class="card-body" style="display:flex;flex-direction:column;gap:1rem;max-width:600px;">
26
- <div>
27
- <label class="form-label">Title <span style="color:var(--dm-danger,#f87171);">*</span></label>
28
- <input id="action-title" type="text" class="form-input" placeholder="e.g. Approve Application">
29
- </div>
30
- <div>
31
- <label class="form-label">Slug</label>
32
- <input id="action-slug" type="text" class="form-input" placeholder="Auto-generated from title">
33
- <small class="text-muted">URL-safe identifier. Leave blank to auto-generate.</small>
34
- </div>
35
- <div>
36
- <label class="form-label">Description</label>
37
- <textarea id="action-description" class="form-input" rows="2" placeholder="Describe what this action does…"></textarea>
38
- </div>
39
- <div>
40
- <label class="form-label">Target Collection <span style="color:var(--dm-danger,#f87171);">*</span></label>
41
- <select id="action-collection" class="form-input">
42
- <option value="">- select collection -</option>
43
- </select>
44
- <small class="text-muted">The collection this action operates on.</small>
45
- </div>
46
- <div>
47
- <label class="form-label">Project</label>
48
- <select id="action-project" class="form-input">
49
- <option value="">- none -</option>
50
- </select>
51
- <small class="text-muted">Tag this action to a project for grouping.</small>
52
- </div>
53
- <div>
54
- <label class="form-check-label" title="Included in fresh installs via the seed script">
55
- <input id="action-bundled" type="checkbox" class="form-check"> Bundled
56
- </label>
17
+ <div class="aex-body" data-bind-hidden="needsMongo.value || loading.value">
18
+ <p class="aex-alert is-dirty" data-bind-hidden="!dirty.value">
19
+ <span data-icon="edit-3" data-icon-size="16"></span>
20
+ <span data-bind-text="dirtyText.value">Unsaved changes.</span>
21
+ <span class="aex-spacer"></span>
22
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="discard">Discard</button>
23
+ <button type="button" class="btn btn-sm btn-primary" data-on-click="save" data-bind-disabled="saving.value">Save</button></p>
24
+ <p class="aex-alert is-bad" data-bind-hidden="!problems.value">
25
+ <span data-icon="alert-triangle" data-icon-size="16"></span>
26
+ <span data-bind-text="problems.value"></span></p>
27
+
28
+ <div class="tabs aex-tabs">
29
+ <div class="tab-list">
30
+ <button type="button" class="tab-item active">General</button>
31
+ <button type="button" class="tab-item">Trigger</button>
32
+ <button type="button" class="tab-item">Steps <span class="aex-count" data-bind-text="stepCount.value"></span></button>
33
+ <button type="button" class="tab-item">Access</button>
34
+ </div>
35
+
36
+ <!-- General -->
37
+ <div class="tab-panel active">
38
+ <div class="aex-card">
39
+ <div class="aex-card-title">What it is</div>
40
+ <div class="aex-grid">
41
+ <div class="aex-wide">
42
+ <label class="form-label" for="action-title">Title <span class="aex-req">*</span></label>
43
+ <input id="action-title" type="text" class="form-input" maxlength="120" placeholder="e.g. Approve application" data-model="title.value">
44
+ <small class="aex-err" data-bind-hidden="!err.value.title" data-bind-text="err.value.title"></small>
45
+ </div>
46
+ <div>
47
+ <label class="form-label" for="action-slug">Slug
48
+ <span data-help-title="Slug" data-help="The action's id - in its admin address and in the [transitions] shortcode. Left blank, it is made from the title. It cannot be changed once the action exists."></span></label>
49
+ <input id="action-slug" type="text" class="form-input aex-mono" maxlength="80" data-model="slug.value" data-bind-disabled="editing" data-bind-placeholder="slugHint.value">
50
+ <small class="aex-err" data-bind-hidden="!err.value.slug" data-bind-text="err.value.slug"></small>
51
+ </div>
52
+ <div>
53
+ <label class="form-label" for="action-collection">Collection <span class="aex-req">*</span>
54
+ <span data-help-title="Collection" data-help="The collection whose entries this action runs on. Its button appears on those entries in the admin, and the Update a field step offers that collection's fields."></span></label>
55
+ <select id="action-collection" class="form-input" data-model="collection.value">
56
+ <option value="">- choose a collection -</option>
57
+ </select>
58
+ <small class="aex-err" data-bind-hidden="!err.value.collection" data-bind-text="err.value.collection"></small>
59
+ </div>
60
+ <div class="aex-wide">
61
+ <label class="form-label" for="action-description">Description</label>
62
+ <textarea id="action-description" class="form-input" rows="2" maxlength="500" placeholder="What this action does, for whoever reads the list" data-model="description.value"></textarea>
63
+ </div>
64
+ <div>
65
+ <label class="form-label" for="action-project">Project
66
+ <span data-help="Files the action under a project (Projects in the sidebar). None means Core."></span></label>
67
+ <select id="action-project" class="form-input" data-model="project.value">
68
+ <option value="">- none (Core) -</option>
69
+ </select>
70
+ </div>
71
+ <div class="aex-setting">
72
+ <label class="form-label" for="action-bundled">Bundled
73
+ <span data-help="Included in fresh installs by the seed script. Leave off for an action you made for this site."></span></label>
74
+ <input id="action-bundled" type="checkbox" class="form-switch" data-model="bundled.value">
75
+ </div>
57
76
  </div>
58
77
  </div>
59
78
  </div>
60
- </div>
61
79
 
62
- <!-- Trigger tab -->
63
- <div class="tab-panel">
64
- <div class="card">
65
- <div class="card-body" style="display:flex;flex-direction:column;gap:1rem;max-width:500px;">
66
- <div>
67
- <label class="form-label">Trigger Type</label>
68
- <select id="action-trigger-type" class="form-input">
69
- <option value="manual">Manual (button in entry list)</option>
70
- </select>
71
- </div>
72
- <div>
73
- <label class="form-label">Button Label</label>
74
- <input id="action-trigger-label" type="text" class="form-input" value="Run" placeholder="e.g. Approve">
80
+ <!-- Trigger -->
81
+ <div class="tab-panel">
82
+ <div class="aex-card">
83
+ <div class="aex-card-title">The button
84
+ <span data-help-title="Trigger" data-help="How the action is started. Manual actions appear as a button on each entry of the collection (and in the [transitions] shortcode when they have a transition)."></span></div>
85
+ <div class="aex-grid">
86
+ <div>
87
+ <label class="form-label" for="action-trigger-type">Trigger</label>
88
+ <select id="action-trigger-type" class="form-input" data-model="triggerType.value">
89
+ <option value="manual">Manual - a button on the entry</option>
90
+ </select>
91
+ </div>
92
+ <div>
93
+ <label class="form-label">Looks like</label>
94
+ <span class="aex-button-preview"><span class="aex-preview-icon"></span><span data-bind-text="buttonText.value">Run</span></span>
95
+ </div>
96
+ <div>
97
+ <label class="form-label" for="action-trigger-label">Button label</label>
98
+ <input id="action-trigger-label" type="text" class="form-input" maxlength="40" placeholder="Run" data-model="triggerLabel.value">
99
+ </div>
100
+ <div>
101
+ <label class="form-label" for="action-trigger-icon">Button icon
102
+ <span data-help="A Domma icon name - check-circle, send, archive, zap… The preview beside it shows whether the name exists."></span></label>
103
+ <input id="action-trigger-icon" type="text" class="form-input aex-mono" maxlength="40" placeholder="zap" data-model="triggerIcon.value">
104
+ </div>
105
+ <div class="aex-wide">
106
+ <label class="form-label" for="action-trigger-confirm">Confirmation message
107
+ <span data-help="Asked before the action runs - worth having for anything that deletes, moves or emails. Leave blank to run straight away."></span></label>
108
+ <input id="action-trigger-confirm" type="text" class="form-input" maxlength="200" placeholder="e.g. Approve this application?" data-model="triggerConfirm.value">
109
+ </div>
75
110
  </div>
76
- <div>
77
- <label class="form-label">Button Icon</label>
78
- <input id="action-trigger-icon" type="text" class="form-input" value="zap" placeholder="e.g. check-circle, zap, send">
79
- <small class="text-muted">Domma icon name.</small>
111
+ </div>
112
+
113
+ <div class="aex-card">
114
+ <div class="aex-setting">
115
+ <label class="form-label" for="action-transition-enabled">State transition (optional)
116
+ <span data-help-title="State transition" data-help="Makes the action a step in a workflow: it is offered only while the entry's state field holds one of the From values (the Collection Browser and the [transitions] shortcode list the transitions available for each entry). To is what the steps are expected to set it to - informational; an Update a field step does the writing."></span></label>
117
+ <input id="action-transition-enabled" type="checkbox" class="form-switch" data-model="transitionEnabled.value">
80
118
  </div>
81
- <div>
82
- <label class="form-label">Confirmation Message</label>
83
- <input id="action-trigger-confirm" type="text" class="form-input"
84
- placeholder="e.g. Are you sure you want to run this action?">
85
- <small class="text-muted">Leave blank to skip the confirmation prompt.</small>
119
+ <div class="aex-grid" data-bind-hidden="!transitionEnabled.value">
120
+ <div>
121
+ <label class="form-label" for="action-transition-field">State field</label>
122
+ <select id="action-transition-field" class="form-input" data-model="transitionField.value"></select>
123
+ <small class="aex-err" data-bind-hidden="!err.value.transitionField" data-bind-text="err.value.transitionField"></small>
124
+ </div>
125
+ <div>
126
+ <label class="form-label" for="action-transition-to">To</label>
127
+ <input id="action-transition-to" type="text" class="form-input" list="action-transition-values" placeholder="e.g. approved" data-model="transitionTo.value">
128
+ </div>
129
+ <div class="aex-wide">
130
+ <label class="form-label" for="action-transition-from">From (comma-separated)</label>
131
+ <input id="action-transition-from" type="text" class="form-input" placeholder="e.g. submitted, reviewed" data-model="transitionFrom.value">
132
+ <small class="aex-hint" data-bind-hidden="!stateValuesText.value" data-bind-text="stateValuesText.value"></small>
133
+ <datalist id="action-transition-values"></datalist>
134
+ </div>
86
135
  </div>
87
136
  </div>
88
137
  </div>
89
- </div>
90
138
 
91
- <!-- Steps tab -->
92
- <div class="tab-panel">
93
- <div class="card mb-3">
94
- <div class="card-header" style="display:flex;justify-content:space-between;align-items:center;">
95
- <h2>Steps</h2>
96
- <div style="display:flex;gap:.5rem;">
97
- <select id="add-step-type" class="form-input form-input--sm">
98
- <option value="updateField">Update Field</option>
99
- <option value="deleteEntry">Delete Entry</option>
100
- <option value="moveToCollection">Move to Collection</option>
101
- <option value="webhook">Webhook</option>
102
- <option value="email">Email</option>
139
+ <!-- Steps -->
140
+ <div class="tab-panel">
141
+ <div class="aex-card">
142
+ <div class="aex-card-title">Steps
143
+ <span data-help-title="Steps" data-help="What the action does, in order. Right-click a step (or ⋮, or Shift+F10 on its header) to move it up or down, duplicate or remove it. Nothing is saved until you Save."></span>
144
+ <span class="aex-spacer"></span>
145
+ <select id="add-step-type" class="form-input aex-add-type" aria-label="Step to add" data-model="addType.value">
146
+ <option value="updateField">Update a field</option>
147
+ <option value="deleteEntry">Delete the entry</option>
148
+ <option value="moveToCollection">Move to another collection</option>
149
+ <option value="webhook">Call a webhook</option>
150
+ <option value="email">Send an email</option>
103
151
  </select>
104
- <button id="add-step-btn" class="btn btn-ghost btn-sm">
105
- <span data-icon="plus"></span> Add Step
106
- </button>
152
+ <button type="button" class="btn btn-sm btn-primary" data-on-click="addStep"><span data-icon="plus" data-icon-size="14"></span> Add step</button>
107
153
  </div>
154
+ <p class="aex-note" data-bind-hidden="stepCount.value">No steps yet - add one to say what this action does.</p>
155
+ <div class="aex-steps" id="action-steps-list"></div>
108
156
  </div>
109
- <div class="card-body">
110
- <div id="action-steps-list">
111
- <p class="text-muted steps-empty-placeholder" style="text-align:center;padding:2rem 0;">
112
- No steps yet. Add a step to define what this action does.
113
- </p>
114
- </div>
115
- </div>
116
- </div>
117
- <div class="card">
118
- <div class="card-body" style="font-size:.8rem;color:var(--dm-text-muted,#888);">
119
- <strong>Template variables:</strong>
120
- <code>{{entry.data.fieldName}}</code> - entry field value &nbsp;|&nbsp;
121
- <code>{{now}}</code> - current ISO timestamp &nbsp;|&nbsp;
122
- <code>{{user.name}}</code> - executing user's name &nbsp;|&nbsp;
123
- <code>{{env.CMS_PUBLIC_*}}</code> - public environment variables
157
+ <div class="aex-card">
158
+ <div class="aex-card-title">Template variables
159
+ <span data-help="Any text in a step may use these; they are filled in when the action runs, for the entry it runs on and the person running it."></span></div>
160
+ <!-- Filled from the view as text, after binding: {{…}} in the page would read as bindings. -->
161
+ <table class="aex-vars"></table>
124
162
  </div>
125
163
  </div>
126
- </div>
127
164
 
128
- <!-- Access tab -->
129
- <div class="tab-panel">
130
- <div class="card mb-3">
131
- <div class="card-body" style="max-width:500px;">
132
- <label class="form-label">Allowed Roles</label>
133
- <small class="text-muted" style="display:block;margin-bottom:.75rem;">
134
- Select which roles can trigger this action.
135
- </small>
136
- <div id="action-roles-checkboxes" style="display:flex;flex-direction:column;gap:.5rem;"></div>
165
+ <!-- Access -->
166
+ <div class="tab-panel">
167
+ <div class="aex-card">
168
+ <div class="aex-card-title">Who can run it
169
+ <span data-help-title="Allowed roles" data-help="A role here lets its holders run the action - and everyone in a more senior role too (the check compares role levels). Roles come from System › Roles; one the action names that no longer exists is kept and flagged."></span></div>
170
+ <div class="aex-roles" data-each="roleRows.value key=name"></div>
171
+ <small class="aex-err" data-bind-hidden="!err.value.roles" data-bind-text="err.value.roles"></small>
137
172
  </div>
138
- </div>
139
- <div class="card">
140
- <div class="card-header"><h2>Row-Level Access</h2></div>
141
- <div class="card-body" style="max-width:500px;display:flex;flex-direction:column;gap:1rem;">
142
- <div>
143
- <label style="display:flex;align-items:center;gap:.5rem;cursor:pointer;">
144
- <input type="checkbox" id="action-rowlevel-enabled">
145
- <span>Restrict to specific entries</span>
146
- </label>
147
- <small class="text-muted" style="display:block;margin-top:.4rem;">
148
- When enabled, users can only run this action on entries they own or are assigned to.
149
- Admins always bypass this restriction.
150
- </small>
173
+ <div class="aex-card">
174
+ <div class="aex-setting">
175
+ <label class="form-label" for="action-rowlevel-enabled">Only on some entries
176
+ <span data-help-title="Row-level access" data-help="Limits a user to entries they own (they created it) or that name them in a field (Field match). Admins always bypass this."></span></label>
177
+ <input id="action-rowlevel-enabled" type="checkbox" class="form-switch" data-model="rowLevelEnabled.value">
151
178
  </div>
152
- <div id="action-rowlevel-config" style="display:none;flex-direction:column;gap:1rem;">
179
+ <div class="aex-grid" data-bind-hidden="!rowLevelEnabled.value">
153
180
  <div>
154
- <label class="form-label">Mode</label>
155
- <select id="action-rowlevel-mode" class="form-input">
156
- <option value="owner">Owner - entry was created by the current user</option>
157
- <option value="field">Field Match - a field on the entry matches the user</option>
181
+ <label class="form-label" for="action-rowlevel-mode">Rule</label>
182
+ <select id="action-rowlevel-mode" class="form-input" data-model="rowLevelMode.value">
183
+ <option value="owner">Owner - they created the entry</option>
184
+ <option value="field">Field match - a field of the entry names them</option>
158
185
  </select>
159
186
  </div>
160
- <div id="action-rowlevel-field-group" style="display:none;">
161
- <label class="form-label">Field Name</label>
162
- <input id="action-rowlevel-field" type="text" class="form-input"
163
- placeholder="e.g. assigned_to">
164
- <small class="text-muted">The entry field whose value must match the user property
165
- below.</small>
166
- </div>
167
187
  <div>
168
- <label class="form-label">User Property</label>
169
- <select id="action-rowlevel-userkey" class="form-input">
170
- <option value="id">id (UUID)</option>
188
+ <label class="form-label" for="action-rowlevel-userkey">Compare with their</label>
189
+ <select id="action-rowlevel-userkey" class="form-input" data-model="rowLevelUserKey.value">
190
+ <option value="id">id</option>
171
191
  <option value="email">email</option>
172
192
  <option value="name">name</option>
173
193
  <option value="role">role</option>
174
194
  </select>
175
- <small class="text-muted">Which property of the logged-in user to compare against.</small>
195
+ </div>
196
+ <div class="aex-wide" data-bind-hidden="rowLevelMode.value !== 'field'">
197
+ <label class="form-label" for="action-rowlevel-field">Entry field</label>
198
+ <input id="action-rowlevel-field" type="text" class="form-input aex-mono" list="action-collection-fields" placeholder="e.g. assigned_to" data-model="rowLevelField.value">
199
+ <datalist id="action-collection-fields"></datalist>
200
+ <small class="aex-err" data-bind-hidden="!err.value.rowLevelField" data-bind-text="err.value.rowLevelField"></small>
176
201
  </div>
177
202
  </div>
178
203
  </div>
179
204
  </div>
180
205
  </div>
181
-
182
206
  </div>
183
207
  </div>
208
+
209
+ <style>
210
+ .aex { display: flex; flex-direction: column; gap: .75rem; }
211
+ .aex [hidden] { display: none !important; }
212
+ .aex-alert { margin: 0; display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; padding: .6rem .8rem; border-radius: var(--dm-radius-md);
213
+ background: color-mix(in srgb, var(--dm-warning) 16%, transparent); color: var(--dm-text); }
214
+ .aex-alert.is-dirty { background: color-mix(in srgb, var(--dm-primary) 12%, transparent); }
215
+ .aex-alert.is-bad { background: color-mix(in srgb, var(--dm-danger) 12%, transparent); }
216
+ .aex-spacer { flex: 1; }
217
+ .aex-count { opacity: .65; font-size: var(--dm-text-xs); margin-left: .2rem; }
218
+ .aex-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .7rem; margin-top: .75rem; }
219
+ .aex-card-title { display: flex; align-items: center; gap: .4rem; font-weight: 600; flex-wrap: wrap; }
220
+ .aex-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem 1rem; }
221
+ .aex-wide { grid-column: 1 / -1; }
222
+ .aex .form-label { display: flex; align-items: center; gap: .3rem; }
223
+ .aex-setting { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
224
+ .aex-setting .form-label { margin: 0; }
225
+ .aex-req { color: var(--dm-danger); }
226
+ .aex-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
227
+ .aex-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); margin-top: .25rem; }
228
+ .aex-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
229
+ .aex-mono { font-family: var(--dm-font-mono, monospace); }
230
+ .aex-add-type { width: auto; max-width: 16rem; }
231
+ .aex-button-preview { display: inline-flex; align-items: center; gap: .4rem; padding: .35rem .8rem; border-radius: var(--dm-radius-md);
232
+ background: var(--dm-primary); color: var(--dm-primary-text); font-size: var(--dm-text-sm); font-weight: 600; }
233
+ .aex-preview-icon { display: inline-flex; width: 14px; height: 14px; }
234
+ .aex-preview-icon svg { width: 100%; height: 100%; }
235
+ .aex-steps { display: flex; flex-direction: column; gap: .6rem; }
236
+ .aex-step { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); background: var(--dm-surface); }
237
+ .aex-step.is-bad { border-color: color-mix(in srgb, var(--dm-danger) 60%, var(--dm-border)); }
238
+ .aex-step-head { display: flex; align-items: center; gap: .6rem; padding: .5rem .7rem; border-bottom: 1px solid var(--dm-border); cursor: default; }
239
+ .aex-step-head:focus-visible { outline: 2px solid var(--dm-primary); outline-offset: -2px; }
240
+ .aex-step-n { display: inline-flex; align-items: center; justify-content: center; min-width: 1.5rem; height: 1.5rem; border-radius: 999px; font-size: var(--dm-text-xs);
241
+ font-weight: 700; background: color-mix(in srgb, var(--dm-primary) 14%, transparent); color: var(--dm-primary); }
242
+ .aex-step-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
243
+ .aex-step-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
244
+ .aex-step-body { padding: .65rem .7rem; display: flex; flex-direction: column; gap: .6rem; }
245
+ .aex-step-body textarea { font-family: var(--dm-font-mono, monospace); font-size: var(--dm-text-sm); resize: vertical; }
246
+ .aex-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); color: var(--dm-text-muted); border-radius: var(--dm-radius-sm); padding: 0 .3rem; }
247
+ .aex-kebab:hover { color: var(--dm-primary); }
248
+ .aex-vars { width: 100%; border-collapse: collapse; font-size: var(--dm-text-sm); }
249
+ .aex-vars td { padding: .2rem .6rem .2rem 0; vertical-align: top; }
250
+ .aex-vars td:last-child { color: var(--dm-text-muted); }
251
+ .aex-roles { display: flex; flex-direction: column; }
252
+ .aex-role { display: flex; align-items: center; gap: .6rem; padding: .35rem .25rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
253
+ .aex-role:last-child { border-bottom: 0; }
254
+ .aex-role .aex-step-main { flex: 1; }
255
+ .aex-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .5rem; border-radius: 999px; white-space: nowrap;
256
+ background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
257
+ .aex-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
258
+ @media (max-width: 720px) { .aex-grid { grid-template-columns: 1fr; } }
259
+ </style>