domma-cms 0.76.0 → 0.77.1

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,143 +1,221 @@
1
- <div class="view-header">
2
- <h1 id="editor-title"><span data-icon="database"></span> <span id="editor-title-text">New Collection</span></h1>
3
- <div style="display:flex;gap:.5rem;align-items:center;">
4
- <a href="#/collections" class="btn btn-ghost btn-sm">
5
- <span data-icon="arrow-left"></span> All Collections
6
- </a>
7
- <button id="save-collection-btn" class="btn btn-primary">
8
- <span data-icon="save"></span> Save
9
- </button>
1
+ <!-- Collection editor. Bound with M.applyBindings - see admin/js/views/collection-editor.js, which also inserts
2
+ the field row template (the router renders a templateUrl through _.render, eating any {{…}}).
3
+ No page heading: the Tool banner above is the heading ("Collections › <title>"); Save, All collections and
4
+ Open entries are banner actions. -->
5
+ <div class="cex">
6
+ <div class="cex-unsaved" data-bind-hidden="!dirty.value">
7
+ <span data-icon="alert-circle" data-icon-size="15"></span>
8
+ <strong>Unsaved changes</strong>
9
+ <small data-bind-hidden="!problemCount.value" data-bind-text="problemText.value"></small>
10
+ <span class="cex-spacer"></span>
11
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="discard">Discard</button>
12
+ <button type="button" class="btn btn-sm btn-primary" data-on-click="save" data-bind-disabled="saving.value">
13
+ <span data-icon="save" data-icon-size="13"></span> Save</button>
10
14
  </div>
11
- </div>
12
15
 
13
- <div class="tabs" id="collection-tabs">
14
- <div class="tab-list">
15
- <button class="tab-item active">Settings</button>
16
- <button class="tab-item">Fields</button>
17
- <button class="tab-item">API &amp; Export</button>
18
- <button class="tab-item" id="storage-tab-btn" style="display:none;">Storage</button>
19
- </div>
20
- <div class="tab-content">
16
+ <div class="tabs cex-tabs">
17
+ <div class="tab-list">
18
+ <button type="button" class="tab-item active">Settings <i class="cex-dot" data-bind-hidden="!settingsBad.value" title="Something here needs fixing"></i></button>
19
+ <button type="button" class="tab-item">Fields <b class="cex-count" data-bind-text="fieldCount.value"></b> <i class="cex-dot" data-bind-hidden="!fieldsBad.value" title="A field needs fixing"></i></button>
20
+ <button type="button" class="tab-item">API &amp; Export <i class="cex-dot" data-bind-hidden="!exportBad.value" title="Something here needs fixing"></i></button>
21
+ <button type="button" class="tab-item cex-storage-tab" data-bind-hidden="!showStorage.value">Storage</button>
22
+ </div>
21
23
 
22
- <!-- Settings tab -->
24
+ <!-- Settings -->
23
25
  <div class="tab-panel active">
