webspresso 0.0.76 → 0.0.77
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
CHANGED
|
@@ -2165,7 +2165,7 @@ const { app } = createApp({
|
|
|
2165
2165
|
```
|
|
2166
2166
|
|
|
2167
2167
|
- **ORM:** `zdb.file({ maxLength: 2048, nullable: true })` — string column for the stored public URL or path; migrations use `table.string(..., maxLength)`.
|
|
2168
|
-
- **Admin forms:** columns with `zdb.file()` automatically render a drag-and-drop upload widget (or a manual URL text field when `uploadUrl` is not configured). Optional `ui: { label, hint, accept, maxBytes }` on the column customizes the widget. For existing `zdb.string()` columns you can use `admin.customFields: { columnName: { type: 'file-upload' } }` instead of changing the schema type.
|
|
2168
|
+
- **Admin forms:** columns with `zdb.file()` automatically render a drag-and-drop upload widget (or a manual URL text field when `uploadUrl` is not configured). Image URLs show an inline thumbnail preview; use `ui.accept: 'image/*'` for image-only fields. Optional `ui: { label, hint, accept, maxBytes }` on the column customizes the widget. For existing `zdb.string()` columns you can use `admin.customFields: { columnName: { type: 'file-upload' } }` instead of changing the schema type.
|
|
2169
2169
|
- **Admin:** the panel reads **`settings.uploadUrl`** from the registry (set automatically when `uploadPlugin` is registered **before** `adminPanelPlugin`, or pass **`adminPanelPlugin({ uploadUrl: '/api/upload' })`**). File fields (`type: 'file'` or `customFields` type `file-upload`) POST to that URL with credentials; the saved record stores the returned **`url`** / **`publicUrl`** string.
|
|
2170
2170
|
- **Response:** `{ url, publicUrl, key? }` — clients typically persist **`url`** / **`publicUrl`** in the model.
|
|
2171
2171
|
- **Custom storage:** `uploadPlugin({ provider: { async put({ buffer, originalName, mimeType, size, req }) { return { publicUrl: '...' }; } } })`.
|
package/package.json
CHANGED
|
@@ -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
|
}
|
|
@@ -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
|
}
|