webspresso 0.0.76 → 0.0.78
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 +6 -1
- package/bin/commands/db-scaffold.js +81 -0
- package/bin/utils/model-migrations.js +211 -0
- package/bin/webspresso.js +2 -0
- package/core/content/cache.js +64 -0
- package/core/content/field-types.js +180 -0
- package/core/content/index.js +30 -0
- package/core/content/renderer.js +84 -0
- package/core/content/schema.js +75 -0
- package/core/content/service.js +400 -0
- package/core/content/types.js +59 -0
- package/index.d.ts +17 -0
- package/index.js +7 -0
- package/package.json +1 -1
- package/plugins/admin-panel/app.js +7 -7
- package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +41 -0
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +99 -15
- package/plugins/admin-panel/client/parts/06-login-setup-forms.js +2 -2
- package/plugins/admin-panel/field-renderers/file-upload.js +108 -27
- package/plugins/admin-panel/index.js +17 -18
- package/plugins/admin-panel/modules/menu.js +1 -0
- package/plugins/content/admin/content-entries-component.js +291 -0
- package/plugins/content/admin/content-types-component.js +250 -0
- package/plugins/content/api-handlers.js +157 -0
- package/plugins/content/client/inline-edit.css +296 -0
- package/plugins/content/client/inline-edit.js +366 -0
- package/plugins/content/helpers.js +77 -0
- package/plugins/content/index.js +231 -0
- package/plugins/content/migration-template.js +54 -0
- package/plugins/content/models/content-entry.js +45 -0
- package/plugins/content/models/content-type.js +36 -0
- package/plugins/index.js +2 -0
- package/src/file-router.js +21 -1
- package/templates/skills/webspresso-usage/REFERENCE-framework.md +1 -1
- package/templates/skills/webspresso-usage/SKILL.md +5 -0
|
@@ -114,8 +114,76 @@ const RichTextField = {
|
|
|
114
114
|
}
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
+
function isImageAccept(accept) {
|
|
118
|
+
return accept && accept !== '*/*' && String(accept).indexOf('image') !== -1;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isImageUrl(url) {
|
|
122
|
+
if (!url || typeof url !== 'string') return false;
|
|
123
|
+
if (url.indexOf('blob:') === 0) return true;
|
|
124
|
+
return /\.(jpe?g|png|gif|webp|svg|avif|bmp|ico)(\?|#|$)/i.test(url.trim());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function shouldShowImagePreview(url, accept) {
|
|
128
|
+
return isImageAccept(accept) || isImageUrl(url);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function renderUploadedFilePreview(url, options) {
|
|
132
|
+
var accept = options.accept;
|
|
133
|
+
var readonly = options.readonly;
|
|
134
|
+
var onRemove = options.onRemove;
|
|
135
|
+
var label = options.label;
|
|
136
|
+
if (!url) return null;
|
|
137
|
+
|
|
138
|
+
if (shouldShowImagePreview(url, accept)) {
|
|
139
|
+
return m('.mt-3.flex.flex-col.items-start.gap-2', [
|
|
140
|
+
m('a.block', { href: url, target: '_blank', rel: 'noopener noreferrer' },
|
|
141
|
+
m('img.max-h-48.max-w-full.rounded-lg.border.border-gray-200.dark:border-slate-600.object-contain.bg-white.dark:bg-slate-900.shadow-sm', {
|
|
142
|
+
src: url,
|
|
143
|
+
alt: label || 'Preview',
|
|
144
|
+
loading: 'lazy',
|
|
145
|
+
})
|
|
146
|
+
),
|
|
147
|
+
m('p.text-xs.text-gray-500.dark:text-slate-400.break-all', url),
|
|
148
|
+
!readonly && onRemove
|
|
149
|
+
? m('button.text-red-600.dark:text-red-400.hover:text-red-800.dark:hover:text-red-300.text-sm', {
|
|
150
|
+
type: 'button',
|
|
151
|
+
onclick: onRemove,
|
|
152
|
+
}, 'Remove')
|
|
153
|
+
: null,
|
|
154
|
+
]);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return m('.mt-3.flex.flex-col.items-start.gap-2', [
|
|
158
|
+
m('a.text-sm.text-indigo-600.dark:text-indigo-400.break-all', {
|
|
159
|
+
href: url,
|
|
160
|
+
target: '_blank',
|
|
161
|
+
rel: 'noopener noreferrer',
|
|
162
|
+
}, url),
|
|
163
|
+
!readonly && onRemove
|
|
164
|
+
? m('button.text-red-600.dark:text-red-400.hover:text-red-800.dark:hover:text-red-300.text-sm', {
|
|
165
|
+
type: 'button',
|
|
166
|
+
onclick: onRemove,
|
|
167
|
+
}, 'Remove')
|
|
168
|
+
: null,
|
|
169
|
+
]);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function revokeLocalPreview(state) {
|
|
173
|
+
if (state && state.localPreview) {
|
|
174
|
+
try { URL.revokeObjectURL(state.localPreview); } catch (e) {}
|
|
175
|
+
state.localPreview = null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
117
179
|
// File upload field (multipart POST to settings.uploadUrl; field name "file")
|
|
118
180
|
const FileUploadField = {
|
|
181
|
+
oninit: function (vnode) {
|
|
182
|
+
vnode.state.localPreview = null;
|
|
183
|
+
},
|
|
184
|
+
onremove: function (vnode) {
|
|
185
|
+
revokeLocalPreview(vnode.state);
|
|
186
|
+
},
|
|
119
187
|
oncreate: (vnode) => {
|
|
120
188
|
const col = vnode.attrs.col;
|
|
121
189
|
const readonly = vnode.attrs.readonly;
|
|
@@ -128,6 +196,7 @@ const FileUploadField = {
|
|
|
128
196
|
if (!dropZone) return;
|
|
129
197
|
const meta = col.ui || {};
|
|
130
198
|
const onChange = vnode.attrs.onChange;
|
|
199
|
+
const state = vnode.state;
|
|
131
200
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(function (eventName) {
|
|
132
201
|
dropZone.addEventListener(eventName, function (e) {
|
|
133
202
|
e.preventDefault();
|
|
@@ -147,14 +216,14 @@ const FileUploadField = {
|
|
|
147
216
|
dropZone.addEventListener('drop', function (e) {
|
|
148
217
|
var files = e.dataTransfer.files;
|
|
149
218
|
if (files.length > 0) {
|
|
150
|
-
handleAdminFileUpload(files[0], onChange, meta);
|
|
219
|
+
handleAdminFileUpload(files[0], onChange, meta, state);
|
|
151
220
|
}
|
|
152
221
|
});
|
|
153
222
|
var fileInput = dropZone.querySelector('input[type=file]');
|
|
154
223
|
if (fileInput) {
|
|
155
224
|
fileInput.addEventListener('change', function (e) {
|
|
156
225
|
if (e.target.files.length > 0) {
|
|
157
|
-
handleAdminFileUpload(e.target.files[0], onChange, meta);
|
|
226
|
+
handleAdminFileUpload(e.target.files[0], onChange, meta, state);
|
|
158
227
|
}
|
|
159
228
|
});
|
|
160
229
|
}
|
|
@@ -164,6 +233,7 @@ const FileUploadField = {
|
|
|
164
233
|
const value = vnode.attrs.value || '';
|
|
165
234
|
const onChange = vnode.attrs.onChange;
|
|
166
235
|
const readonly = vnode.attrs.readonly || false;
|
|
236
|
+
const state = vnode.state;
|
|
167
237
|
const meta = col.ui || {};
|
|
168
238
|
const label = meta.label || formatColumnLabel(col.name);
|
|
169
239
|
const hint = meta.hint || '';
|
|
@@ -176,10 +246,17 @@ const FileUploadField = {
|
|
|
176
246
|
var maxSize = meta.maxSize || meta.maxBytes || (10 * 1024 * 1024);
|
|
177
247
|
var accept = meta.accept || '*/*';
|
|
178
248
|
var dropZoneId = 'drop-zone-' + col.name;
|
|
249
|
+
var displayUrl = state.localPreview || value;
|
|
250
|
+
var clearValue = function () {
|
|
251
|
+
revokeLocalPreview(state);
|
|
252
|
+
if (onChange) onChange('');
|
|
253
|
+
};
|
|
179
254
|
if (readonly) {
|
|
180
255
|
return m('.mb-4', [
|
|
181
256
|
m('label.block.text-sm.font-medium.text-gray-700.dark:text-slate-300.mb-1', label, required ? m('span.text-red-500', ' *') : null),
|
|
182
|
-
value
|
|
257
|
+
value
|
|
258
|
+
? renderUploadedFilePreview(value, { accept: accept, readonly: true, label: label })
|
|
259
|
+
: m('span.text-gray-400.dark:text-slate-500', '—'),
|
|
183
260
|
hint ? m('p.text-xs.text-gray-500.dark:text-slate-400.mt-1', hint) : null,
|
|
184
261
|
]);
|
|
185
262
|
}
|
|
@@ -196,32 +273,29 @@ const FileUploadField = {
|
|
|
196
273
|
required: required,
|
|
197
274
|
oninput: function (e) { if (onChange) onChange(e.target.value); },
|
|
198
275
|
}),
|
|
276
|
+
value ? renderUploadedFilePreview(value, { accept: accept, readonly: false, onRemove: clearValue, label: label }) : null,
|
|
199
277
|
hint ? m('p.text-xs.text-gray-500.dark:text-slate-400.mt-1', hint) : null,
|
|
200
278
|
]);
|
|
201
279
|
}
|
|
202
280
|
return m('.mb-4', [
|
|
203
281
|
m('label.block.text-sm.font-medium.text-gray-700.dark:text-slate-300.mb-1', label, required ? m('span.text-red-500', ' *') : null),
|
|
204
|
-
|
|
282
|
+
displayUrl
|
|
283
|
+
? renderUploadedFilePreview(displayUrl, { accept: accept, readonly: false, onRemove: clearValue, label: label })
|
|
284
|
+
: null,
|
|
285
|
+
m('div#' + dropZoneId + '.border-2.border-dashed.border-gray-300.dark:border-slate-600.rounded-lg.p-6.text-center.bg-gray-50.dark:bg-slate-900/40', { style: 'cursor: pointer;' }, [
|
|
205
286
|
m('input[type=file].hidden', {
|
|
206
287
|
id: 'file-input-' + col.name,
|
|
207
288
|
accept: accept,
|
|
208
289
|
onchange: function (e) {
|
|
209
290
|
if (e.target.files.length > 0) {
|
|
210
|
-
handleAdminFileUpload(e.target.files[0], onChange, meta);
|
|
291
|
+
handleAdminFileUpload(e.target.files[0], onChange, meta, state);
|
|
211
292
|
}
|
|
212
293
|
},
|
|
213
294
|
}),
|
|
214
295
|
m('div', [
|
|
215
|
-
m('p.text-gray-600.dark:text-slate-400.mb-2', 'Drag and drop a file here, or'),
|
|
216
|
-
m('label.text-blue-600.hover:text-blue-800.dark:text-blue-400.cursor-pointer', { for: 'file-input-' + col.name }, 'browse'),
|
|
296
|
+
m('p.text-gray-600.dark:text-slate-400.mb-2', displayUrl ? 'Drag and drop to replace, or' : 'Drag and drop a file here, or'),
|
|
297
|
+
m('label.text-blue-600.hover:text-blue-800.dark:text-blue-400.cursor-pointer', { for: 'file-input-' + col.name }, displayUrl ? 'choose another file' : 'browse'),
|
|
217
298
|
]),
|
|
218
|
-
value ? m('.mt-4.text-left', [
|
|
219
|
-
m('p.text-sm.text-gray-600.dark:text-slate-400.break-all', 'Current: ' + value),
|
|
220
|
-
m('button.text-red-600.dark:text-red-400.hover:text-red-800.dark:hover:text-red-300.text-sm.mt-2', {
|
|
221
|
-
type: 'button',
|
|
222
|
-
onclick: function () { if (onChange) onChange(''); },
|
|
223
|
-
}, 'Remove'),
|
|
224
|
-
]) : null,
|
|
225
299
|
]),
|
|
226
300
|
m('input[type=hidden]', { name: col.name, value: typeof value === 'string' ? value : '' }),
|
|
227
301
|
m('p.text-xs.text-gray-500.dark:text-slate-400.mt-1', 'Max ' + Math.round(maxSize / 1024 / 1024) + ' MB (server enforces limits)'),
|
|
@@ -230,7 +304,7 @@ const FileUploadField = {
|
|
|
230
304
|
},
|
|
231
305
|
};
|
|
232
306
|
|
|
233
|
-
async function handleAdminFileUpload(file, onChange, meta) {
|
|
307
|
+
async function handleAdminFileUpload(file, onChange, meta, state) {
|
|
234
308
|
var uploadUrl = '';
|
|
235
309
|
try {
|
|
236
310
|
var cfg = window.__ADMIN_CONFIG__;
|
|
@@ -245,6 +319,11 @@ async function handleAdminFileUpload(file, onChange, meta) {
|
|
|
245
319
|
alert('File too large (max ' + Math.round(maxSize / 1024 / 1024) + ' MB).');
|
|
246
320
|
return;
|
|
247
321
|
}
|
|
322
|
+
if (state && file.type && file.type.indexOf('image/') === 0) {
|
|
323
|
+
revokeLocalPreview(state);
|
|
324
|
+
state.localPreview = URL.createObjectURL(file);
|
|
325
|
+
m.redraw();
|
|
326
|
+
}
|
|
248
327
|
var fd = new FormData();
|
|
249
328
|
fd.append('file', file);
|
|
250
329
|
try {
|
|
@@ -252,13 +331,18 @@ async function handleAdminFileUpload(file, onChange, meta) {
|
|
|
252
331
|
var data = {};
|
|
253
332
|
try { data = await res.json(); } catch (e2) { data = {}; }
|
|
254
333
|
if (!res.ok) {
|
|
334
|
+
revokeLocalPreview(state);
|
|
335
|
+
m.redraw();
|
|
255
336
|
alert(data.message || data.error || ('Upload failed (' + res.status + ')'));
|
|
256
337
|
return;
|
|
257
338
|
}
|
|
339
|
+
revokeLocalPreview(state);
|
|
258
340
|
var url = data.url || data.publicUrl || '';
|
|
259
341
|
if (onChange) onChange(url);
|
|
260
342
|
m.redraw();
|
|
261
343
|
} catch (err) {
|
|
344
|
+
revokeLocalPreview(state);
|
|
345
|
+
m.redraw();
|
|
262
346
|
alert(err.message || 'Upload failed');
|
|
263
347
|
}
|
|
264
348
|
}
|
|
@@ -19,7 +19,7 @@ const LoginForm = {
|
|
|
19
19
|
password: data.get('password'),
|
|
20
20
|
});
|
|
21
21
|
state.user = result.user;
|
|
22
|
-
m.route.set('/');
|
|
22
|
+
m.route.set(consumeIntendedRoute('/'));
|
|
23
23
|
} catch (err) {
|
|
24
24
|
state.error = err.message;
|
|
25
25
|
} finally {
|
|
@@ -78,7 +78,7 @@ const SetupForm = {
|
|
|
78
78
|
});
|
|
79
79
|
state.user = result.user;
|
|
80
80
|
state.needsSetup = false;
|
|
81
|
-
m.route.set('/');
|
|
81
|
+
m.route.set(consumeIntendedRoute('/'));
|
|
82
82
|
} catch (err) {
|
|
83
83
|
state.error = err.message;
|
|
84
84
|
} finally {
|
|
@@ -13,8 +13,76 @@ function getUploadUrlFromAdminConfig() {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function isImageAccept(accept) {
|
|
17
|
+
return accept && accept !== '*/*' && String(accept).includes('image');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isImageUrl(url) {
|
|
21
|
+
if (!url || typeof url !== 'string') return false;
|
|
22
|
+
if (url.startsWith('blob:')) return true;
|
|
23
|
+
return /\.(jpe?g|png|gif|webp|svg|avif|bmp|ico)(\?|#|$)/i.test(url.trim());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function shouldShowImagePreview(url, accept) {
|
|
27
|
+
return isImageAccept(accept) || isImageUrl(url);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function renderUploadedFilePreview(url, { accept, readonly, onRemove, label }) {
|
|
31
|
+
if (!url) return null;
|
|
32
|
+
|
|
33
|
+
if (shouldShowImagePreview(url, accept)) {
|
|
34
|
+
return m('.mt-3.flex.flex-col.items-start.gap-2', [
|
|
35
|
+
m('a.block', { href: url, target: '_blank', rel: 'noopener noreferrer' },
|
|
36
|
+
m('img.max-h-48.max-w-full.rounded-lg.border.border-gray-200.dark:border-slate-600.object-contain.bg-white.dark:bg-slate-900.shadow-sm', {
|
|
37
|
+
src: url,
|
|
38
|
+
alt: label || 'Preview',
|
|
39
|
+
loading: 'lazy',
|
|
40
|
+
})
|
|
41
|
+
),
|
|
42
|
+
m('p.text-xs.text-gray-500.dark:text-slate-400.break-all', url),
|
|
43
|
+
!readonly && onRemove
|
|
44
|
+
? m('button.text-red-600.dark:text-red-400.hover:text-red-800.dark:hover:text-red-300.text-sm', {
|
|
45
|
+
type: 'button',
|
|
46
|
+
onclick: onRemove,
|
|
47
|
+
}, 'Remove')
|
|
48
|
+
: null,
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return m('.mt-3.flex.flex-col.items-start.gap-2', [
|
|
53
|
+
m('a.text-sm.text-indigo-600.dark:text-indigo-400.break-all', {
|
|
54
|
+
href: url,
|
|
55
|
+
target: '_blank',
|
|
56
|
+
rel: 'noopener noreferrer',
|
|
57
|
+
}, url),
|
|
58
|
+
!readonly && onRemove
|
|
59
|
+
? m('button.text-red-600.dark:text-red-400.hover:text-red-800.dark:hover:text-red-300.text-sm', {
|
|
60
|
+
type: 'button',
|
|
61
|
+
onclick: onRemove,
|
|
62
|
+
}, 'Remove')
|
|
63
|
+
: null,
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function revokeLocalPreview(state) {
|
|
68
|
+
if (state?.localPreview) {
|
|
69
|
+
try {
|
|
70
|
+
URL.revokeObjectURL(state.localPreview);
|
|
71
|
+
} catch {
|
|
72
|
+
// ignore
|
|
73
|
+
}
|
|
74
|
+
state.localPreview = null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
16
78
|
module.exports = {
|
|
17
79
|
FileUploadField: {
|
|
80
|
+
oninit: (vnode) => {
|
|
81
|
+
vnode.state.localPreview = null;
|
|
82
|
+
},
|
|
83
|
+
onremove: (vnode) => {
|
|
84
|
+
revokeLocalPreview(vnode.state);
|
|
85
|
+
},
|
|
18
86
|
oncreate: (vnode) => {
|
|
19
87
|
const { name, meta = {} } = vnode.attrs;
|
|
20
88
|
if (!getUploadUrlFromAdminConfig()) return;
|
|
@@ -60,17 +128,24 @@ module.exports = {
|
|
|
60
128
|
},
|
|
61
129
|
|
|
62
130
|
view: (vnode) => {
|
|
63
|
-
const { name, value = '', meta = {}, required = false } = vnode.attrs;
|
|
131
|
+
const { name, value = '', meta = {}, required = false, readonly = false } = vnode.attrs;
|
|
132
|
+
const state = vnode.state;
|
|
64
133
|
const dropZoneId = 'drop-zone-' + name;
|
|
65
134
|
const maxSize = meta.maxSize || meta.maxBytes || 10 * 1024 * 1024;
|
|
66
135
|
const accept = meta.accept || '*/*';
|
|
67
136
|
const uploadUrl = getUploadUrlFromAdminConfig();
|
|
137
|
+
const displayUrl = state.localPreview || value;
|
|
138
|
+
const label = meta.label || name;
|
|
139
|
+
const clearValue = () => {
|
|
140
|
+
revokeLocalPreview(state);
|
|
141
|
+
if (vnode.attrs.onchange) vnode.attrs.onchange('');
|
|
142
|
+
};
|
|
68
143
|
|
|
69
144
|
if (!uploadUrl) {
|
|
70
145
|
return m('.mb-4', [
|
|
71
146
|
m(
|
|
72
147
|
'label.block.text-sm.font-medium.mb-2',
|
|
73
|
-
|
|
148
|
+
label,
|
|
74
149
|
required ? m('span.text-red-500', ' *') : null
|
|
75
150
|
),
|
|
76
151
|
m('p.text-xs.text-amber-700.dark:text-amber-400.mb-2', 'Upload URL is not configured.'),
|
|
@@ -80,62 +155,57 @@ module.exports = {
|
|
|
80
155
|
value: typeof value === 'string' ? value : '',
|
|
81
156
|
placeholder: 'https://… or /uploads/…',
|
|
82
157
|
required,
|
|
158
|
+
readonly,
|
|
83
159
|
oninput: (e) => {
|
|
84
160
|
if (vnode.attrs.onchange) vnode.attrs.onchange(e.target.value);
|
|
85
161
|
},
|
|
86
162
|
}),
|
|
163
|
+
value
|
|
164
|
+
? renderUploadedFilePreview(value, { accept, readonly, onRemove: clearValue, label })
|
|
165
|
+
: null,
|
|
87
166
|
]);
|
|
88
167
|
}
|
|
89
168
|
|
|
90
169
|
return m('.mb-4', [
|
|
91
170
|
m(
|
|
92
171
|
'label.block.text-sm.font-medium.mb-2',
|
|
93
|
-
|
|
172
|
+
label,
|
|
94
173
|
required ? m('span.text-red-500', ' *') : null
|
|
95
174
|
),
|
|
175
|
+
displayUrl
|
|
176
|
+
? renderUploadedFilePreview(displayUrl, { accept, readonly, onRemove: clearValue, label })
|
|
177
|
+
: null,
|
|
96
178
|
m(
|
|
97
|
-
'div.border-2.border-dashed.border-gray-300.dark:border-slate-600.rounded.p-
|
|
179
|
+
'div.border-2.border-dashed.border-gray-300.dark:border-slate-600.rounded.p-6.text-center',
|
|
98
180
|
{
|
|
99
181
|
id: dropZoneId,
|
|
100
|
-
style: 'cursor: pointer;',
|
|
182
|
+
style: readonly ? undefined : 'cursor: pointer;',
|
|
101
183
|
},
|
|
102
184
|
[
|
|
103
185
|
m('input[type=file]', {
|
|
104
186
|
class: 'hidden',
|
|
105
187
|
id: 'file-input-' + name,
|
|
106
188
|
accept,
|
|
189
|
+
disabled: readonly,
|
|
107
190
|
onchange: (e) => {
|
|
108
191
|
if (e.target.files.length > 0) {
|
|
109
192
|
handleFileUpload(e.target.files[0], vnode, meta);
|
|
110
193
|
}
|
|
111
194
|
},
|
|
112
195
|
}),
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
m(
|
|
116
|
-
'label.text-blue-600.hover:text-blue-800.cursor-pointer',
|
|
117
|
-
{ for: 'file-input-' + name },
|
|
118
|
-
'browse'
|
|
119
|
-
),
|
|
120
|
-
]),
|
|
121
|
-
value
|
|
122
|
-
? m('.mt-4', [
|
|
196
|
+
readonly
|
|
197
|
+
? null
|
|
198
|
+
: m('div', [
|
|
123
199
|
m(
|
|
124
|
-
'p.text-
|
|
125
|
-
|
|
200
|
+
'p.text-gray-600.dark:text-slate-400.mb-2',
|
|
201
|
+
displayUrl ? 'Drag and drop to replace, or' : 'Drag and drop a file here, or'
|
|
126
202
|
),
|
|
127
203
|
m(
|
|
128
|
-
'
|
|
129
|
-
{
|
|
130
|
-
|
|
131
|
-
onclick: () => {
|
|
132
|
-
if (vnode.attrs.onchange) vnode.attrs.onchange('');
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
'Remove'
|
|
204
|
+
'label.text-blue-600.hover:text-blue-800.cursor-pointer',
|
|
205
|
+
{ for: 'file-input-' + name },
|
|
206
|
+
displayUrl ? 'choose another file' : 'browse'
|
|
136
207
|
),
|
|
137
|
-
])
|
|
138
|
-
: null,
|
|
208
|
+
]),
|
|
139
209
|
]
|
|
140
210
|
),
|
|
141
211
|
m('input[type=hidden]', {
|
|
@@ -169,6 +239,12 @@ async function handleFileUpload(file, vnode, meta) {
|
|
|
169
239
|
return;
|
|
170
240
|
}
|
|
171
241
|
|
|
242
|
+
if (file.type?.startsWith('image/')) {
|
|
243
|
+
revokeLocalPreview(vnode.state);
|
|
244
|
+
vnode.state.localPreview = URL.createObjectURL(file);
|
|
245
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
246
|
+
}
|
|
247
|
+
|
|
172
248
|
const fd = new FormData();
|
|
173
249
|
fd.append('file', file);
|
|
174
250
|
|
|
@@ -181,13 +257,18 @@ async function handleFileUpload(file, vnode, meta) {
|
|
|
181
257
|
data = {};
|
|
182
258
|
}
|
|
183
259
|
if (!res.ok) {
|
|
260
|
+
revokeLocalPreview(vnode.state);
|
|
261
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
184
262
|
alert(data.message || data.error || `Upload failed (${res.status})`);
|
|
185
263
|
return;
|
|
186
264
|
}
|
|
265
|
+
revokeLocalPreview(vnode.state);
|
|
187
266
|
const url = data.url || data.publicUrl || '';
|
|
188
267
|
if (vnode.attrs.onchange) vnode.attrs.onchange(url);
|
|
189
268
|
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
190
269
|
} catch (err) {
|
|
270
|
+
revokeLocalPreview(vnode.state);
|
|
271
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
191
272
|
alert(err.message || 'Upload failed');
|
|
192
273
|
}
|
|
193
274
|
}
|
|
@@ -119,6 +119,22 @@ function adminPanelPlugin(options = {}) {
|
|
|
119
119
|
if (typeof configure === 'function') {
|
|
120
120
|
configure(registry);
|
|
121
121
|
}
|
|
122
|
+
|
|
123
|
+
// Session must be registered before file routes so SSR pages can read adminUser.
|
|
124
|
+
if (enabled && ctx.app && !ctx.app._webspressoSessionInitialized) {
|
|
125
|
+
const secret = sessionSecret || process.env.SESSION_SECRET || 'webspresso-admin-secret-change-in-production';
|
|
126
|
+
ctx.app.use(session({
|
|
127
|
+
secret,
|
|
128
|
+
resave: false,
|
|
129
|
+
saveUninitialized: false,
|
|
130
|
+
cookie: {
|
|
131
|
+
secure: process.env.NODE_ENV === 'production',
|
|
132
|
+
httpOnly: true,
|
|
133
|
+
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
|
134
|
+
},
|
|
135
|
+
}));
|
|
136
|
+
ctx.app._webspressoSessionInitialized = true;
|
|
137
|
+
}
|
|
122
138
|
},
|
|
123
139
|
|
|
124
140
|
/**
|
|
@@ -164,24 +180,7 @@ function adminPanelPlugin(options = {}) {
|
|
|
164
180
|
db.registerModel(AdminUser);
|
|
165
181
|
}
|
|
166
182
|
|
|
167
|
-
//
|
|
168
|
-
if (!app._webspressoSessionInitialized) {
|
|
169
|
-
const secret = sessionSecret || process.env.SESSION_SECRET || 'webspresso-admin-secret-change-in-production';
|
|
170
|
-
|
|
171
|
-
app.use(session({
|
|
172
|
-
secret,
|
|
173
|
-
resave: false,
|
|
174
|
-
saveUninitialized: false,
|
|
175
|
-
cookie: {
|
|
176
|
-
secure: process.env.NODE_ENV === 'production',
|
|
177
|
-
httpOnly: true,
|
|
178
|
-
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
|
179
|
-
},
|
|
180
|
-
}));
|
|
181
|
-
|
|
182
|
-
app._webspressoSessionInitialized = true;
|
|
183
|
-
}
|
|
184
|
-
|
|
183
|
+
// Session middleware is initialized in register() (before file routes).
|
|
185
184
|
// Register default modules
|
|
186
185
|
registerSystemMenuItems({ registry });
|
|
187
186
|
registerModelMenuItems({ registry, db });
|