24
- <div class="card mb-4">
25
- <div class="card-body">
26
- <div class="row mb-3">
27
- <div class="col-7">
28
- <label class="form-label">Title</label>
29
- <input id="field-title" type="text" class="form-input" placeholder="e.g. Products">
30
- </div>
31
- <div class="col-5">
32
- <label class="form-label">Slug</label>
33
- <input id="field-slug" type="text" class="form-input" placeholder="products">
34
- <p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;" id="slug-hint">Auto-generated from title. Cannot be changed after creation.</p>
35
- </div>
26
+ <div class="cex-card">
27
+ <div class="cex-card-title">Collection
28
+ <span data-help-title="Collections" data-help="A collection is a set of entries that share the same fields - products, bookings, team members. Its entries are edited under Data › Collections, shown on pages with the [collection] shortcode, and can be read by the public API if you allow it on the API &amp; Export tab."></span></div>
29
+ <div class="cex-grid">
30
+ <div class="cex-wide">
31
+ <label class="form-label" for="field-title">Title <span class="text-danger">*</span></label>
32
+ <input id="field-title" type="text" class="form-input" maxlength="120" placeholder="e.g. Products" data-model="title.value">
33
+ <small class="cex-err" data-bind-hidden="!errTitle.value" data-bind-text="errTitle.value"></small>
34
+ </div>
35
+ <div>
36
+ <label class="form-label" for="field-slug">Slug
37
+ <span data-help-title="Slug" data-help="The collection's id - in its admin address, its API address (/api/collections/&lt;slug&gt;/public) and the [collection] shortcode. Made from the title as you type; it cannot be changed once the collection exists."></span></label>
38
+ <input id="field-slug" type="text" class="form-input font-mono" maxlength="80" placeholder="products" data-model="slug.value" data-bind-disabled="!isNew.value">
39
+ <small class="cex-err" data-bind-hidden="!errSlug.value" data-bind-text="errSlug.value"></small>
40
+ <small class="cex-hint" data-bind-hidden="errSlug.value" data-bind-text="slugHint.value"></small>
41
+ </div>
42
+ <div>
43
+ <label class="form-label" for="collection-project">Project
44
+ <span data-help-title="Project" data-help="Files the collection under a project, for the sidebar and for users limited to certain projects. Left empty it belongs to Core, which everyone can see."></span></label>
45
+ <select id="collection-project" class="form-input" data-model="project.value">
46
+ <option value="">- none (Core) -</option>
47
+ </select>
36
48
  </div>
37
- <div class="row">
38
- <div class="col">
39
- <label class="form-label">Description</label>
40
- <textarea id="field-description" class="form-input" rows="2" placeholder="Optional description…"></textarea>
41
- </div>
49
+ <div class="cex-wide">
50
+ <label class="form-label" for="field-description">Description</label>
51
+ <textarea id="field-description" class="form-input" rows="2" maxlength="500" placeholder="Optional - what the entries are for" data-model="description.value"></textarea>
42
52
  </div>
43
- <div class="row mt-3">
44
- <div class="col-6">
45
- <label class="form-label">Entry Form Layout</label>
46
- <select id="collection-layout" class="form-input">
47
- <option value="stacked">Stacked</option>
48
- <option value="grid">Grid</option>
49
- </select>
50
- </div>
51
- <div class="col-6" id="collection-columns-group" style="display:none;">
52
- <label class="form-label">Columns</label>
53
- <input id="collection-columns" type="number" class="form-input" min="1" max="6" value="2">
54
- </div>
53
+ </div>
54
+ </div>
55
+
56
+ <div class="cex-card">
57
+ <div class="cex-card-title">Entry form
58
+ <span data-help-title="Entry form" data-help="How the form for adding and editing an entry is laid out. Stacked puts one field per row; Grid places them in columns, and each field can span several columns or the full width (set on the field)."></span></div>
59
+ <div class="cex-grid">
60
+ <div>
61
+ <label class="form-label" for="collection-layout">Layout</label>
62
+ <select id="collection-layout" class="form-input" data-model="layout.value">
63
+ <option value="stacked">Stacked - one field per row</option>
64
+ <option value="grid">Grid - fields in columns</option>
65
+ </select>
55
66
  </div>
56
- <div class="row mt-3">
57
- <div class="col-6">
58
- <label class="form-label">Project</label>
59
- <select id="collection-project" class="form-input">
60
- <option value="">- none -</option>
61
- </select>
62
- <p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">Tag this collection to a project for grouping.</p>
63
- </div>
67
+ <div data-bind-hidden="layout.value !== 'grid'">
68
+ <label class="form-label" for="collection-columns">Columns</label>
69
+ <input id="collection-columns" type="number" class="form-input" min="1" max="6" data-model="columns.value">
70
+ <small class="cex-err" data-bind-hidden="!errColumns.value" data-bind-text="errColumns.value"></small>
64
71
  </div>
