domma-cms 0.2.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/admin/css/admin.css +1 -1200
- package/admin/dist/domma/domma-tools.css +2313 -0
- package/admin/dist/domma/domma-tools.min.js +10 -0
- package/admin/index.html +4 -0
- package/admin/js/api.js +1 -242
- package/admin/js/app.js +9 -279
- package/admin/js/config/sidebar-config.js +1 -115
- package/admin/js/lib/card.js +1 -63
- package/admin/js/lib/image-editor.js +1 -869
- package/admin/js/lib/markdown-toolbar.js +54 -421
- package/admin/js/templates/action-editor.html +171 -0
- package/admin/js/templates/actions-list.html +19 -0
- package/admin/js/templates/api-reference.html +1411 -0
- package/admin/js/templates/block-editor.html +158 -0
- package/admin/js/templates/blocks.html +8 -0
- package/admin/js/templates/collection-editor.html +47 -0
- package/admin/js/templates/collection-entries.html +3 -0
- package/admin/js/templates/collections.html +51 -4
- package/admin/js/templates/documentation.html +258 -0
- package/admin/js/templates/form-editor.html +238 -0
- package/{plugins/form-builder/admin → admin/js}/templates/form-submissions.html +30 -30
- package/{plugins/form-builder/admin/templates/forms-list.html → admin/js/templates/forms.html} +17 -17
- package/admin/js/templates/layouts.html +44 -7
- package/admin/js/templates/login.html +29 -4
- package/admin/js/templates/my-profile.html +17 -0
- package/admin/js/templates/page-editor.html +48 -0
- package/admin/js/templates/pages.html +6 -1
- package/admin/js/templates/pro-docs.html +259 -0
- package/admin/js/templates/role-editor.html +59 -0
- package/admin/js/templates/roles.html +10 -0
- package/admin/js/templates/settings.html +137 -18
- package/admin/js/templates/tutorials.html +81 -0
- package/admin/js/templates/user-editor.html +7 -0
- package/admin/js/templates/users.html +3 -1
- package/admin/js/templates/view-editor.html +201 -0
- package/admin/js/templates/view-preview.html +51 -0
- package/admin/js/templates/views-list.html +19 -0
- package/admin/js/views/action-editor.js +1 -0
- package/admin/js/views/actions-list.js +1 -0
- package/admin/js/views/api-reference.js +1 -0
- package/admin/js/views/block-editor.js +8 -0
- package/admin/js/views/blocks.js +4 -0
- package/admin/js/views/collection-editor.js +3 -487
- package/admin/js/views/collection-entries.js +1 -484
- package/admin/js/views/collections.js +1 -153
- package/admin/js/views/dashboard.js +1 -56
- package/admin/js/views/documentation.js +1 -12
- package/admin/js/views/form-editor.js +8 -0
- package/admin/js/views/form-submissions.js +1 -0
- package/admin/js/views/forms.js +1 -0
- package/admin/js/views/index.js +1 -39
- package/admin/js/views/layouts.js +9 -42
- package/admin/js/views/login.js +7 -251
- package/admin/js/views/media.js +1 -240
- package/admin/js/views/my-profile.js +1 -0
- package/admin/js/views/navigation.js +14 -212
- package/admin/js/views/page-editor.js +72 -661
- package/admin/js/views/pages.js +5 -72
- package/admin/js/views/plugins.js +13 -90
- package/admin/js/views/pro-docs.js +1 -0
- package/admin/js/views/role-editor.js +1 -0
- package/admin/js/views/roles.js +4 -0
- package/admin/js/views/settings.js +3 -199
- package/admin/js/views/tutorials.js +1 -12
- package/admin/js/views/user-editor.js +1 -88
- package/admin/js/views/users.js +4 -76
- package/admin/js/views/view-editor.js +1 -0
- package/admin/js/views/view-preview.js +1 -0
- package/admin/js/views/views-list.js +1 -0
- package/bin/cli.js +1 -1
- package/config/auth.json +2 -17
- package/config/connections.json.bak +9 -0
- package/config/connections.json.example +9 -0
- package/config/navigation.json +15 -0
- package/config/plugins.json +19 -29
- package/config/server.json +6 -6
- package/config/site.json +17 -6
- package/package.json +24 -10
- package/plugins/domma-effects/public/celebrations/core/canvas.js +2 -104
- package/plugins/domma-effects/public/celebrations/core/particles.js +1 -144
- package/plugins/domma-effects/public/celebrations/core/physics.js +1 -166
- package/plugins/domma-effects/public/celebrations/index.js +1 -535
- package/plugins/domma-effects/public/celebrations/themes/christmas.js +1 -1805
- package/plugins/domma-effects/public/celebrations/themes/guy-fawkes.js +1 -1477
- package/plugins/domma-effects/public/celebrations/themes/halloween.js +1 -1837
- package/plugins/domma-effects/public/celebrations/themes/st-andrews.js +1 -1175
- package/plugins/domma-effects/public/celebrations/themes/st-davids.js +1 -1258
- package/plugins/domma-effects/public/celebrations/themes/st-georges.js +1 -1754
- package/plugins/domma-effects/public/celebrations/themes/st-patricks.js +1 -1290
- package/plugins/domma-effects/public/celebrations/themes/valentines.js +1 -1361
- package/plugins/example-analytics/stats.json +21 -12
- package/plugins/theme-roller/admin/templates/theme-roller.html +71 -0
- package/plugins/theme-roller/admin/views/theme-roller-view.js +403 -0
- package/plugins/theme-roller/config.js +1 -0
- package/plugins/theme-roller/plugin.js +233 -0
- package/plugins/theme-roller/plugin.json +31 -0
- package/plugins/theme-roller/public/active-theme.css +0 -0
- package/plugins/theme-roller/public/inject-head-late.html +1 -0
- package/public/css/forms.css +1 -0
- package/public/css/site.css +1 -302
- package/public/js/btt.js +1 -90
- package/public/js/cookie-consent.js +1 -61
- package/public/js/form-logic-engine.js +1 -0
- package/public/js/forms.js +1 -0
- package/public/js/site.js +1 -204
- package/scripts/build.js +194 -129
- package/scripts/pro.js +254 -0
- package/scripts/reset.js +33 -8
- package/scripts/seed.js +343 -78
- package/scripts/setup.js +5 -4
- package/server/middleware/auth.js +136 -97
- package/server/routes/api/actions.js +200 -0
- package/server/routes/api/auth.js +292 -116
- package/server/routes/api/blocks.js +84 -0
- package/server/routes/api/collections.js +88 -23
- package/{plugins/form-builder/plugin.js → server/routes/api/forms.js} +483 -505
- package/server/routes/api/layouts.js +49 -25
- package/server/routes/api/media.js +118 -93
- package/server/routes/api/navigation.js +40 -37
- package/server/routes/api/pages.js +132 -118
- package/server/routes/api/plugins.js +6 -3
- package/server/routes/api/settings.js +104 -89
- package/server/routes/api/users.js +27 -21
- package/server/routes/api/views.js +148 -0
- package/server/routes/public.js +124 -108
- package/server/server.js +269 -173
- package/server/services/actions.js +387 -0
- package/server/services/adapterRegistry.js +98 -0
- package/server/services/adapters/FileAdapter.js +192 -0
- package/server/services/adapters/MongoAdapter.js +220 -0
- package/server/services/blocks.js +162 -0
- package/server/services/collections.js +74 -86
- package/server/services/connectionManager.js +102 -0
- package/server/services/content.js +312 -307
- package/{plugins/form-builder → server/services}/email.js +126 -103
- package/server/services/forms.js +173 -0
- package/server/services/markdown.js +1378 -648
- package/server/services/permissionRegistry.js +173 -0
- package/server/services/presetCollections.js +251 -0
- package/server/services/renderer.js +75 -1
- package/server/services/roles.js +227 -0
- package/server/services/rowAccess.js +104 -0
- package/server/services/userProfiles.js +199 -0
- package/server/services/users.js +281 -212
- package/server/services/views.js +280 -0
- package/server/templates/page.html +119 -113
- package/plugins/form-builder/admin/templates/form-editor.html +0 -171
- package/plugins/form-builder/admin/templates/form-settings.html +0 -29
- package/plugins/form-builder/admin/views/form-editor.js +0 -1442
- package/plugins/form-builder/admin/views/form-settings.js +0 -38
- package/plugins/form-builder/admin/views/form-submissions.js +0 -295
- package/plugins/form-builder/admin/views/forms-list.js +0 -164
- package/plugins/form-builder/config.js +0 -9
- package/plugins/form-builder/data/forms/consent.json +0 -104
- package/plugins/form-builder/data/forms/contact-details.json +0 -63
- package/plugins/form-builder/data/forms/contacts.json +0 -66
- package/plugins/form-builder/data/submissions/consent.json +0 -13
- package/plugins/form-builder/data/submissions/contact-details.json +0 -1
- package/plugins/form-builder/data/submissions/contacts.json +0 -26
- package/plugins/form-builder/plugin.json +0 -52
- package/plugins/form-builder/public/form-logic-engine.js +0 -568
- package/plugins/form-builder/public/inject-body.html +0 -352
- package/plugins/form-builder/public/inject-head.html +0 -58
- package/plugins/form-builder/public/package.json +0 -1
- package/scripts/copy-domma.js +0 -48
|
@@ -1,487 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Field editor mirrors the form-builder pattern (same DOM ID convention) but
|
|
5
|
-
* without page-break or spacer types — collections are pure data stores.
|
|
6
|
-
*/
|
|
7
|
-
import { api } from '../api.js';
|
|
8
|
-
|
|
9
|
-
const FIELD_TYPES = [
|
|
10
|
-
{ value: 'string', label: 'Text (single line)' },
|
|
11
|
-
{ value: 'email', label: 'Email' },
|
|
12
|
-
{ value: 'tel', label: 'Phone' },
|
|
13
|
-
{ value: 'number', label: 'Number' },
|
|
14
|
-
{ value: 'textarea', label: 'Textarea (multi-line)' },
|
|
15
|
-
{ value: 'select', label: 'Dropdown (select)' },
|
|
16
|
-
{ value: 'radio', label: 'Radio buttons' },
|
|
17
|
-
{ value: 'checkbox', label: 'Single checkbox' },
|
|
18
|
-
{ value: 'checkbox-group', label: 'Checkbox group' },
|
|
19
|
-
{ value: 'date', label: 'Date' },
|
|
20
|
-
{ value: 'time', label: 'Time' },
|
|
21
|
-
{ value: 'url', label: 'URL' },
|
|
22
|
-
{ value: 'hidden', label: 'Hidden field' }
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
const OPTION_TYPES = new Set(['select', 'radio', 'checkbox-group']);
|
|
26
|
-
const ACCESS_LEVELS = ['public', 'subscriber', 'editor', 'manager', 'admin'];
|
|
27
|
-
const OPERATIONS = ['create', 'read', 'update', 'delete'];
|
|
28
|
-
|
|
29
|
-
let fields = [];
|
|
30
|
-
let collectionSlug = null;
|
|
31
|
-
let isNew = true;
|
|
32
|
-
|
|
33
|
-
function slugify(str) {
|
|
34
|
-
return str.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function getTypeLabel(type) {
|
|
38
|
-
return FIELD_TYPES.find(t => t.value === type)?.label || type;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
// Collect current field state from DOM
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
|
|
45
|
-
function collectFieldFromDOM(idx) {
|
|
46
|
-
const field = { ...fields[idx] };
|
|
47
|
-
|
|
48
|
-
const label = document.getElementById(`fb-label-${idx}`);
|
|
49
|
-
const name = document.getElementById(`fb-name-${idx}`);
|
|
50
|
-
const type = document.getElementById(`fb-type-${idx}`);
|
|
51
|
-
const required = document.getElementById(`fb-required-${idx}`);
|
|
52
|
-
const placeholder = document.getElementById(`fb-placeholder-${idx}`);
|
|
53
|
-
const helper = document.getElementById(`fb-helper-${idx}`);
|
|
54
|
-
|
|
55
|
-
if (label) field.label = label.value.trim() || field.label;
|
|
56
|
-
if (name) field.name = name.value.trim() || field.name;
|
|
57
|
-
if (type) field.type = type.value || field.type;
|
|
58
|
-
if (required) field.required = required.checked;
|
|
59
|
-
if (placeholder) field.placeholder = placeholder.value.trim();
|
|
60
|
-
if (helper) field.helper = helper.value.trim();
|
|
61
|
-
|
|
62
|
-
if (OPTION_TYPES.has(field.type)) {
|
|
63
|
-
const ta = document.getElementById(`fb-options-${idx}`);
|
|
64
|
-
if (ta) {
|
|
65
|
-
field.options = ta.value.split('\n').filter(l => l.trim()).map(line => {
|
|
66
|
-
const [v, ...rest] = line.split(':');
|
|
67
|
-
return { value: v.trim(), label: rest.join(':').trim() || v.trim() };
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return field;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function collectAllFields() {
|
|
76
|
-
return fields.map((_, idx) => collectFieldFromDOM(idx));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
// Build a field card DOM element
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
|
|
83
|
-
function buildFieldCard(field, idx) {
|
|
84
|
-
const card = document.createElement('div');
|
|
85
|
-
card.className = 'fb-field-card';
|
|
86
|
-
card.dataset.index = idx;
|
|
87
|
-
card.style.cssText = 'border:1px solid var(--border-color,#333);border-radius:8px;margin-bottom:.75rem;overflow:hidden;';
|
|
88
|
-
|
|
89
|
-
// Header
|
|
90
|
-
const header = document.createElement('div');
|
|
91
|
-
header.className = 'fb-field-header';
|
|
92
|
-
header.style.cssText = 'display:flex;align-items:center;gap:.5rem;padding:.6rem .75rem;background:var(--card-header-bg,rgba(255,255,255,.03));cursor:pointer;user-select:none;';
|
|
93
|
-
|
|
94
|
-
const grip = document.createElement('span');
|
|
95
|
-
grip.textContent = '⠿';
|
|
96
|
-
grip.style.cssText = 'cursor:grab;opacity:.4;font-size:1.1rem;';
|
|
97
|
-
|
|
98
|
-
const labelSpan = document.createElement('span');
|
|
99
|
-
labelSpan.className = 'fb-field-summary';
|
|
100
|
-
labelSpan.style.cssText = 'flex:1;font-weight:500;font-size:.9rem;';
|
|
101
|
-
labelSpan.textContent = field.label || '(Untitled field)';
|
|
102
|
-
|
|
103
|
-
const typeSpan = document.createElement('span');
|
|
104
|
-
typeSpan.style.cssText = 'font-size:.75rem;opacity:.5;';
|
|
105
|
-
typeSpan.textContent = getTypeLabel(field.type);
|
|
106
|
-
|
|
107
|
-
const chevron = document.createElement('span');
|
|
108
|
-
chevron.className = 'fb-field-chevron';
|
|
109
|
-
chevron.textContent = '▾';
|
|
110
|
-
chevron.style.cssText = 'opacity:.5;transition:transform .2s;';
|
|
111
|
-
|
|
112
|
-
const delBtn = document.createElement('button');
|
|
113
|
-
delBtn.type = 'button';
|
|
114
|
-
delBtn.textContent = '×';
|
|
115
|
-
delBtn.className = 'btn btn-sm';
|
|
116
|
-
delBtn.style.cssText = 'padding:.15rem .45rem;line-height:1;font-size:1rem;opacity:.6;';
|
|
117
|
-
delBtn.title = 'Remove field';
|
|
118
|
-
delBtn.addEventListener('click', (e) => {
|
|
119
|
-
e.stopPropagation();
|
|
120
|
-
fields.splice(idx, 1);
|
|
121
|
-
renderFields(document.getElementById('fields-list'));
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
header.appendChild(grip);
|
|
125
|
-
header.appendChild(labelSpan);
|
|
126
|
-
header.appendChild(typeSpan);
|
|
127
|
-
header.appendChild(chevron);
|
|
128
|
-
header.appendChild(delBtn);
|
|
129
|
-
|
|
130
|
-
// Body
|
|
131
|
-
const body = document.createElement('div');
|
|
132
|
-
body.className = 'fb-field-body';
|
|
133
|
-
body.style.cssText = 'padding:.75rem;display:none;';
|
|
134
|
-
|
|
135
|
-
// Row 1: Label + Name + Type
|
|
136
|
-
const row1 = document.createElement('div');
|
|
137
|
-
row1.style.cssText = 'display:grid;grid-template-columns:1fr 1fr 1fr;gap:.6rem;margin-bottom:.6rem;';
|
|
138
|
-
|
|
139
|
-
const labelWrap = document.createElement('div');
|
|
140
|
-
const labelLbl = document.createElement('label');
|
|
141
|
-
labelLbl.className = 'form-label';
|
|
142
|
-
labelLbl.textContent = 'Label';
|
|
143
|
-
const labelInput = document.createElement('input');
|
|
144
|
-
labelInput.id = `fb-label-${idx}`;
|
|
145
|
-
labelInput.type = 'text';
|
|
146
|
-
labelInput.className = 'form-input';
|
|
147
|
-
labelInput.value = field.label || '';
|
|
148
|
-
labelInput.addEventListener('input', () => {
|
|
149
|
-
labelSpan.textContent = labelInput.value.trim() || '(Untitled field)';
|
|
150
|
-
const nameEl = document.getElementById(`fb-name-${idx}`);
|
|
151
|
-
if (nameEl && !nameEl.dataset.manual) {
|
|
152
|
-
nameEl.value = slugify(labelInput.value).replace(/-/g, '_');
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
labelWrap.appendChild(labelLbl);
|
|
156
|
-
labelWrap.appendChild(labelInput);
|
|
157
|
-
|
|
158
|
-
const nameWrap = document.createElement('div');
|
|
159
|
-
const nameLbl = document.createElement('label');
|
|
160
|
-
nameLbl.className = 'form-label';
|
|
161
|
-
nameLbl.textContent = 'Name (key)';
|
|
162
|
-
const nameInput = document.createElement('input');
|
|
163
|
-
nameInput.id = `fb-name-${idx}`;
|
|
164
|
-
nameInput.type = 'text';
|
|
165
|
-
nameInput.className = 'form-input';
|
|
166
|
-
nameInput.value = field.name || '';
|
|
167
|
-
nameInput.addEventListener('input', () => { nameInput.dataset.manual = '1'; });
|
|
168
|
-
nameWrap.appendChild(nameLbl);
|
|
169
|
-
nameWrap.appendChild(nameInput);
|
|
170
|
-
|
|
171
|
-
const typeWrap = document.createElement('div');
|
|
172
|
-
const typeLbl = document.createElement('label');
|
|
173
|
-
typeLbl.className = 'form-label';
|
|
174
|
-
typeLbl.textContent = 'Type';
|
|
175
|
-
const typeSelect = document.createElement('select');
|
|
176
|
-
typeSelect.id = `fb-type-${idx}`;
|
|
177
|
-
typeSelect.className = 'form-input';
|
|
178
|
-
FIELD_TYPES.forEach(ft => {
|
|
179
|
-
const opt = document.createElement('option');
|
|
180
|
-
opt.value = ft.value;
|
|
181
|
-
opt.textContent = ft.label;
|
|
182
|
-
if (ft.value === field.type) opt.selected = true;
|
|
183
|
-
typeSelect.appendChild(opt);
|
|
184
|
-
});
|
|
185
|
-
typeSelect.addEventListener('change', () => {
|
|
186
|
-
typeSpan.textContent = getTypeLabel(typeSelect.value);
|
|
187
|
-
const optWrap = body.querySelector('.fb-options-wrap');
|
|
188
|
-
if (optWrap) optWrap.style.display = OPTION_TYPES.has(typeSelect.value) ? '' : 'none';
|
|
189
|
-
});
|
|
190
|
-
typeWrap.appendChild(typeLbl);
|
|
191
|
-
typeWrap.appendChild(typeSelect);
|
|
192
|
-
|
|
193
|
-
row1.appendChild(labelWrap);
|
|
194
|
-
row1.appendChild(nameWrap);
|
|
195
|
-
row1.appendChild(typeWrap);
|
|
196
|
-
|
|
197
|
-
// Row 2: Placeholder + Helper + Required
|
|
198
|
-
const row2 = document.createElement('div');
|
|
199
|
-
row2.style.cssText = 'display:grid;grid-template-columns:1fr 1fr auto;gap:.6rem;align-items:end;margin-bottom:.6rem;';
|
|
200
|
-
|
|
201
|
-
const phWrap = document.createElement('div');
|
|
202
|
-
const phLbl = document.createElement('label');
|
|
203
|
-
phLbl.className = 'form-label';
|
|
204
|
-
phLbl.textContent = 'Placeholder';
|
|
205
|
-
const phInput = document.createElement('input');
|
|
206
|
-
phInput.id = `fb-placeholder-${idx}`;
|
|
207
|
-
phInput.type = 'text';
|
|
208
|
-
phInput.className = 'form-input';
|
|
209
|
-
phInput.value = field.placeholder || '';
|
|
210
|
-
phWrap.appendChild(phLbl);
|
|
211
|
-
phWrap.appendChild(phInput);
|
|
212
|
-
|
|
213
|
-
const hlWrap = document.createElement('div');
|
|
214
|
-
const hlLbl = document.createElement('label');
|
|
215
|
-
hlLbl.className = 'form-label';
|
|
216
|
-
hlLbl.textContent = 'Helper text';
|
|
217
|
-
const hlInput = document.createElement('input');
|
|
218
|
-
hlInput.id = `fb-helper-${idx}`;
|
|
219
|
-
hlInput.type = 'text';
|
|
220
|
-
hlInput.className = 'form-input';
|
|
221
|
-
hlInput.value = field.helper || '';
|
|
222
|
-
hlWrap.appendChild(hlLbl);
|
|
223
|
-
hlWrap.appendChild(hlInput);
|
|
224
|
-
|
|
225
|
-
const reqWrap = document.createElement('div');
|
|
226
|
-
reqWrap.style.cssText = 'padding-bottom:.35rem;';
|
|
227
|
-
const reqLabel = document.createElement('label');
|
|
228
|
-
reqLabel.style.cssText = 'display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;';
|
|
229
|
-
const reqCheck = document.createElement('input');
|
|
230
|
-
reqCheck.id = `fb-required-${idx}`;
|
|
231
|
-
reqCheck.type = 'checkbox';
|
|
232
|
-
reqCheck.checked = !!field.required;
|
|
233
|
-
reqLabel.appendChild(reqCheck);
|
|
234
|
-
reqLabel.appendChild(document.createTextNode('Required'));
|
|
235
|
-
reqWrap.appendChild(reqLabel);
|
|
236
|
-
|
|
237
|
-
row2.appendChild(phWrap);
|
|
238
|
-
row2.appendChild(hlWrap);
|
|
239
|
-
row2.appendChild(reqWrap);
|
|
240
|
-
|
|
241
|
-
// Options (only for select/radio/checkbox-group)
|
|
242
|
-
const optWrap = document.createElement('div');
|
|
243
|
-
optWrap.className = 'fb-options-wrap';
|
|
244
|
-
optWrap.style.display = OPTION_TYPES.has(field.type) ? '' : 'none';
|
|
245
|
-
const optLbl = document.createElement('label');
|
|
246
|
-
optLbl.className = 'form-label';
|
|
247
|
-
optLbl.textContent = 'Options (one per line: value: Label)';
|
|
248
|
-
const optTa = document.createElement('textarea');
|
|
249
|
-
optTa.id = `fb-options-${idx}`;
|
|
250
|
-
optTa.className = 'form-input';
|
|
251
|
-
optTa.rows = 4;
|
|
252
|
-
optTa.value = (field.options || []).map(o => `${o.value}: ${o.label}`).join('\n');
|
|
253
|
-
optWrap.appendChild(optLbl);
|
|
254
|
-
optWrap.appendChild(optTa);
|
|
255
|
-
|
|
256
|
-
body.appendChild(row1);
|
|
257
|
-
body.appendChild(row2);
|
|
258
|
-
body.appendChild(optWrap);
|
|
259
|
-
|
|
260
|
-
// Toggle expand/collapse
|
|
261
|
-
header.addEventListener('click', () => {
|
|
262
|
-
const expanded = body.style.display !== 'none';
|
|
263
|
-
body.style.display = expanded ? 'none' : '';
|
|
264
|
-
chevron.style.transform = expanded ? '' : 'rotate(180deg)';
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
card.appendChild(header);
|
|
268
|
-
card.appendChild(body);
|
|
269
|
-
return card;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// ---------------------------------------------------------------------------
|
|
273
|
-
// Render all fields
|
|
274
|
-
// ---------------------------------------------------------------------------
|
|
275
|
-
|
|
276
|
-
function renderFields(listEl) {
|
|
277
|
-
if (!listEl) return;
|
|
278
|
-
listEl.textContent = '';
|
|
279
|
-
|
|
280
|
-
if (fields.length === 0) {
|
|
281
|
-
const msg = document.createElement('p');
|
|
282
|
-
msg.className = 'text-muted';
|
|
283
|
-
msg.id = 'fields-empty-msg';
|
|
284
|
-
msg.style.cssText = 'text-align:center;padding:2rem 0;';
|
|
285
|
-
msg.textContent = 'No fields yet. Click "Add Field" to get started.';
|
|
286
|
-
listEl.appendChild(msg);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
fields.forEach((field, idx) => {
|
|
291
|
-
listEl.appendChild(buildFieldCard(field, idx));
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// ---------------------------------------------------------------------------
|
|
296
|
-
// Build API Access rows
|
|
297
|
-
// ---------------------------------------------------------------------------
|
|
298
|
-
|
|
299
|
-
function buildApiAccessRows(apiConfig, containerEl) {
|
|
300
|
-
containerEl.textContent = '';
|
|
301
|
-
|
|
302
|
-
OPERATIONS.forEach(op => {
|
|
303
|
-
const access = apiConfig?.[op] || { enabled: false, access: 'admin' };
|
|
304
|
-
|
|
305
|
-
const row = document.createElement('div');
|
|
306
|
-
row.style.cssText = 'display:grid;grid-template-columns:140px 1fr 160px;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--border-color,#333);';
|
|
307
|
-
|
|
308
|
-
const opLabel = document.createElement('strong');
|
|
309
|
-
opLabel.textContent = op.charAt(0).toUpperCase() + op.slice(1);
|
|
310
|
-
opLabel.style.cssText = 'font-size:.9rem;';
|
|
311
|
-
|
|
312
|
-
const toggleLabel = document.createElement('label');
|
|
313
|
-
toggleLabel.style.cssText = 'display:flex;align-items:center;gap:.45rem;cursor:pointer;font-size:.875rem;';
|
|
314
|
-
const toggleCheck = document.createElement('input');
|
|
315
|
-
toggleCheck.type = 'checkbox';
|
|
316
|
-
toggleCheck.id = `api-${op}-enabled`;
|
|
317
|
-
toggleCheck.checked = !!access.enabled;
|
|
318
|
-
toggleLabel.appendChild(toggleCheck);
|
|
319
|
-
toggleLabel.appendChild(document.createTextNode('Enable public access'));
|
|
320
|
-
|
|
321
|
-
const accessSelect = document.createElement('select');
|
|
322
|
-
accessSelect.id = `api-${op}-access`;
|
|
323
|
-
accessSelect.className = 'form-input';
|
|
324
|
-
ACCESS_LEVELS.forEach(level => {
|
|
325
|
-
const opt = document.createElement('option');
|
|
326
|
-
opt.value = level;
|
|
327
|
-
opt.textContent = level.charAt(0).toUpperCase() + level.slice(1);
|
|
328
|
-
if (level === access.access) opt.selected = true;
|
|
329
|
-
accessSelect.appendChild(opt);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
row.appendChild(opLabel);
|
|
333
|
-
row.appendChild(toggleLabel);
|
|
334
|
-
row.appendChild(accessSelect);
|
|
335
|
-
containerEl.appendChild(row);
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function collectApiAccess() {
|
|
340
|
-
const result = {};
|
|
341
|
-
OPERATIONS.forEach(op => {
|
|
342
|
-
const enabled = document.getElementById(`api-${op}-enabled`)?.checked ?? false;
|
|
343
|
-
const access = document.getElementById(`api-${op}-access`)?.value || 'admin';
|
|
344
|
-
result[op] = { enabled, access };
|
|
345
|
-
});
|
|
346
|
-
return result;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// ---------------------------------------------------------------------------
|
|
350
|
-
// View export
|
|
351
|
-
// ---------------------------------------------------------------------------
|
|
352
|
-
|
|
353
|
-
export const collectionEditorView = {
|
|
354
|
-
templateUrl: '/admin/js/templates/collection-editor.html',
|
|
355
|
-
|
|
356
|
-
async onMount($container) {
|
|
357
|
-
fields = [];
|
|
358
|
-
collectionSlug = null;
|
|
359
|
-
isNew = true;
|
|
360
|
-
|
|
361
|
-
// Detect edit mode from URL
|
|
362
|
-
const hash = window.location.hash;
|
|
363
|
-
const match = hash.match(/\/collections\/edit\/([^/?#]+)/);
|
|
364
|
-
if (match) {
|
|
365
|
-
collectionSlug = match[1];
|
|
366
|
-
isNew = false;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
E.tabs($container.find('#collection-tabs').get(0));
|
|
370
|
-
|
|
371
|
-
const listEl = $container.find('#fields-list').get(0);
|
|
372
|
-
const apiContainer = $container.find('#api-access-rows').get(0);
|
|
373
|
-
|
|
374
|
-
let apiConfig = {
|
|
375
|
-
create: { enabled: false, access: 'admin' },
|
|
376
|
-
read: { enabled: true, access: 'public' },
|
|
377
|
-
update: { enabled: false, access: 'admin' },
|
|
378
|
-
delete: { enabled: false, access: 'admin' }
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
if (!isNew) {
|
|
382
|
-
try {
|
|
383
|
-
const schema = await api.collections.get(collectionSlug);
|
|
384
|
-
if (!schema) {
|
|
385
|
-
E.toast('Collection not found.', { type: 'error' });
|
|
386
|
-
R.navigate('/collections');
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const titleText = $container.find('#editor-title-text').get(0);
|
|
391
|
-
if (titleText) titleText.textContent = schema.title;
|
|
392
|
-
|
|
393
|
-
$container.find('#field-title').val(schema.title || '');
|
|
394
|
-
$container.find('#field-slug').val(schema.slug || '');
|
|
395
|
-
$container.find('#field-slug').prop('readonly', true);
|
|
396
|
-
$container.find('#slug-hint').get(0).textContent = 'Slug cannot be changed after creation.';
|
|
397
|
-
$container.find('#field-description').val(schema.description || '');
|
|
398
|
-
|
|
399
|
-
fields = schema.fields || [];
|
|
400
|
-
apiConfig = schema.api || apiConfig;
|
|
401
|
-
} catch {
|
|
402
|
-
E.toast('Failed to load collection.', { type: 'error' });
|
|
403
|
-
R.navigate('/collections');
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
} else {
|
|
407
|
-
// Auto-populate slug from title input
|
|
408
|
-
const titleInput = $container.find('#field-title').get(0);
|
|
409
|
-
const slugInput = $container.find('#field-slug').get(0);
|
|
410
|
-
if (titleInput && slugInput) {
|
|
411
|
-
titleInput.addEventListener('input', () => {
|
|
412
|
-
if (!slugInput.dataset.manual) {
|
|
413
|
-
slugInput.value = slugify(titleInput.value);
|
|
414
|
-
}
|
|
415
|
-
});
|
|
416
|
-
slugInput.addEventListener('input', () => { slugInput.dataset.manual = '1'; });
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
renderFields(listEl);
|
|
421
|
-
buildApiAccessRows(apiConfig, apiContainer);
|
|
422
|
-
|
|
423
|
-
// Add field button
|
|
424
|
-
$container.find('#add-field-btn').off('click').on('click', () => {
|
|
425
|
-
fields = collectAllFields();
|
|
426
|
-
fields.push({
|
|
427
|
-
id: `field-${Date.now()}`,
|
|
428
|
-
name: '',
|
|
429
|
-
label: '',
|
|
430
|
-
type: 'string',
|
|
431
|
-
required: false,
|
|
432
|
-
placeholder: '',
|
|
433
|
-
helper: '',
|
|
434
|
-
options: [],
|
|
435
|
-
validation: [],
|
|
436
|
-
logic: null
|
|
437
|
-
});
|
|
438
|
-
renderFields(listEl);
|
|
439
|
-
// Auto-expand and focus the new card
|
|
440
|
-
const cards = listEl.querySelectorAll('.fb-field-card');
|
|
441
|
-
if (cards.length) {
|
|
442
|
-
const last = cards[cards.length - 1];
|
|
443
|
-
const body = last.querySelector('.fb-field-body');
|
|
444
|
-
const chev = last.querySelector('.fb-field-chevron');
|
|
445
|
-
if (body) body.style.display = '';
|
|
446
|
-
if (chev) chev.style.transform = 'rotate(180deg)';
|
|
447
|
-
last.querySelector(`#fb-label-${fields.length - 1}`)?.focus();
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
// Save button
|
|
452
|
-
$container.find('#save-collection-btn').off('click').on('click', async () => {
|
|
453
|
-
const title = $container.find('#field-title').val().trim();
|
|
454
|
-
const slug = $container.find('#field-slug').val().trim();
|
|
455
|
-
const description = $container.find('#field-description').val().trim();
|
|
456
|
-
|
|
457
|
-
if (!title) {
|
|
458
|
-
E.toast('Title is required.', { type: 'warning' });
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
const finalFields = collectAllFields();
|
|
463
|
-
const finalApi = collectApiAccess();
|
|
464
|
-
|
|
465
|
-
const $btn = $container.find('#save-collection-btn');
|
|
466
|
-
$btn.prop('disabled', true);
|
|
467
|
-
try {
|
|
468
|
-
if (isNew) {
|
|
469
|
-
const created = await api.collections.create({ title, slug, description, fields: finalFields, api: finalApi });
|
|
470
|
-
collectionSlug = created.slug;
|
|
471
|
-
isNew = false;
|
|
472
|
-
E.toast('Collection created.', { type: 'success' });
|
|
473
|
-
R.navigate(`/collections/edit/${created.slug}`);
|
|
474
|
-
} else {
|
|
475
|
-
await api.collections.update(collectionSlug, { title, description, fields: finalFields, api: finalApi });
|
|
476
|
-
E.toast('Collection saved.', { type: 'success' });
|
|
477
|
-
}
|
|
478
|
-
} catch (err) {
|
|
479
|
-
E.toast(err.message || 'Failed to save.', { type: 'error' });
|
|
480
|
-
} finally {
|
|
481
|
-
$btn.prop('disabled', false);
|
|
482
|
-
}
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
Domma.icons.scan();
|
|
486
|
-
}
|
|
487
|
-
};
|
|
1
|
+
import{api as D}from"../api.js";const Q=[{value:"string",label:"Text (single line)"},{value:"email",label:"Email"},{value:"tel",label:"Phone"},{value:"number",label:"Number"},{value:"textarea",label:"Textarea (multi-line)"},{value:"select",label:"Dropdown (select)"},{value:"radio",label:"Radio buttons"},{value:"checkbox",label:"Single checkbox"},{value:"checkbox-group",label:"Checkbox group"},{value:"date",label:"Date"},{value:"time",label:"Time"},{value:"url",label:"URL"},{value:"hidden",label:"Hidden field"}],J=new Set(["select","radio","checkbox-group"]),le=["public","subscriber","editor","manager","admin"],X=["create","read","update","delete"];let f=[],x=null,T=!0;function Z(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function $(e){return Q.find(t=>t.value===e)?.label||e}function ne(e){const t={...f[e]},o=document.getElementById(`fb-label-${e}`),n=document.getElementById(`fb-name-${e}`),i=document.getElementById(`fb-type-${e}`),p=document.getElementById(`fb-required-${e}`),r=document.getElementById(`fb-placeholder-${e}`),l=document.getElementById(`fb-helper-${e}`);if(o&&(t.label=o.value.trim()||t.label),n&&(t.name=n.value.trim()||t.name),i&&(t.type=i.value||t.type),p&&(t.required=p.checked),r&&(t.placeholder=r.value.trim()),l&&(t.helper=l.value.trim()),J.has(t.type)){const c=document.getElementById(`fb-options-${e}`);c&&(t.options=c.value.split(`
|
|
2
|
+
`).filter(u=>u.trim()).map(u=>{const[g,...m]=u.split(":");return{value:g.trim(),label:m.join(":").trim()||g.trim()}}))}const a=document.getElementById(`fb-span-${e}`);if(document.getElementById(`fb-fullwidth-${e}`)?.checked)t.fullWidth=!0,delete t.span;else{delete t.fullWidth;const c=parseInt(a?.value,10);c>1?t.span=c:delete t.span}return t}function ee(){return f.map((e,t)=>ne(t))}function ae(e,t){const o=document.createElement("div");o.className="fb-field-card",o.dataset.index=t,o.style.cssText="border:1px solid var(--border-color,#333);border-radius:8px;margin-bottom:.75rem;overflow:hidden;";const n=document.createElement("div");n.className="fb-field-header",n.style.cssText="display:flex;align-items:center;gap:.5rem;padding:.6rem .75rem;background:var(--card-header-bg,rgba(255,255,255,.03));cursor:pointer;user-select:none;";const i=document.createElement("span");i.textContent="\u283F",i.style.cssText="cursor:grab;opacity:.4;font-size:1.1rem;";const p=document.createElement("span");p.className="fb-field-summary",p.style.cssText="flex:1;font-weight:500;font-size:.9rem;",p.textContent=e.label||"(Untitled field)";const r=document.createElement("span");r.style.cssText="font-size:.75rem;opacity:.5;",r.textContent=$(e.type);const l=document.createElement("span");l.className="fb-field-chevron",l.textContent="\u25BE",l.style.cssText="opacity:.5;transition:transform .2s;";const a=document.createElement("button");a.type="button",a.textContent="\xD7",a.className="btn btn-sm",a.style.cssText="padding:.15rem .45rem;line-height:1;font-size:1rem;opacity:.6;",a.title="Remove field",a.addEventListener("click",d=>{d.stopPropagation(),f.splice(t,1),K(document.getElementById("fields-list"))}),n.appendChild(i),n.appendChild(p),n.appendChild(r),n.appendChild(l),n.appendChild(a);const s=document.createElement("div");s.className="fb-field-body",s.style.cssText="padding:.75rem;display:none;";const c=document.createElement("div");c.style.cssText="display:grid;grid-template-columns:1fr 1fr 1fr;gap:.6rem;margin-bottom:.6rem;";const u=document.createElement("div"),g=document.createElement("label");g.className="form-label",g.textContent="Label";const m=document.createElement("input");m.id=`fb-label-${t}`,m.type="text",m.className="form-input",m.value=e.label||"",m.addEventListener("input",()=>{p.textContent=m.value.trim()||"(Untitled field)";const d=document.getElementById(`fb-name-${t}`);d&&!d.dataset.manual&&(d.value=Z(m.value).replace(/-/g,"_"))}),u.appendChild(g),u.appendChild(m);const w=document.createElement("div"),h=document.createElement("label");h.className="form-label",h.textContent="Name (key)";const y=document.createElement("input");y.id=`fb-name-${t}`,y.type="text",y.className="form-input",y.value=e.name||"",y.addEventListener("input",()=>{y.dataset.manual="1"}),w.appendChild(h),w.appendChild(y);const z=document.createElement("div"),P=document.createElement("label");P.className="form-label",P.textContent="Type";const v=document.createElement("select");v.id=`fb-type-${t}`,v.className="form-input",Q.forEach(d=>{const F=document.createElement("option");F.value=d.value,F.textContent=d.label,d.value===e.type&&(F.selected=!0),v.appendChild(F)}),v.addEventListener("change",()=>{r.textContent=$(v.value);const d=s.querySelector(".fb-options-wrap");d&&(d.style.display=J.has(v.value)?"":"none")}),z.appendChild(P),z.appendChild(v),c.appendChild(u),c.appendChild(w),c.appendChild(z);const N=document.createElement("div");N.style.cssText="display:grid;grid-template-columns:1fr 1fr auto;gap:.6rem;align-items:end;margin-bottom:.6rem;";const O=document.createElement("div"),U=document.createElement("label");U.className="form-label",U.textContent="Placeholder";const I=document.createElement("input");I.id=`fb-placeholder-${t}`,I.type="text",I.className="form-input",I.value=e.placeholder||"",O.appendChild(U),O.appendChild(I);const M=document.createElement("div"),_=document.createElement("label");_.className="form-label",_.textContent="Helper text";const k=document.createElement("input");k.id=`fb-helper-${t}`,k.type="text",k.className="form-input",k.value=e.helper||"",M.appendChild(_),M.appendChild(k);const j=document.createElement("div");j.style.cssText="padding-bottom:.35rem;";const B=document.createElement("label");B.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const q=document.createElement("input");q.id=`fb-required-${t}`,q.type="checkbox",q.checked=!!e.required,B.appendChild(q),B.appendChild(document.createTextNode("Required")),j.appendChild(B),N.appendChild(O),N.appendChild(M),N.appendChild(j);const L=document.createElement("div");L.className="fb-options-wrap",L.style.display=J.has(e.type)?"":"none";const V=document.createElement("label");V.className="form-label",V.textContent="Options (one per line: value: Label)";const S=document.createElement("textarea");S.id=`fb-options-${t}`,S.className="form-input",S.rows=4,S.value=(e.options||[]).map(d=>typeof d=="string"?`${d}: ${d}`:`${d.value??""}: ${d.label??d.value??""}`).join(`
|
|
3
|
+
`),L.appendChild(V),L.appendChild(S);const b=document.createElement("div");b.className="fb-grid-row",b.style.gridTemplateColumns="1fr auto",b.style.gap=".6rem",b.style.alignItems="end",b.style.marginBottom=".6rem",b.style.display=document.getElementById("collection-layout")?.value==="grid"?"grid":"none";const H=document.createElement("div"),Y=document.createElement("label");Y.className="form-label",Y.textContent="Column Span";const C=document.createElement("input");C.id=`fb-span-${t}`,C.type="number",C.className="form-input",C.min="1",C.max="6",C.value=e.span>1?String(e.span):"1",H.appendChild(Y),H.appendChild(C);const G=document.createElement("div");G.style.cssText="padding-bottom:.35rem;";const W=document.createElement("label");W.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const A=document.createElement("input");return A.id=`fb-fullwidth-${t}`,A.type="checkbox",A.checked=!!e.fullWidth,W.appendChild(A),W.appendChild(document.createTextNode("Full Width")),G.appendChild(W),b.appendChild(H),b.appendChild(G),s.appendChild(c),s.appendChild(N),s.appendChild(L),s.appendChild(b),n.addEventListener("click",()=>{const d=s.style.display!=="none";s.style.display=d?"none":"",l.style.transform=d?"":"rotate(180deg)"}),o.appendChild(n),o.appendChild(s),o}function K(e){if(e){if(e.textContent="",f.length===0){const t=document.createElement("p");t.className="text-muted",t.id="fields-empty-msg",t.style.cssText="text-align:center;padding:2rem 0;",t.textContent='No fields yet. Click "Add Field" to get started.',e.appendChild(t);return}f.forEach((t,o)=>{e.appendChild(ae(t,o))})}}function oe(e,t){t.textContent="",X.forEach(o=>{const n=e?.[o]||{enabled:!1,access:"admin"},i=document.createElement("div");i.style.cssText="display:grid;grid-template-columns:140px 1fr 160px;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--border-color,#333);";const p=document.createElement("strong");p.textContent=o.charAt(0).toUpperCase()+o.slice(1),p.style.cssText="font-size:.9rem;";const r=document.createElement("label");r.style.cssText="display:flex;align-items:center;gap:.45rem;cursor:pointer;font-size:.875rem;";const l=document.createElement("input");l.type="checkbox",l.id=`api-${o}-enabled`,l.checked=!!n.enabled,r.appendChild(l),r.appendChild(document.createTextNode("Enable public access"));const a=document.createElement("select");a.id=`api-${o}-access`,a.className="form-input",le.forEach(s=>{const c=document.createElement("option");c.value=s,c.textContent=s.charAt(0).toUpperCase()+s.slice(1),s===n.access&&(c.selected=!0),a.appendChild(c)}),i.appendChild(p),i.appendChild(r),i.appendChild(a),t.appendChild(i)})}function se(e,t){E.dropdown("#storage-adapter-trigger",{items:[{label:"File (default)",value:"file"},{label:"MongoDB",value:"mongodb"}],onSelect:({item:n})=>{e.find("#storage-adapter").val(n.value),e.find("#storage-adapter-label").text(n.label);const i=n.value==="mongodb";e.find("#storage-connection-group").toggle(i),e.find("#storage-migration-warning").toggle(i&&!T)}});const o=t.map(n=>({label:n,value:n}));E.dropdown("#storage-connection-trigger",{items:o.length?o:[{label:"default",value:"default"}],onSelect:({item:n})=>{e.find("#storage-connection").val(n.value),e.find("#storage-connection-label").text(n.label)}})}function te(){return(document.getElementById("storage-adapter")?.value||"file")==="mongodb"?{adapter:"mongodb",connection:document.getElementById("storage-connection")?.value||"default"}:{adapter:"file"}}function ce(){const e={};return X.forEach(t=>{const o=document.getElementById(`api-${t}-enabled`)?.checked??!1,n=document.getElementById(`api-${t}-access`)?.value||"admin";e[t]={enabled:o,access:n}}),e}export const collectionEditorView={templateUrl:"/admin/js/templates/collection-editor.html",async onMount(e){f=[],x=null,T=!0;const o=window.location.hash.match(/\/collections\/edit\/([^/?#]+)/);o&&(x=o[1],T=!1),E.tabs(e.find("#collection-tabs").get(0)),e.find("#collection-layout").get(0)?.addEventListener("change",function(){const l=this.value==="grid";e.find("#collection-columns-group").get(0).style.display=l?"":"none",document.querySelectorAll(".fb-grid-row").forEach(a=>{a.style.display=l?"grid":"none"})});const n=e.find("#fields-list").get(0),i=e.find("#api-access-rows").get(0),p=await D.collections.proStatus();p?.pro&&x!=="roles"&&(e.find("#storage-tab-btn").show(),se(e,p.connections));let r={create:{enabled:!1,access:"admin"},read:{enabled:!0,access:"public"},update:{enabled:!1,access:"admin"},delete:{enabled:!1,access:"admin"}};if(T){const l=e.find("#field-title").get(0),a=e.find("#field-slug").get(0);l&&a&&(l.addEventListener("input",()=>{a.dataset.manual||(a.value=Z(l.value))}),a.addEventListener("input",()=>{a.dataset.manual="1"}))}else try{const l=await D.collections.get(x);if(!l){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const a=e.find("#editor-title-text").get(0);a&&(a.textContent=l.title),e.find("#field-title").val(l.title||""),e.find("#field-slug").val(l.slug||""),e.find("#field-slug").prop("readonly",!0),e.find("#slug-hint").get(0).textContent="Slug cannot be changed after creation.",e.find("#field-description").val(l.description||""),e.find("#collection-layout").val(l.layout||"stacked"),e.find("#collection-columns").val(l.columns||2),e.find("#collection-columns-group").get(0).style.display=l.layout==="grid"?"":"none",f=l.fields||[],r=l.api||r,l.storage&&(e.find("#storage-adapter").val(l.storage.adapter||"file"),e.find("#storage-adapter-label").text(l.storage.adapter==="mongodb"?"MongoDB":"File (default)"),l.storage.adapter==="mongodb"&&(e.find("#storage-connection-group").show(),e.find("#storage-connection").val(l.storage.connection||"default"),e.find("#storage-connection-label").text(l.storage.connection||"default"))),x==="roles"&&e.find("#storage-tab-btn").hide()}catch{E.toast("Failed to load collection.",{type:"error"}),R.navigate("/collections");return}K(n),oe(r,i),e.find("#add-field-btn").off("click").on("click",()=>{f=ee(),f.push({id:`field-${Date.now()}`,name:"",label:"",type:"string",required:!1,placeholder:"",helper:"",options:[],validation:[],logic:null}),K(n);const l=n.querySelectorAll(".fb-field-card");if(l.length){const a=l[l.length-1],s=a.querySelector(".fb-field-body"),c=a.querySelector(".fb-field-chevron");s&&(s.style.display=""),c&&(c.style.transform="rotate(180deg)"),a.querySelector(`#fb-label-${f.length-1}`)?.focus()}}),e.find("#save-collection-btn").off("click").on("click",async()=>{const l=e.find("#field-title").val().trim(),a=e.find("#field-slug").val().trim(),s=e.find("#field-description").val().trim();if(!l){E.toast("Title is required.",{type:"warning"});return}const c=ee(),u=ce(),g=e.find("#collection-layout").val()||"stacked",m=parseInt(e.find("#collection-columns").val(),10)||2,w=e.find("#save-collection-btn");w.prop("disabled",!0);try{if(T){const h=await D.collections.create({title:l,slug:a,description:s,layout:g,columns:m,fields:c,api:u,storage:te()});x=h.slug,T=!1,E.toast("Collection created.",{type:"success"}),R.navigate(`/collections/edit/${h.slug}`)}else await D.collections.update(x,{title:l,description:s,layout:g,columns:m,fields:c,api:u,storage:te()}),E.toast("Collection saved.",{type:"success"})}catch(h){E.toast(h.message||"Failed to save.",{type:"error"})}finally{w.prop("disabled",!1)}}),Domma.icons.scan()}};
|