65
- <div class="row mt-3">
66
- <div class="col-auto">
67
- <label class="form-check-label" title="Included in fresh installs via the seed script">
68
- <input id="collection-bundled" type="checkbox" class="form-check"> Bundled
69
- </label>
70
- </div>
72
+ <div class="cex-setting cex-wide">
73
+ <label class="form-label" for="collection-bundled">Bundled
74
+ <span data-help-title="Bundled" data-help="Ships with the site: the seed script includes it in a fresh install. Leave it off for a collection that only this site needs."></span></label>
75
+ <input id="collection-bundled" type="checkbox" class="form-switch" data-model="bundled.value">
71
76
  </div>
72
77
  </div>
73
78
  </div>
74
79
  </div>
75
80
 
76
- <!-- Fields tab -->
81
+ <!-- Fields -->
77
82
  <div class="tab-panel">
78
- <div class="card mb-4">
79
- <div class="card-header" style="display:flex;justify-content:space-between;align-items:center;">
80
- <h2>Fields</h2>
81
- <button id="add-field-btn" class="btn btn-ghost btn-sm" type="button">
82
- <span data-icon="plus"></span> Add Field
83
- </button>
84
- </div>
85
- <div class="card-body" style="padding:0;">
86
- <div id="fields-list" style="padding:1rem;">
87
- <p class="text-muted" id="fields-empty-msg" style="text-align:center;padding:2rem 0;">No fields yet. Click "Add Field" to get started.</p>
88
- </div>
83
+ <div class="cex-card">
84
+ <div class="cex-card-title">Fields
85
+ <span data-help-title="Fields" data-help="What every entry holds. Click a field to edit it; drag it by its grip, or right-click it (or ⋮, or Shift+F10) to move it up or down, duplicate it or remove it. Alt+↑ / Alt+↓ move the focused field. Changes are kept here until you Save."></span>
86
+ <span class="cex-spacer"></span>
87
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="addField"><span data-icon="plus" data-icon-size="13"></span> Add field</button>
89
88
  </div>
89
+ <p class="cex-note" data-bind-hidden="fieldCount.value">No fields yet - Add field to give entries something to hold.</p>
90
+ <div class="cex-list" data-each="rows.value key=key"></div>
90
91
  </div>
91
92
  </div>
92
93
 
93
- <!-- API Access tab -->
94
+ <!-- API & Export -->
94
95
  <div class="tab-panel">
95
- <div class="card mb-4">
96
- <div class="card-header">
97
- <h2>Public API Access</h2>
96
+ <div class="cex-card">
97
+ <div class="cex-card-title">Public API
98
+ <span data-help-title="Public API" data-help="What the public API may do with this collection, and who may do it: Public is anyone; Token needs an API token (System › API Tokens); the others need a signed-in user with at least that role. The admin itself is always governed by roles and permissions, not by these switches."></span></div>
99
+ <p class="cex-note">At <code data-bind-text="apiPath.value"></code></p>
100
+ <div class="cex-ops">
101
+ <div class="cex-op"><strong>Create</strong>
102
+ <label class="cex-check"><input type="checkbox" data-model="createEnabled.value"> Allowed</label>
103
+ <select class="form-input" aria-label="Who may create" data-model="createAccess.value" data-bind-disabled="!createEnabled.value"></select></div>
104
+ <div class="cex-op"><strong>Read</strong>
105
+ <label class="cex-check"><input type="checkbox" data-model="readEnabled.value"> Allowed</label>
106
+ <select class="form-input" aria-label="Who may read" data-model="readAccess.value" data-bind-disabled="!readEnabled.value"></select></div>
107
+ <div class="cex-op cex-op-sub">
108
+ <label for="api-read-fields">Read fields
109
+ <span data-help-title="Read fields" data-help="Only these fields are returned by the public and external read endpoints - a comma-separated list of field names. Empty returns every field."></span></label>
110
+ <input id="api-read-fields" type="text" class="form-input" placeholder="Empty = every field" data-model="readFields.value">
111
+ <small class="cex-hint cex-wide" data-bind-hidden="!unknownReadFields.value" data-bind-text="unknownReadFields.value"></small>
112
+ </div>
113
+ <div class="cex-op"><strong>Update</strong>
114
+ <label class="cex-check"><input type="checkbox" data-model="updateEnabled.value"> Allowed</label>
115
+ <select class="form-input" aria-label="Who may update" data-model="updateAccess.value" data-bind-disabled="!updateEnabled.value"></select></div>
116
+ <div class="cex-op"><strong>Delete</strong>
117
+ <label class="cex-check"><input type="checkbox" data-model="deleteEnabled.value"> Allowed</label>
118
+ <select class="form-input" aria-label="Who may delete" data-model="deleteAccess.value" data-bind-disabled="!deleteEnabled.value"></select></div>
98
119
  </div>
99
- <div class="card-body">
100
- <p class="text-muted mb-4" style="font-size:.875rem;">Control which operations are available via the public API (<code>/api/collections/{slug}/public</code>). Admin endpoints are always protected by role.</p>
120
+ </div>
101
121
 
102
- <div id="api-access-rows">
103
- <!-- Rows built by JS -->
122
+ <div class="cex-card">
123
+ <div class="cex-card-title">Export
124
+ <span data-help-title="Export" data-help="Lets visitors download the entries a [collection] display shows, from its right-click menu - as CSV and/or JSON. Who may export is a role: Public is anyone."></span></div>
125
+ <div class="cex-ops">
126
+ <div class="cex-op"><strong>Export</strong>
127
+ <label class="cex-check"><input id="export-enabled" type="checkbox" data-model="exportEnabled.value"> Allowed</label>
128
+ <select id="export-access" class="form-input" aria-label="Who may export" data-model="exportAccess.value" data-bind-disabled="!exportEnabled.value"></select></div>
129
+ <div class="cex-op cex-op-sub"><span>Formats</span>
130
+ <span class="cex-formats">
131
+ <label class="cex-check"><input id="export-format-csv" type="checkbox" data-model="exportCsv.value" data-bind-disabled="!exportEnabled.value"> CSV</label>
132
+ <label class="cex-check"><input id="export-format-json" type="checkbox" data-model="exportJson.value" data-bind-disabled="!exportEnabled.value"> JSON</label>
133
+ </span>
134
+ <small class="cex-err cex-wide" data-bind-hidden="!errExport.value" data-bind-text="errExport.value"></small>
104
135
  </div>
105
136
  </div>
106
137
  </div>
107
138
  </div>
108
139
 
109
- <!-- Storage tab (Pro) -->
110
- <div class="tab-panel" id="storage-panel">
111
- <div class="card">
112
- <div class="card-header"><h2>Storage Adapter</h2></div>
113
- <div class="card-body">
114
- <p class="text-muted mb-4">Choose where this collection's entries are stored. File-based storage is the
115
- default. MongoDB requires Pro mode to be enabled.</p>
116
- <div class="form-group mb-3">
117
- <label class="form-label">Adapter</label>
118
- <button id="storage-adapter-trigger" class="form-input" type="button"
119
- style="text-align:left;cursor:pointer;display:flex;align-items:center;justify-content:space-between;">
120
- <span id="storage-adapter-label">File (default)</span>
121
- <span data-icon="chevron-down" style="opacity:.5;"></span>
122
- </button>
123
- <input type="hidden" id="storage-adapter" value="file">
124
- </div>
125
- <div id="storage-connection-group" class="form-group mb-3" style="display:none;">
126
- <label class="form-label">Connection</label>
127
- <button id="storage-connection-trigger" class="form-input" type="button"
128
- style="text-align:left;cursor:pointer;display:flex;align-items:center;justify-content:space-between;">
129
- <span id="storage-connection-label">default</span>
130
- <span data-icon="chevron-down" style="opacity:.5;"></span>
131
- </button>
132
- <input type="hidden" id="storage-connection" value="default">
133
- <p class="form-text text-muted mt-1">Named connection from config/connections.json</p>
134
- </div>
135
- <div id="storage-migration-warning" class="alert alert-warning mt-3" style="display:none;">
136
- Changing the storage adapter does not migrate existing entries. Export your data before switching.
140
+ <!-- Storage (Pro) -->
141
+ <div class="tab-panel">
142
+ <div class="cex-card">
143
+ <div class="cex-card-title">Storage
144
+ <span data-help-title="Storage" data-help="Where this collection's entries live: in a file on this server (the default), or in MongoDB (Pro). Switching an existing collection asks whether to move its entries across when you Save - moving copies each entry to the new store. Export the entries first if they matter."></span></div>
145
+ <div class="cex-grid">
146
+ <div>
147
+ <label class="form-label" for="storage-adapter">Adapter</label>
148
+ <select id="storage-adapter" class="form-input" data-model="storageAdapter.value">
149
+ <option value="file">File (default)</option>
150
+ <option value="mongodb">MongoDB</option>
151
+ </select>
152
+ </div>
153
+ <div data-bind-hidden="storageAdapter.value !== 'mongodb'">
154
+ <label class="form-label" for="storage-connection">Connection
155
+ <span data-help="A named connection from config/connections.json."></span></label>
156
+ <select id="storage-connection" class="form-input" data-model="storageConnection.value"></select>
157
+ </div>
158
+ </div>
159
+ <p class="cex-warn" data-bind-hidden="!storageNote.value">
160
+ <span data-icon="alert-triangle" data-icon-size="15"></span> <span data-bind-text="storageNote.value"></span>
161
+ </p>
137
162
  </div>
138
- </div>
139
163
  </div>
140
- </div>
141
-
142
164
  </div>
143
165
  </div>
166
+
167
+ <style>
168
+ .cex { display: flex; flex-direction: column; gap: .75rem; }
169
+ .cex [hidden], .cex-panel [hidden] { display: none !important; }
170
+ .cex-unsaved { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: .5rem; padding: .5rem .75rem;
171
+ border: 1px solid color-mix(in srgb, var(--dm-warning) 45%, var(--dm-border)); border-radius: var(--dm-radius-md);
172
+ background: color-mix(in srgb, var(--dm-warning) 12%, var(--dm-surface)); color: var(--dm-text); }
173
+ .cex-unsaved small { color: var(--dm-danger); }
174
+ .cex-unsaved .btn { display: inline-flex; align-items: center; gap: .3rem; }
175
+ .cex-spacer { flex: 1; }
176
+ .cex-tabs .tab-item { display: inline-flex; align-items: center; gap: .3rem; }
177
+ .cex-count { font-weight: 500; opacity: .65; font-size: var(--dm-text-xs); }
178
+ .cex-dot { width: .45rem; height: .45rem; border-radius: 50%; background: var(--dm-danger); display: inline-block; }
179
+ .cex-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; }
180
+ .cex-card-title { display: flex; align-items: center; gap: .35rem; font-weight: 600; }
181
+ .cex-card-title .btn { display: inline-flex; align-items: center; gap: .3rem; font-weight: 500; }
182
+ .cex-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .8rem; }
183
+ .cex-wide { grid-column: 1 / -1; }
184
+ .cex .form-label, .cex-panel .form-label { display: flex; align-items: center; gap: .3rem; }
185
+ .cex-err { display: block; margin-top: .25rem; color: var(--dm-danger); font-size: var(--dm-text-xs); }
186
+ .cex-hint { display: block; margin-top: .25rem; color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
187
+ .cex-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
188
+ .cex-warn { margin: 0; display: flex; gap: .45rem; align-items: flex-start; padding: .55rem .7rem; border-radius: var(--dm-radius-md);
189
+ background: color-mix(in srgb, var(--dm-warning) 14%, transparent); color: var(--dm-text); font-size: var(--dm-text-sm); }
190
+ .cex-setting { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
191
+ .cex-setting .form-label { margin: 0; }
192
+ .cex-list { display: flex; flex-direction: column; }
193
+ .cex-row { display: flex; align-items: center; gap: .6rem; padding: .5rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
194
+ .cex-row:last-child { border-bottom: 0; }
195
+ .cex-row:hover, .cex-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
196
+ .cex-row.is-bad { box-shadow: inset 3px 0 0 var(--dm-danger); }
197
+ .cex-row.is-drag-over { box-shadow: inset 0 2px 0 var(--dm-primary); }
198
+ .cex-row.is-dragging { opacity: .45; }
199
+ .cex-grip { cursor: grab; color: var(--dm-text-muted); user-select: none; padding: 0 .15rem; }
200
+ .cex-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
201
+ .cex-main strong, .cex-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
202
+ .cex-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
203
+ .cex-main small.cex-problem { color: var(--dm-danger); }
204
+ .cex-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;
205
+ background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
206
+ .cex-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
207
+ .cex-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; }
208
+ .cex-kebab:hover { color: var(--dm-primary); }
209
+ .cex-ops { display: flex; flex-direction: column; }
210
+ .cex-op { display: grid; grid-template-columns: 7.5rem 1fr 11rem; gap: .75rem; align-items: center; padding: .5rem 0; border-bottom: 1px solid var(--dm-border); }
211
+ .cex-op:last-child { border-bottom: 0; }
212
+ .cex-op-sub { grid-template-columns: 7.5rem 1fr; padding-left: 1rem; }
213
+ .cex-op-sub > label, .cex-op-sub > span:first-child { color: var(--dm-text-muted); font-size: var(--dm-text-sm); display: flex; align-items: center; gap: .3rem; }
214
+ .cex-check { display: inline-flex; align-items: center; gap: .4rem; cursor: pointer; font-size: var(--dm-text-sm); }
215
+ .cex-formats { display: flex; gap: 1rem; }
216
+ /* The field panel (slideover) */
217
+ .cex-panel { display: flex; flex-direction: column; gap: .85rem; }
218
+ .cex-panel-actions { display: flex; align-items: center; gap: .4rem; padding-top: .6rem; border-top: 1px solid var(--dm-border); }
219
+ .cex-panel-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
220
+ @media (max-width: 720px) { .cex-grid { grid-template-columns: 1fr; } .cex-op { grid-template-columns: 1fr; } }
221
+ </style>
@@ -1,40 +1,103 @@
1
- <div class="view-header">
2
- <h1><span data-icon="database"></span> <span id="entries-title">Entries</span></h1>
3
- <div style="display:flex;gap:.5rem;align-items:center;">
4
- <a href="#/collections" class="btn btn-ghost btn-sm">
5
- <span data-icon="arrow-left"></span> Collections
6
- </a>
7
- <a id="edit-schema-btn" href="#/collections" class="btn btn-ghost btn-sm">
8
- <span data-icon="edit-3"></span> Edit Schema
9
- </a>
10
- <a id="form-btn" href="#/forms" class="btn btn-ghost btn-sm">
11
- <span data-icon="layout"></span> Form
12
- </a>
13
- <button id="import-btn" class="btn btn-ghost btn-sm">
14
- <span data-icon="upload"></span> Import
15
- </button>
16
- <button id="export-btn" class="btn btn-ghost btn-sm">
17
- <span data-icon="download"></span> Export
18
- </button>
19
- <button id="clear-all-btn" class="btn btn-danger btn-sm">
20
- <span data-icon="trash"></span> Clear All
21
- </button>
22
- <button id="add-entry-btn" class="btn btn-primary">
23
- <span data-icon="plus"></span> Add Entry
24
- </button>
25
- </div>
26
- </div>
1
+ <!-- A collection's entries. Bound with M.applyBindings - see admin/views/collection-entries.js, which also
2
+ builds the table's head and row template from the collection's fields (the router renders a templateUrl
3
+ through _.render, eating any {{…}}). No page heading: the Tool banner is the heading (its section is the
4
+ collection's title), and Add / Import / Export / Edit fields / Form are banner actions. -->
5
+ <div class="cex">
6
+ <p class="cex-note" data-bind-hidden="!ready.value">
7
+ <span data-bind-text="introText.value"></span>
8
+ <span data-help-title="Entries" data-help="Click an entry to edit it. Right-click it (or ⋮, or Shift+F10) to see all of it, duplicate it, copy its id, run one of the collection's actions or delete it - and right-click a value to filter by it. Tick entries to delete several at once. Filters and the search box work on the entries loaded; a big collection loads 500 at a time."></span>
9
+ </p>
10
+ <p class="cex-note is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
27
11
 
28
- <div class="card mb-3">
29
- <div class="card-body" style="display:flex;gap:.75rem;align-items:center;flex-wrap:wrap;">
30
- <input id="entry-search" type="text" class="form-input" placeholder="Search entries…" style="flex:1;min-width:200px;">
31
- <span id="entry-count" class="text-muted" style="white-space:nowrap;font-size:.85rem;"></span>
32
- <div id="entry-filter-chips" style="display:none;flex-basis:100%;gap:.4rem;flex-wrap:wrap;align-items:center;"></div>
33
- </div>
34
- </div>
12
+ <div class="cex-card" data-bind-hidden="!ready.value">
13
+ <div class="cex-bar">
14
+ <input type="search" class="form-input cex-search" placeholder="Search entries…" aria-label="Search entries" data-model="query.value">
15
+ <span class="cex-count" data-bind-text="countLine.value"></span>
16
+ <span class="cex-spacer"></span>
17
+ <span class="cex-bulk" data-bind-hidden="!selectedCount.value">
18
+ <strong data-bind-text="selectedText.value"></strong>
19
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="clearSelection">Clear</button>
20
+ <button type="button" class="btn btn-sm btn-danger" data-on-click="deleteSelected" data-bind-disabled="busy.value">
21
+ <span data-icon="trash" data-icon-size="13"></span> Delete</button>
22
+ </span>
23
+ <button type="button" class="btn btn-sm btn-ghost text-danger" data-on-click="clearAll" data-bind-hidden="!canClear.value || !loaded.value.length || selectedCount.value" data-bind-disabled="busy.value">
24
+ Delete all…</button>
25
+ </div>
26
+ <div class="cex-chips" data-bind-hidden="!chips.value.length">
27
+ <span class="cex-chips-list" data-each="chips.value key=key"></span>
28
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="clearFilters">Clear filters</button>
29
+ </div>
30
+
31
+ <p class="cex-note" data-bind-hidden="!loading.value">Loading…</p>
32
+ <p class="cex-note" data-bind-hidden="loading.value || rows.value.length" data-bind-text="emptyText.value"></p>
35
33
 
36
- <div class="card">
37
- <div class="card-body">
38
- <div id="entries-table"></div>
34
+ <div class="cex-scroll" data-bind-hidden="!rows.value.length">
35
+ <table class="cex-table">
36
+ <thead><tr class="cex-head"></tr></thead>
37
+ <tbody class="cex-rows" data-each="rows.value key=id"></tbody>
38
+ </table>
39
+ </div>
40
+ <div class="cex-more" data-bind-hidden="!moreText.value">
41
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="showMore" data-bind-disabled="loading.value">
42
+ <span data-bind-text="moreText.value"></span></button>
43
+ <span data-help="Shown 100 at a time. When a collection has more entries than are loaded, the next 500 come from the server - filters and search only see what is loaded."></span>
44
+ </div>
39
45
  </div>
40
46
  </div>
47
+
48
+ <style>
49
+ .cex { display: flex; flex-direction: column; gap: .75rem; }
50
+ .cex [hidden], .cex-form [hidden] { display: none !important; }
51
+ .cex-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
52
+ .cex-note.is-bad { color: var(--dm-danger); }
53
+ .cex-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .6rem; }
54
+ .cex-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
55
+ .cex-bar .btn, .cex-more .btn { display: inline-flex; align-items: center; gap: .3rem; }
56
+ .cex-search { flex: 1; min-width: 10rem; max-width: 24rem; }
57
+ .cex-count { color: var(--dm-text-muted); font-size: var(--dm-text-sm); white-space: nowrap; }
58
+ .cex-spacer { flex: 1; }
59
+ .cex-bulk { display: inline-flex; align-items: center; gap: .4rem; font-size: var(--dm-text-sm); }
60
+ .cex-chips { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
61
+ .cex-chips-list { display: contents; }
62
+ .cex-chip { display: inline-flex; align-items: center; gap: .35rem; font-size: var(--dm-text-xs); font-weight: 600; padding: .15rem .55rem; border-radius: 999px;
63
+ background: color-mix(in srgb, var(--dm-primary) 14%, transparent); color: var(--dm-primary); }
64
+ .cex-chip button { border: 0; background: none; color: inherit; cursor: pointer; font-size: var(--dm-text-sm); line-height: 1; padding: 0; }
65
+ .cex-scroll { overflow-x: auto; }
66
+ .cex-table { width: 100%; border-collapse: collapse; font-size: var(--dm-text-sm); }
67
+ .cex-table th { text-align: left; font-weight: 600; color: var(--dm-text-muted); font-size: var(--dm-text-xs); padding: .35rem .5rem;
68
+ border-bottom: 1px solid var(--dm-border); white-space: nowrap; }
69
+ .cex-table th button { border: 0; background: none; color: inherit; font: inherit; cursor: pointer; padding: 0; display: inline-flex; gap: .25rem; align-items: center; }
70
+ .cex-table th button:hover { color: var(--dm-text); }
71
+ .cex-table th .cex-sort { opacity: .7; }
72
+ .cex-row { border-bottom: 1px solid var(--dm-border); cursor: pointer; }
73
+ .cex-row:last-child { border-bottom: 0; }
74
+ .cex-row:hover, .cex-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
75
+ .cex-row.is-selected { background: color-mix(in srgb, var(--dm-primary) 8%, transparent); }
76
+ .cex-row td { padding: .45rem .5rem; vertical-align: middle; max-width: 22rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
77
+ .cex-row td.cex-when { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
78
+ .cex-check { width: 1.75rem; }
79
+ .cex-tools { text-align: right; width: 1%; white-space: nowrap; }
80
+ .cex-run { border: 1px solid var(--dm-border); background: none; color: var(--dm-text-muted); border-radius: 999px; font-size: var(--dm-text-xs);
81
+ padding: .05rem .5rem; cursor: pointer; margin-right: .25rem; }
82
+ .cex-run:hover { color: var(--dm-primary); border-color: var(--dm-primary); }
83
+ .cex-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; }
84
+ .cex-kebab:hover { color: var(--dm-primary); }
85
+ .cex-more { display: flex; align-items: center; gap: .4rem; justify-content: center; }
86
+ /* Slideovers */
87
+ .cex-form { display: flex; flex-direction: column; gap: .85rem; }
88
+ .cex-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
89
+ .cex-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); }
90
+ .cex-ok { display: block; color: var(--dm-success); font-size: var(--dm-text-xs); }
91
+ .cex-actions { display: flex; align-items: center; gap: .4rem; padding-top: .6rem; border-top: 1px solid var(--dm-border); }
92
+ .cex-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
93
+ .cex-dirty { font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .5rem; border-radius: 999px;
94
+ background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
95
+ .cex-detail { display: flex; flex-direction: column; gap: .6rem; }
96
+ .cex-detail dt { font-size: var(--dm-text-xs); color: var(--dm-text-muted); font-weight: 600; }
97
+ .cex-detail dd { margin: 0 0 .5rem; word-break: break-word; white-space: pre-wrap; padding-bottom: .5rem; border-bottom: 1px solid var(--dm-border); }
98
+ .cex-meta { font-size: var(--dm-text-xs); color: var(--dm-text-muted); display: flex; flex-direction: column; gap: .2rem; }
99
+ .cex-import { width: 100%; font-family: var(--dm-font-mono, monospace); font-size: var(--dm-text-sm); min-height: 12rem; resize: vertical; }
100
+ .cex-export { display: flex; flex-direction: column; gap: .5rem; }
101
+ .cex-export .btn { justify-content: flex-start; display: inline-flex; gap: .4rem; align-items: center; }
102
+ @media (max-width: 640px) { .cex-row td.cex-when { display: none; } .cex-table th.cex-when { display: none; } }
103
+ </style>