@dmitryvim/form-builder 0.2.26 → 0.2.27
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/dist/browser/formbuilder.min.js +457 -189
- package/dist/browser/formbuilder.v0.2.27.min.js +874 -0
- package/dist/cjs/index.cjs +1600 -1012
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +1566 -988
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +457 -189
- package/dist/types/components/file/constraints.d.ts +26 -0
- package/dist/types/components/file/dom.d.ts +44 -0
- package/dist/types/components/file/preview.d.ts +69 -0
- package/dist/types/components/file/render-edit.d.ts +15 -0
- package/dist/types/components/file/render-readonly.d.ts +23 -0
- package/dist/types/components/file/styles.d.ts +1 -0
- package/dist/types/components/file/upload.d.ts +13 -0
- package/dist/types/components/file/validate.d.ts +13 -0
- package/dist/types/components/file.d.ts +5 -27
- package/dist/types/types/config.d.ts +4 -0
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.2.26.min.js +0 -606
package/dist/cjs/index.cjs
CHANGED
|
@@ -2119,7 +2119,7 @@ function updateSwitcherField(element, fieldPath, value, context) {
|
|
|
2119
2119
|
}
|
|
2120
2120
|
}
|
|
2121
2121
|
|
|
2122
|
-
// src/components/file.ts
|
|
2122
|
+
// src/components/file/constraints.ts
|
|
2123
2123
|
function getAllowedExtensions(accept) {
|
|
2124
2124
|
if (!accept) return [];
|
|
2125
2125
|
if (typeof accept === "object" && Array.isArray(accept.extensions)) {
|
|
@@ -2131,18 +2131,669 @@ function getAllowedExtensions(accept) {
|
|
|
2131
2131
|
return [];
|
|
2132
2132
|
}
|
|
2133
2133
|
function isFileExtensionAllowed(fileName, allowedExtensions) {
|
|
2134
|
-
var _a;
|
|
2134
|
+
var _a, _b;
|
|
2135
2135
|
if (allowedExtensions.length === 0) return true;
|
|
2136
|
-
const ext = ((_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase())
|
|
2136
|
+
const ext = (_b = (_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase()) != null ? _b : "";
|
|
2137
2137
|
return allowedExtensions.includes(ext);
|
|
2138
2138
|
}
|
|
2139
2139
|
function isFileSizeAllowed(file, maxSizeMB) {
|
|
2140
2140
|
if (maxSizeMB === Infinity) return true;
|
|
2141
2141
|
return file.size <= maxSizeMB * 1024 * 1024;
|
|
2142
2142
|
}
|
|
2143
|
+
function addPrefillFilesToIndex(initialFiles, resourceIndex) {
|
|
2144
|
+
var _a;
|
|
2145
|
+
for (const resourceId of initialFiles) {
|
|
2146
|
+
if (resourceIndex.has(resourceId)) continue;
|
|
2147
|
+
const filename = resourceId.split("/").pop() || "file";
|
|
2148
|
+
const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
|
|
2149
|
+
let fileType = "application/octet-stream";
|
|
2150
|
+
if (extension) {
|
|
2151
|
+
if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
|
|
2152
|
+
fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
|
|
2153
|
+
} else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
|
|
2154
|
+
fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
resourceIndex.set(resourceId, {
|
|
2158
|
+
name: filename,
|
|
2159
|
+
type: fileType,
|
|
2160
|
+
size: 0,
|
|
2161
|
+
uploadedAt: /* @__PURE__ */ new Date(),
|
|
2162
|
+
file: void 0
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
// src/components/file/styles.ts
|
|
2168
|
+
var STYLE_ID = "fb-file-styles";
|
|
2169
|
+
function ensureFileStyles() {
|
|
2170
|
+
if (typeof document === "undefined") return;
|
|
2171
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
2172
|
+
const style = document.createElement("style");
|
|
2173
|
+
style.id = STYLE_ID;
|
|
2174
|
+
style.setAttribute("data-fb-file-styles", "true");
|
|
2175
|
+
style.textContent = `
|
|
2176
|
+
@keyframes fb-spin { to { transform: rotate(360deg); } }
|
|
2177
|
+
|
|
2178
|
+
/* Spinner used during single-file and multi-file upload */
|
|
2179
|
+
.fb-spinner {
|
|
2180
|
+
width: 36px;
|
|
2181
|
+
height: 36px;
|
|
2182
|
+
border: 3px solid rgba(0,0,0,0.12);
|
|
2183
|
+
border-top-color: var(--fb-text-secondary-color, #6b7280);
|
|
2184
|
+
border-radius: 50%;
|
|
2185
|
+
animation: fb-spin 0.7s linear infinite;
|
|
2186
|
+
flex-shrink: 0;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
/* Base tile: fixed 160\xD7160 square, theme-aware background */
|
|
2190
|
+
.fb-tile {
|
|
2191
|
+
width: var(--fb-tile-size, 160px);
|
|
2192
|
+
height: var(--fb-tile-size, 160px);
|
|
2193
|
+
flex-shrink: 0;
|
|
2194
|
+
position: relative;
|
|
2195
|
+
overflow: hidden;
|
|
2196
|
+
border-radius: var(--fb-border-radius, 0.5rem);
|
|
2197
|
+
background: var(--fb-file-upload-bg-color, #f3f4f6);
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
/* Uploaded resource tile \u2014 adds a visible border */
|
|
2201
|
+
.fb-tile-resource {
|
|
2202
|
+
border: 1px solid var(--fb-file-upload-border-color, #d1d5db);
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
/* Uploading placeholder tile \u2014 dashed border, uploading indicator */
|
|
2206
|
+
.fb-tile-uploading {
|
|
2207
|
+
border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
/* "+" add-more tile */
|
|
2211
|
+
.fb-tile-add {
|
|
2212
|
+
border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
|
|
2213
|
+
display: flex;
|
|
2214
|
+
align-items: center;
|
|
2215
|
+
justify-content: center;
|
|
2216
|
+
cursor: pointer;
|
|
2217
|
+
font-size: 32px;
|
|
2218
|
+
color: var(--fb-file-upload-text-color, #9ca3af);
|
|
2219
|
+
transition:
|
|
2220
|
+
border-color var(--fb-transition-duration, 200ms),
|
|
2221
|
+
color var(--fb-transition-duration, 200ms);
|
|
2222
|
+
}
|
|
2223
|
+
.fb-tile-add:hover {
|
|
2224
|
+
border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
|
|
2225
|
+
color: var(--fb-text-color, #1f2937);
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
/* Count chip shown when at maxCount */
|
|
2229
|
+
.fb-tile-counter {
|
|
2230
|
+
font-size: 11px;
|
|
2231
|
+
color: var(--fb-text-secondary-color, #6b7280);
|
|
2232
|
+
background: var(--fb-file-upload-bg-color, #f3f4f6);
|
|
2233
|
+
border: 1px solid var(--fb-file-upload-border-color, #d1d5db);
|
|
2234
|
+
border-radius: 4px;
|
|
2235
|
+
padding: 2px 6px;
|
|
2236
|
+
align-self: flex-end;
|
|
2237
|
+
margin-bottom: 4px;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
/* Empty-state dropzone */
|
|
2241
|
+
.fb-file-dropzone {
|
|
2242
|
+
width: 100%;
|
|
2243
|
+
height: 128px;
|
|
2244
|
+
border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
|
|
2245
|
+
border-radius: var(--fb-border-radius, 0.5rem);
|
|
2246
|
+
display: flex;
|
|
2247
|
+
flex-direction: column;
|
|
2248
|
+
align-items: center;
|
|
2249
|
+
justify-content: center;
|
|
2250
|
+
gap: 4px;
|
|
2251
|
+
cursor: pointer;
|
|
2252
|
+
transition:
|
|
2253
|
+
border-color var(--fb-transition-duration, 200ms),
|
|
2254
|
+
background var(--fb-transition-duration, 200ms);
|
|
2255
|
+
}
|
|
2256
|
+
.fb-file-dropzone:hover {
|
|
2257
|
+
border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
|
|
2258
|
+
background: var(--fb-background-hover-color, #f9fafb);
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
/* Inline text inside tiles */
|
|
2262
|
+
.fb-tile-label {
|
|
2263
|
+
font-size: 9px;
|
|
2264
|
+
color: var(--fb-text-secondary-color, #6b7280);
|
|
2265
|
+
text-align: center;
|
|
2266
|
+
overflow: hidden;
|
|
2267
|
+
word-break: break-all;
|
|
2268
|
+
max-height: 28px;
|
|
2269
|
+
}
|
|
2270
|
+
.fb-tile-uploading-text {
|
|
2271
|
+
font-size: 8px;
|
|
2272
|
+
color: var(--fb-file-upload-text-color, #9ca3af);
|
|
2273
|
+
}
|
|
2274
|
+
.fb-tile-hint {
|
|
2275
|
+
font-size: 11px;
|
|
2276
|
+
color: var(--fb-file-upload-text-color, #9ca3af);
|
|
2277
|
+
margin-top: 4px;
|
|
2278
|
+
}
|
|
2279
|
+
.fb-tile-empty-text {
|
|
2280
|
+
font-size: 12px;
|
|
2281
|
+
color: var(--fb-text-secondary-color, #6b7280);
|
|
2282
|
+
padding: 4px 0;
|
|
2283
|
+
}
|
|
2284
|
+
.fb-dropzone-primary-text {
|
|
2285
|
+
font-size: 13px;
|
|
2286
|
+
color: var(--fb-text-secondary-color, #6b7280);
|
|
2287
|
+
}
|
|
2288
|
+
.fb-dropzone-hint-text {
|
|
2289
|
+
font-size: 11px;
|
|
2290
|
+
color: var(--fb-file-upload-text-color, #9ca3af);
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
/* Hover overlay + X-button on resource tiles */
|
|
2294
|
+
.fb-tile-overlay {
|
|
2295
|
+
position: absolute;
|
|
2296
|
+
inset: 0;
|
|
2297
|
+
background: transparent;
|
|
2298
|
+
transition: background var(--fb-transition-duration, 200ms);
|
|
2299
|
+
display: flex;
|
|
2300
|
+
align-items: flex-start;
|
|
2301
|
+
justify-content: flex-end;
|
|
2302
|
+
}
|
|
2303
|
+
.fb-tile-resource:hover .fb-tile-overlay {
|
|
2304
|
+
background: var(--fb-tile-hover-overlay-color, rgba(0,0,0,0.4));
|
|
2305
|
+
}
|
|
2306
|
+
.fb-tile-x-btn {
|
|
2307
|
+
margin: 3px;
|
|
2308
|
+
width: 18px;
|
|
2309
|
+
height: 18px;
|
|
2310
|
+
background: var(--fb-error-color, #ef4444);
|
|
2311
|
+
color: var(--fb-file-bg-color, #fff);
|
|
2312
|
+
border: none;
|
|
2313
|
+
border-radius: 50%;
|
|
2314
|
+
font-size: 11px;
|
|
2315
|
+
line-height: 1;
|
|
2316
|
+
cursor: pointer;
|
|
2317
|
+
display: flex;
|
|
2318
|
+
align-items: center;
|
|
2319
|
+
justify-content: center;
|
|
2320
|
+
opacity: 0;
|
|
2321
|
+
transition: opacity var(--fb-transition-duration, 200ms);
|
|
2322
|
+
}
|
|
2323
|
+
.fb-tile-resource:hover .fb-tile-x-btn {
|
|
2324
|
+
opacity: 1;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
/* Video play button overlay (readonly tiles with video thumbnails) */
|
|
2328
|
+
.fb-video-overlay {
|
|
2329
|
+
position: absolute;
|
|
2330
|
+
inset: 0;
|
|
2331
|
+
display: flex;
|
|
2332
|
+
align-items: center;
|
|
2333
|
+
justify-content: center;
|
|
2334
|
+
background: var(--fb-tile-hover-overlay-color, rgba(0,0,0,0.25));
|
|
2335
|
+
}
|
|
2336
|
+
.fb-play-btn {
|
|
2337
|
+
background: var(--fb-file-bg-color, rgba(255,255,255,0.9));
|
|
2338
|
+
border-radius: 50%;
|
|
2339
|
+
display: flex;
|
|
2340
|
+
align-items: center;
|
|
2341
|
+
justify-content: center;
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
/* Edit-mode local video preview wrapper */
|
|
2345
|
+
.fb-video-preview-wrap {
|
|
2346
|
+
position: relative;
|
|
2347
|
+
width: 100%;
|
|
2348
|
+
height: 100%;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
/* Hover overlay for edit-mode local video (Remove / Change buttons) */
|
|
2352
|
+
.fb-video-btn-overlay {
|
|
2353
|
+
position: absolute;
|
|
2354
|
+
top: 8px;
|
|
2355
|
+
right: 8px;
|
|
2356
|
+
z-index: 10;
|
|
2357
|
+
display: flex;
|
|
2358
|
+
gap: 4px;
|
|
2359
|
+
opacity: 0;
|
|
2360
|
+
transition: opacity var(--fb-transition-duration, 200ms);
|
|
2361
|
+
pointer-events: none;
|
|
2362
|
+
}
|
|
2363
|
+
.fb-video-preview-wrap:hover .fb-video-btn-overlay {
|
|
2364
|
+
opacity: 1;
|
|
2365
|
+
pointer-events: auto;
|
|
2366
|
+
}
|
|
2367
|
+
.fb-video-btn {
|
|
2368
|
+
border: none;
|
|
2369
|
+
border-radius: var(--fb-border-radius, 4px);
|
|
2370
|
+
font-size: 11px;
|
|
2371
|
+
padding: 4px 8px;
|
|
2372
|
+
cursor: pointer;
|
|
2373
|
+
color: #fff;
|
|
2374
|
+
line-height: 1.2;
|
|
2375
|
+
}
|
|
2376
|
+
.fb-video-btn-delete {
|
|
2377
|
+
background: rgba(220, 38, 38, 0.85);
|
|
2378
|
+
}
|
|
2379
|
+
.fb-video-btn-delete:hover {
|
|
2380
|
+
background: rgba(185, 28, 28, 0.95);
|
|
2381
|
+
}
|
|
2382
|
+
.fb-video-btn-change {
|
|
2383
|
+
background: rgba(31, 41, 55, 0.85);
|
|
2384
|
+
}
|
|
2385
|
+
.fb-video-btn-change:hover {
|
|
2386
|
+
background: rgba(17, 24, 39, 0.95);
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
/* Tile action icon buttons (download / open / remove) \u2014 shown on tile hover */
|
|
2390
|
+
.fb-tile-actions {
|
|
2391
|
+
position: absolute;
|
|
2392
|
+
top: 3px;
|
|
2393
|
+
right: 3px;
|
|
2394
|
+
display: flex;
|
|
2395
|
+
flex-direction: row;
|
|
2396
|
+
gap: 3px;
|
|
2397
|
+
opacity: 0;
|
|
2398
|
+
transition: opacity var(--fb-transition-duration, 200ms);
|
|
2399
|
+
z-index: 10;
|
|
2400
|
+
}
|
|
2401
|
+
.fb-tile-resource:hover .fb-tile-actions {
|
|
2402
|
+
opacity: 1;
|
|
2403
|
+
}
|
|
2404
|
+
.fb-tile-action-btn {
|
|
2405
|
+
width: 28px;
|
|
2406
|
+
height: 28px;
|
|
2407
|
+
display: flex;
|
|
2408
|
+
align-items: center;
|
|
2409
|
+
justify-content: center;
|
|
2410
|
+
border: none;
|
|
2411
|
+
border-radius: 50%;
|
|
2412
|
+
cursor: pointer;
|
|
2413
|
+
background: rgba(31, 41, 55, 0.75);
|
|
2414
|
+
color: #fff;
|
|
2415
|
+
padding: 0;
|
|
2416
|
+
flex-shrink: 0;
|
|
2417
|
+
transition:
|
|
2418
|
+
background var(--fb-transition-duration, 200ms),
|
|
2419
|
+
opacity var(--fb-transition-duration, 200ms);
|
|
2420
|
+
}
|
|
2421
|
+
.fb-tile-action-btn:hover {
|
|
2422
|
+
background: rgba(17, 24, 39, 0.95);
|
|
2423
|
+
}
|
|
2424
|
+
.fb-tile-action-remove {
|
|
2425
|
+
background: rgba(220, 38, 38, 0.8);
|
|
2426
|
+
}
|
|
2427
|
+
.fb-tile-action-remove:hover {
|
|
2428
|
+
background: rgba(185, 28, 28, 0.95);
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
/* Actions row inside zoom popup \u2014 always visible while popup is shown */
|
|
2432
|
+
.fb-tile-zoom-preview .fb-tile-actions {
|
|
2433
|
+
position: absolute;
|
|
2434
|
+
top: 6px;
|
|
2435
|
+
right: 6px;
|
|
2436
|
+
opacity: 1;
|
|
2437
|
+
z-index: 10000;
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
/* Hover zoom preview popup for image tiles \u2014 appended to document.body (fixed) */
|
|
2441
|
+
.fb-tile-zoom-preview {
|
|
2442
|
+
position: fixed;
|
|
2443
|
+
z-index: 9999;
|
|
2444
|
+
background: var(--fb-background-color, #fff);
|
|
2445
|
+
border: 1px solid var(--fb-file-upload-border-color, #d1d5db);
|
|
2446
|
+
border-radius: var(--fb-border-radius, 0.5rem);
|
|
2447
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.18);
|
|
2448
|
+
padding: 4px;
|
|
2449
|
+
width: 350px;
|
|
2450
|
+
height: 350px;
|
|
2451
|
+
pointer-events: none;
|
|
2452
|
+
opacity: 0;
|
|
2453
|
+
transition: opacity 150ms ease;
|
|
2454
|
+
}
|
|
2455
|
+
.fb-tile-zoom-preview.fb-tile-zoom-preview--visible {
|
|
2456
|
+
opacity: 1;
|
|
2457
|
+
}
|
|
2458
|
+
.fb-tile-zoom-preview-img {
|
|
2459
|
+
width: 100%;
|
|
2460
|
+
height: 100%;
|
|
2461
|
+
object-fit: contain;
|
|
2462
|
+
display: block;
|
|
2463
|
+
background: var(--fb-file-upload-bg-color, #f3f4f6);
|
|
2464
|
+
border-radius: calc(var(--fb-border-radius, 0.5rem) - 2px);
|
|
2465
|
+
}
|
|
2466
|
+
`;
|
|
2467
|
+
document.head.appendChild(style);
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
// src/components/file/dom.ts
|
|
2471
|
+
var TILE_SIZE = "160px";
|
|
2472
|
+
function createFileTile() {
|
|
2473
|
+
ensureFileStyles();
|
|
2474
|
+
const tile = document.createElement("div");
|
|
2475
|
+
tile.className = "fb-tile";
|
|
2476
|
+
return tile;
|
|
2477
|
+
}
|
|
2478
|
+
function showFileError(container, message) {
|
|
2479
|
+
var _a, _b;
|
|
2480
|
+
const existing = (_a = container.closest(".space-y-2")) == null ? void 0 : _a.querySelector(".file-error-message");
|
|
2481
|
+
if (existing) existing.remove();
|
|
2482
|
+
const errorEl = document.createElement("div");
|
|
2483
|
+
errorEl.className = "file-error-message error-message";
|
|
2484
|
+
errorEl.style.cssText = `
|
|
2485
|
+
color: var(--fb-error-color);
|
|
2486
|
+
font-size: var(--fb-font-size-small);
|
|
2487
|
+
margin-top: 0.25rem;
|
|
2488
|
+
`;
|
|
2489
|
+
errorEl.textContent = message;
|
|
2490
|
+
(_b = container.closest(".space-y-2")) == null ? void 0 : _b.appendChild(errorEl);
|
|
2491
|
+
}
|
|
2492
|
+
function clearFileError(container) {
|
|
2493
|
+
var _a;
|
|
2494
|
+
const existing = (_a = container.closest(".space-y-2")) == null ? void 0 : _a.querySelector(".file-error-message");
|
|
2495
|
+
if (existing) existing.remove();
|
|
2496
|
+
}
|
|
2497
|
+
function addDeleteButton(container, state, onDelete) {
|
|
2498
|
+
const existingOverlay = container.querySelector(".delete-overlay");
|
|
2499
|
+
if (existingOverlay) existingOverlay.remove();
|
|
2500
|
+
const overlay = document.createElement("div");
|
|
2501
|
+
overlay.className = "delete-overlay absolute inset-0 bg-black bg-opacity-50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center";
|
|
2502
|
+
const deleteBtn = document.createElement("button");
|
|
2503
|
+
deleteBtn.className = "bg-red-600 text-white px-3 py-1 rounded text-sm hover:bg-red-700 transition-colors";
|
|
2504
|
+
deleteBtn.textContent = t("removeElement", state);
|
|
2505
|
+
deleteBtn.onclick = (e) => {
|
|
2506
|
+
e.stopPropagation();
|
|
2507
|
+
onDelete();
|
|
2508
|
+
};
|
|
2509
|
+
overlay.appendChild(deleteBtn);
|
|
2510
|
+
container.appendChild(overlay);
|
|
2511
|
+
}
|
|
2512
|
+
function findFilePicker(container) {
|
|
2513
|
+
var _a;
|
|
2514
|
+
let el = container.parentElement;
|
|
2515
|
+
while (el && !el.dataset.filesWrapper) {
|
|
2516
|
+
el = el.parentElement;
|
|
2517
|
+
}
|
|
2518
|
+
return (_a = el == null ? void 0 : el.querySelector('input[type="file"]')) != null ? _a : null;
|
|
2519
|
+
}
|
|
2520
|
+
function createUploadingTile(fileName, state) {
|
|
2521
|
+
ensureFileStyles();
|
|
2522
|
+
const tile = createFileTile();
|
|
2523
|
+
tile.classList.add("fb-tile-uploading");
|
|
2524
|
+
tile.className += " fb-uploading-tile";
|
|
2525
|
+
const label = fileName.length > 10 ? fileName.substring(0, 8) + "\u2026" : fileName;
|
|
2526
|
+
tile.innerHTML = `
|
|
2527
|
+
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:6px;padding:4px;">
|
|
2528
|
+
<div class="fb-spinner"></div>
|
|
2529
|
+
<div class="fb-tile-label">${escapeHtml(label)}</div>
|
|
2530
|
+
<div class="fb-tile-uploading-text">${escapeHtml(t("uploadingFile", state))}</div>
|
|
2531
|
+
</div>`;
|
|
2532
|
+
return tile;
|
|
2533
|
+
}
|
|
2534
|
+
function ensureTilesWrap(list) {
|
|
2535
|
+
const existing = list.querySelector(".fb-tiles-wrap");
|
|
2536
|
+
if (existing) return existing;
|
|
2537
|
+
const dropzone = list.querySelector(".fb-file-dropzone");
|
|
2538
|
+
if (dropzone) dropzone.remove();
|
|
2539
|
+
const tilesWrap = document.createElement("div");
|
|
2540
|
+
tilesWrap.className = "fb-tiles-wrap";
|
|
2541
|
+
tilesWrap.style.cssText = "display:flex;flex-wrap:wrap;gap:6px;align-items:flex-start;";
|
|
2542
|
+
const addTile = document.createElement("div");
|
|
2543
|
+
addTile.className = "fb-tile fb-tile-add";
|
|
2544
|
+
addTile.innerHTML = "+";
|
|
2545
|
+
tilesWrap.appendChild(addTile);
|
|
2546
|
+
list.appendChild(tilesWrap);
|
|
2547
|
+
return tilesWrap;
|
|
2548
|
+
}
|
|
2549
|
+
function setEmptyFileContainer(fileContainer, state, hint) {
|
|
2550
|
+
const hintHtml = hint ? `<div class="text-xs text-gray-500 mt-1">${escapeHtml(hint)}</div>` : "";
|
|
2551
|
+
fileContainer.innerHTML = `
|
|
2552
|
+
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
2553
|
+
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
2554
|
+
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
|
|
2555
|
+
</svg>
|
|
2556
|
+
<div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
|
|
2557
|
+
${hintHtml}
|
|
2558
|
+
</div>
|
|
2559
|
+
`;
|
|
2560
|
+
}
|
|
2561
|
+
function setupDragAndDrop(element, dropHandler) {
|
|
2562
|
+
element.addEventListener("dragover", (e) => {
|
|
2563
|
+
e.preventDefault();
|
|
2564
|
+
element.classList.add("border-blue-500", "bg-blue-50");
|
|
2565
|
+
});
|
|
2566
|
+
element.addEventListener("dragleave", (e) => {
|
|
2567
|
+
e.preventDefault();
|
|
2568
|
+
element.classList.remove("border-blue-500", "bg-blue-50");
|
|
2569
|
+
});
|
|
2570
|
+
element.addEventListener("drop", (e) => {
|
|
2571
|
+
var _a;
|
|
2572
|
+
e.preventDefault();
|
|
2573
|
+
element.classList.remove("border-blue-500", "bg-blue-50");
|
|
2574
|
+
if ((_a = e.dataTransfer) == null ? void 0 : _a.files) {
|
|
2575
|
+
dropHandler(e.dataTransfer.files);
|
|
2576
|
+
}
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// src/components/file/preview.ts
|
|
2581
|
+
var ICON_DOWNLOAD = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`;
|
|
2582
|
+
var ICON_OPEN = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>`;
|
|
2583
|
+
var ICON_REMOVE = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"/></svg>`;
|
|
2584
|
+
function canDownload(state, meta) {
|
|
2585
|
+
return Boolean(
|
|
2586
|
+
state.config.downloadFile || state.config.getDownloadUrl || state.config.getThumbnail || (meta == null ? void 0 : meta.file)
|
|
2587
|
+
);
|
|
2588
|
+
}
|
|
2589
|
+
function canOpenInTab(state, meta) {
|
|
2590
|
+
return Boolean(
|
|
2591
|
+
state.config.getDownloadUrl || state.config.getThumbnail || (meta == null ? void 0 : meta.file)
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
function createTileActions(options) {
|
|
2595
|
+
const { canRemove, removeHandler, state, resourceId, fileName, meta } = options;
|
|
2596
|
+
const group = document.createElement("div");
|
|
2597
|
+
group.className = "fb-tile-actions";
|
|
2598
|
+
const makeBtn = (icon, label, cls) => {
|
|
2599
|
+
const btn = document.createElement("button");
|
|
2600
|
+
btn.type = "button";
|
|
2601
|
+
btn.className = `fb-tile-action-btn ${cls}`;
|
|
2602
|
+
btn.innerHTML = icon;
|
|
2603
|
+
btn.title = label;
|
|
2604
|
+
btn.setAttribute("aria-label", label);
|
|
2605
|
+
btn.addEventListener("click", (e) => {
|
|
2606
|
+
e.stopPropagation();
|
|
2607
|
+
});
|
|
2608
|
+
return btn;
|
|
2609
|
+
};
|
|
2610
|
+
if (canDownload(state, meta)) {
|
|
2611
|
+
const dlBtn = makeBtn(ICON_DOWNLOAD, t("downloadFile", state), "fb-tile-action-download");
|
|
2612
|
+
dlBtn.addEventListener("click", () => {
|
|
2613
|
+
triggerTileDownload(resourceId, fileName, state, meta);
|
|
2614
|
+
});
|
|
2615
|
+
group.appendChild(dlBtn);
|
|
2616
|
+
}
|
|
2617
|
+
if (canOpenInTab(state, meta)) {
|
|
2618
|
+
const openBtn = makeBtn(ICON_OPEN, t("openInNewTab", state), "fb-tile-action-open");
|
|
2619
|
+
openBtn.addEventListener("click", () => {
|
|
2620
|
+
triggerTileOpen(resourceId, state, meta).catch((err) => {
|
|
2621
|
+
console.error("Open failed:", err);
|
|
2622
|
+
});
|
|
2623
|
+
});
|
|
2624
|
+
group.appendChild(openBtn);
|
|
2625
|
+
}
|
|
2626
|
+
if (canRemove && removeHandler) {
|
|
2627
|
+
const rmBtn = makeBtn(ICON_REMOVE, t("removeElement", state), "fb-tile-action-remove");
|
|
2628
|
+
rmBtn.addEventListener("click", () => {
|
|
2629
|
+
removeHandler();
|
|
2630
|
+
});
|
|
2631
|
+
group.appendChild(rmBtn);
|
|
2632
|
+
}
|
|
2633
|
+
return group;
|
|
2634
|
+
}
|
|
2635
|
+
var localFileUrlCache = /* @__PURE__ */ new WeakMap();
|
|
2636
|
+
function getLocalFileUrl(file) {
|
|
2637
|
+
let url = localFileUrlCache.get(file);
|
|
2638
|
+
if (!url) {
|
|
2639
|
+
url = URL.createObjectURL(file);
|
|
2640
|
+
localFileUrlCache.set(file, url);
|
|
2641
|
+
}
|
|
2642
|
+
return url;
|
|
2643
|
+
}
|
|
2644
|
+
function releaseLocalFileUrl(file) {
|
|
2645
|
+
if (!file) return;
|
|
2646
|
+
const url = localFileUrlCache.get(file);
|
|
2647
|
+
if (url) {
|
|
2648
|
+
URL.revokeObjectURL(url);
|
|
2649
|
+
localFileUrlCache.delete(file);
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
function triggerTileDownload(resourceId, fileName, state, meta) {
|
|
2653
|
+
if (state.config.downloadFile) {
|
|
2654
|
+
state.config.downloadFile(resourceId, fileName);
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
if ((meta == null ? void 0 : meta.file) instanceof File) {
|
|
2658
|
+
downloadBlob(meta.file, fileName || meta.file.name);
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2661
|
+
forceDownload(resourceId, fileName, state).catch((err) => {
|
|
2662
|
+
console.error("Download failed:", err);
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
async function triggerTileOpen(resourceId, state, meta) {
|
|
2666
|
+
let url = null;
|
|
2667
|
+
if (state.config.getDownloadUrl) {
|
|
2668
|
+
url = state.config.getDownloadUrl(resourceId);
|
|
2669
|
+
} else if (state.config.getThumbnail) {
|
|
2670
|
+
url = await state.config.getThumbnail(resourceId);
|
|
2671
|
+
} else if ((meta == null ? void 0 : meta.file) instanceof File) {
|
|
2672
|
+
url = getLocalFileUrl(meta.file);
|
|
2673
|
+
}
|
|
2674
|
+
if (url) {
|
|
2675
|
+
window.open(url, "_blank");
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
var sharedZoomPopup = null;
|
|
2679
|
+
var zoomTimer = null;
|
|
2680
|
+
var zoomHideTimer = null;
|
|
2681
|
+
var zoomOwner = null;
|
|
2682
|
+
function getOrCreateZoomPopup() {
|
|
2683
|
+
if (!sharedZoomPopup) {
|
|
2684
|
+
sharedZoomPopup = document.createElement("div");
|
|
2685
|
+
sharedZoomPopup.className = "fb-tile-zoom-preview";
|
|
2686
|
+
const img = document.createElement("img");
|
|
2687
|
+
img.className = "fb-tile-zoom-preview-img";
|
|
2688
|
+
sharedZoomPopup.appendChild(img);
|
|
2689
|
+
sharedZoomPopup.addEventListener("mouseenter", cancelHideZoomPopup);
|
|
2690
|
+
sharedZoomPopup.addEventListener("mouseleave", scheduleHideZoomPopup);
|
|
2691
|
+
}
|
|
2692
|
+
return sharedZoomPopup;
|
|
2693
|
+
}
|
|
2694
|
+
function positionZoomPopup(popup, tile) {
|
|
2695
|
+
const tileRect = tile.getBoundingClientRect();
|
|
2696
|
+
const popupSize = 350;
|
|
2697
|
+
const margin = 6;
|
|
2698
|
+
const padding = 8;
|
|
2699
|
+
let top;
|
|
2700
|
+
if (tileRect.top - popupSize - margin >= padding) {
|
|
2701
|
+
top = tileRect.top - popupSize - margin;
|
|
2702
|
+
} else if (tileRect.bottom + margin + popupSize + padding <= window.innerHeight) {
|
|
2703
|
+
top = tileRect.bottom + margin;
|
|
2704
|
+
} else {
|
|
2705
|
+
top = Math.max(padding, Math.min(window.innerHeight - popupSize - padding, tileRect.top));
|
|
2706
|
+
}
|
|
2707
|
+
const tileCenterX = tileRect.left + tileRect.width / 2;
|
|
2708
|
+
let left = tileCenterX - popupSize / 2;
|
|
2709
|
+
left = Math.max(padding, Math.min(window.innerWidth - popupSize - padding, left));
|
|
2710
|
+
popup.style.top = `${top}px`;
|
|
2711
|
+
popup.style.left = `${left}px`;
|
|
2712
|
+
}
|
|
2713
|
+
function scheduleHideZoomPopup() {
|
|
2714
|
+
if (zoomHideTimer !== null) {
|
|
2715
|
+
clearTimeout(zoomHideTimer);
|
|
2716
|
+
}
|
|
2717
|
+
zoomHideTimer = setTimeout(() => {
|
|
2718
|
+
zoomHideTimer = null;
|
|
2719
|
+
removeZoomPopupNow();
|
|
2720
|
+
}, 100);
|
|
2721
|
+
}
|
|
2722
|
+
function cancelHideZoomPopup() {
|
|
2723
|
+
if (zoomHideTimer !== null) {
|
|
2724
|
+
clearTimeout(zoomHideTimer);
|
|
2725
|
+
zoomHideTimer = null;
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
function removeZoomPopupNow() {
|
|
2729
|
+
if (zoomTimer !== null) {
|
|
2730
|
+
clearTimeout(zoomTimer);
|
|
2731
|
+
zoomTimer = null;
|
|
2732
|
+
}
|
|
2733
|
+
if (sharedZoomPopup && sharedZoomPopup.parentNode) {
|
|
2734
|
+
sharedZoomPopup.classList.remove("fb-tile-zoom-preview--visible");
|
|
2735
|
+
sharedZoomPopup.parentNode.removeChild(sharedZoomPopup);
|
|
2736
|
+
}
|
|
2737
|
+
zoomOwner = null;
|
|
2738
|
+
}
|
|
2739
|
+
function attachZoomHover(tile, src, alt, actionsEl) {
|
|
2740
|
+
tile.dataset.zoomSrc = src;
|
|
2741
|
+
tile.dataset.zoomAlt = alt;
|
|
2742
|
+
tile.addEventListener("mouseenter", () => {
|
|
2743
|
+
cancelHideZoomPopup();
|
|
2744
|
+
if (zoomOwner !== tile) {
|
|
2745
|
+
removeZoomPopupNow();
|
|
2746
|
+
}
|
|
2747
|
+
zoomOwner = tile;
|
|
2748
|
+
zoomTimer = setTimeout(() => {
|
|
2749
|
+
zoomTimer = null;
|
|
2750
|
+
const popup = getOrCreateZoomPopup();
|
|
2751
|
+
const existingActions = popup.querySelector(".fb-tile-actions");
|
|
2752
|
+
if (existingActions) existingActions.remove();
|
|
2753
|
+
const img = popup.querySelector(".fb-tile-zoom-preview-img");
|
|
2754
|
+
img.src = src;
|
|
2755
|
+
img.alt = alt;
|
|
2756
|
+
if (actionsEl) {
|
|
2757
|
+
popup.appendChild(actionsEl.cloneNode(true));
|
|
2758
|
+
attachClonedActionListeners(
|
|
2759
|
+
popup.querySelector(".fb-tile-actions"),
|
|
2760
|
+
actionsEl
|
|
2761
|
+
);
|
|
2762
|
+
}
|
|
2763
|
+
popup.style.pointerEvents = "auto";
|
|
2764
|
+
positionZoomPopup(popup, tile);
|
|
2765
|
+
document.body.appendChild(popup);
|
|
2766
|
+
popup.getBoundingClientRect();
|
|
2767
|
+
popup.classList.add("fb-tile-zoom-preview--visible");
|
|
2768
|
+
}, 200);
|
|
2769
|
+
});
|
|
2770
|
+
tile.addEventListener("mouseleave", () => {
|
|
2771
|
+
if (zoomTimer !== null) {
|
|
2772
|
+
clearTimeout(zoomTimer);
|
|
2773
|
+
zoomTimer = null;
|
|
2774
|
+
zoomOwner = null;
|
|
2775
|
+
} else {
|
|
2776
|
+
scheduleHideZoomPopup();
|
|
2777
|
+
}
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
function attachClonedActionListeners(cloned, original) {
|
|
2781
|
+
const originalBtns = Array.from(original.querySelectorAll(".fb-tile-action-btn"));
|
|
2782
|
+
const clonedBtns = Array.from(cloned.querySelectorAll(".fb-tile-action-btn"));
|
|
2783
|
+
clonedBtns.forEach((clonedBtn, i) => {
|
|
2784
|
+
const origBtn = originalBtns[i];
|
|
2785
|
+
if (origBtn) {
|
|
2786
|
+
clonedBtn.addEventListener("click", (e) => {
|
|
2787
|
+
e.stopPropagation();
|
|
2788
|
+
origBtn.click();
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2143
2793
|
function renderLocalImagePreview(container, file, fileName, state) {
|
|
2144
2794
|
const img = document.createElement("img");
|
|
2145
2795
|
img.className = "w-full h-full object-contain";
|
|
2796
|
+
img.style.background = "var(--fb-file-upload-bg-color,#f3f4f6)";
|
|
2146
2797
|
img.alt = fileName || t("previewAlt", state);
|
|
2147
2798
|
const reader = new FileReader();
|
|
2148
2799
|
reader.onload = (e) => {
|
|
@@ -2152,23 +2803,27 @@ function renderLocalImagePreview(container, file, fileName, state) {
|
|
|
2152
2803
|
reader.readAsDataURL(file);
|
|
2153
2804
|
container.appendChild(img);
|
|
2154
2805
|
}
|
|
2155
|
-
function
|
|
2156
|
-
const videoUrl = URL.createObjectURL(file);
|
|
2806
|
+
function setupDragDropless(container, _deps) {
|
|
2157
2807
|
container.onclick = null;
|
|
2158
2808
|
const newContainer = container.cloneNode(false);
|
|
2159
2809
|
if (container.parentNode) {
|
|
2160
2810
|
container.parentNode.replaceChild(newContainer, container);
|
|
2161
2811
|
}
|
|
2812
|
+
return newContainer;
|
|
2813
|
+
}
|
|
2814
|
+
function renderLocalVideoPreview(container, file, videoType, resourceId, state, deps) {
|
|
2815
|
+
const videoUrl = URL.createObjectURL(file);
|
|
2816
|
+
const newContainer = setupDragDropless(container);
|
|
2162
2817
|
newContainer.innerHTML = `
|
|
2163
|
-
<div class="
|
|
2818
|
+
<div class="fb-video-preview-wrap">
|
|
2164
2819
|
<video class="w-full h-full object-contain" controls preload="auto" muted src="${videoUrl}">
|
|
2165
2820
|
${escapeHtml(t("videoNotSupported", state))}
|
|
2166
2821
|
</video>
|
|
2167
|
-
<div class="
|
|
2168
|
-
<button class="
|
|
2822
|
+
<div class="fb-video-btn-overlay">
|
|
2823
|
+
<button class="fb-video-btn fb-video-btn-delete delete-file-btn">
|
|
2169
2824
|
${escapeHtml(t("removeElement", state))}
|
|
2170
2825
|
</button>
|
|
2171
|
-
<button class="
|
|
2826
|
+
<button class="fb-video-btn fb-video-btn-change change-file-btn">
|
|
2172
2827
|
${escapeHtml(t("changeButton", state))}
|
|
2173
2828
|
</button>
|
|
2174
2829
|
</div>
|
|
@@ -2178,20 +2833,15 @@ function renderLocalVideoPreview(container, file, videoType, resourceId, state,
|
|
|
2178
2833
|
return newContainer;
|
|
2179
2834
|
}
|
|
2180
2835
|
function attachVideoButtonHandlers(container, resourceId, state, deps) {
|
|
2181
|
-
const changeBtn = container.querySelector(
|
|
2182
|
-
".change-file-btn"
|
|
2183
|
-
);
|
|
2836
|
+
const changeBtn = container.querySelector(".change-file-btn");
|
|
2184
2837
|
if (changeBtn) {
|
|
2185
2838
|
changeBtn.onclick = (e) => {
|
|
2839
|
+
var _a;
|
|
2186
2840
|
e.stopPropagation();
|
|
2187
|
-
|
|
2188
|
-
deps.picker.click();
|
|
2189
|
-
}
|
|
2841
|
+
(_a = deps == null ? void 0 : deps.picker) == null ? void 0 : _a.click();
|
|
2190
2842
|
};
|
|
2191
2843
|
}
|
|
2192
|
-
const deleteBtn = container.querySelector(
|
|
2193
|
-
".delete-file-btn"
|
|
2194
|
-
);
|
|
2844
|
+
const deleteBtn = container.querySelector(".delete-file-btn");
|
|
2195
2845
|
if (deleteBtn) {
|
|
2196
2846
|
deleteBtn.onclick = (e) => {
|
|
2197
2847
|
e.stopPropagation();
|
|
@@ -2211,9 +2861,6 @@ function handleVideoDelete(container, resourceId, state, deps) {
|
|
|
2211
2861
|
if (deps == null ? void 0 : deps.fileUploadHandler) {
|
|
2212
2862
|
container.onclick = deps.fileUploadHandler;
|
|
2213
2863
|
}
|
|
2214
|
-
if (deps == null ? void 0 : deps.dragHandler) {
|
|
2215
|
-
setupDragAndDrop(container, deps.dragHandler);
|
|
2216
|
-
}
|
|
2217
2864
|
container.innerHTML = `
|
|
2218
2865
|
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
2219
2866
|
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
@@ -2222,16 +2869,9 @@ function handleVideoDelete(container, resourceId, state, deps) {
|
|
|
2222
2869
|
<div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
|
|
2223
2870
|
</div>
|
|
2224
2871
|
`;
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
video.className = "w-full h-full object-contain";
|
|
2229
|
-
video.controls = true;
|
|
2230
|
-
video.preload = "metadata";
|
|
2231
|
-
video.muted = true;
|
|
2232
|
-
video.src = thumbnailUrl;
|
|
2233
|
-
video.appendChild(document.createTextNode(t("videoNotSupported", state)));
|
|
2234
|
-
container.appendChild(video);
|
|
2872
|
+
if (deps == null ? void 0 : deps.setupDrop) {
|
|
2873
|
+
deps.setupDrop(container);
|
|
2874
|
+
}
|
|
2235
2875
|
}
|
|
2236
2876
|
function renderDeleteButton(container, resourceId, state) {
|
|
2237
2877
|
addDeleteButton(container, state, () => {
|
|
@@ -2254,13 +2894,12 @@ function renderDeleteButton(container, resourceId, state) {
|
|
|
2254
2894
|
});
|
|
2255
2895
|
}
|
|
2256
2896
|
async function renderLocalFilePreview(container, meta, fileName, resourceId, isReadonly, state, deps) {
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
if (meta.type && meta.type.startsWith("image/")) {
|
|
2897
|
+
var _a, _b, _c;
|
|
2898
|
+
if (!meta.file || !(meta.file instanceof File)) return;
|
|
2899
|
+
if ((_a = meta.type) == null ? void 0 : _a.startsWith("image/")) {
|
|
2261
2900
|
renderLocalImagePreview(container, meta.file, fileName, state);
|
|
2262
|
-
} else if (meta.type
|
|
2263
|
-
|
|
2901
|
+
} else if ((_b = meta.type) == null ? void 0 : _b.startsWith("video/")) {
|
|
2902
|
+
container = renderLocalVideoPreview(
|
|
2264
2903
|
container,
|
|
2265
2904
|
meta.file,
|
|
2266
2905
|
meta.type,
|
|
@@ -2268,15 +2907,25 @@ async function renderLocalFilePreview(container, meta, fileName, resourceId, isR
|
|
|
2268
2907
|
state,
|
|
2269
2908
|
deps
|
|
2270
2909
|
);
|
|
2271
|
-
container = newContainer;
|
|
2272
2910
|
} else {
|
|
2273
|
-
container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div
|
|
2911
|
+
container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div style="font-size:36px;" class="mb-2">\u{1F4C1}</div><div class="text-sm">${escapeHtml(fileName)}</div></div>`;
|
|
2274
2912
|
}
|
|
2275
|
-
if (!isReadonly && !(meta.type
|
|
2913
|
+
if (!isReadonly && !((_c = meta.type) == null ? void 0 : _c.startsWith("video/"))) {
|
|
2276
2914
|
renderDeleteButton(container, resourceId, state);
|
|
2277
2915
|
}
|
|
2278
2916
|
}
|
|
2917
|
+
function renderUploadedVideoPreview(container, thumbnailUrl, state) {
|
|
2918
|
+
const video = document.createElement("video");
|
|
2919
|
+
video.className = "w-full h-full object-contain";
|
|
2920
|
+
video.controls = true;
|
|
2921
|
+
video.preload = "metadata";
|
|
2922
|
+
video.muted = true;
|
|
2923
|
+
video.src = thumbnailUrl;
|
|
2924
|
+
video.appendChild(document.createTextNode(t("videoNotSupported", state)));
|
|
2925
|
+
container.appendChild(video);
|
|
2926
|
+
}
|
|
2279
2927
|
async function renderUploadedFilePreview(container, resourceId, fileName, meta, state) {
|
|
2928
|
+
var _a;
|
|
2280
2929
|
if (!state.config.getThumbnail) {
|
|
2281
2930
|
setEmptyFileContainer(container, state);
|
|
2282
2931
|
return;
|
|
@@ -2285,11 +2934,12 @@ async function renderUploadedFilePreview(container, resourceId, fileName, meta,
|
|
|
2285
2934
|
const thumbnailUrl = await state.config.getThumbnail(resourceId);
|
|
2286
2935
|
if (thumbnailUrl) {
|
|
2287
2936
|
clear(container);
|
|
2288
|
-
if (meta
|
|
2289
|
-
renderUploadedVideoPreview(container, thumbnailUrl,
|
|
2937
|
+
if ((_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("video/")) {
|
|
2938
|
+
renderUploadedVideoPreview(container, thumbnailUrl, state);
|
|
2290
2939
|
} else {
|
|
2291
2940
|
const img = document.createElement("img");
|
|
2292
2941
|
img.className = "w-full h-full object-contain";
|
|
2942
|
+
img.style.background = "var(--fb-file-upload-bg-color,#f3f4f6)";
|
|
2293
2943
|
img.alt = fileName || t("previewAlt", state);
|
|
2294
2944
|
img.src = thumbnailUrl;
|
|
2295
2945
|
container.appendChild(img);
|
|
@@ -2318,9 +2968,6 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
|
|
|
2318
2968
|
);
|
|
2319
2969
|
}
|
|
2320
2970
|
clear(container);
|
|
2321
|
-
if (isReadonly) {
|
|
2322
|
-
container.classList.add("cursor-pointer");
|
|
2323
|
-
}
|
|
2324
2971
|
const meta = state.resourceIndex.get(resourceId);
|
|
2325
2972
|
if (meta && meta.file && meta.file instanceof File) {
|
|
2326
2973
|
await renderLocalFilePreview(
|
|
@@ -2333,369 +2980,296 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
|
|
|
2333
2980
|
deps
|
|
2334
2981
|
);
|
|
2335
2982
|
} else {
|
|
2336
|
-
await renderUploadedFilePreview(
|
|
2337
|
-
container,
|
|
2338
|
-
resourceId,
|
|
2339
|
-
fileName,
|
|
2340
|
-
meta,
|
|
2341
|
-
state
|
|
2342
|
-
);
|
|
2983
|
+
await renderUploadedFilePreview(container, resourceId, fileName, meta, state);
|
|
2343
2984
|
const isVideo = (_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("video/");
|
|
2344
2985
|
if (!isReadonly && !isVideo) {
|
|
2345
2986
|
renderDeleteButton(container, resourceId, state);
|
|
2346
2987
|
}
|
|
2347
2988
|
}
|
|
2348
2989
|
}
|
|
2349
|
-
|
|
2990
|
+
function resolveFileName(resourceId, meta, fileName) {
|
|
2991
|
+
var _a;
|
|
2992
|
+
if (fileName) return fileName;
|
|
2993
|
+
if ((_a = meta == null ? void 0 : meta.name) == null ? void 0 : _a.includes(".")) return meta.name;
|
|
2994
|
+
const basename = resourceId.includes("/") ? resourceId.split("/").pop() : resourceId;
|
|
2995
|
+
return (basename == null ? void 0 : basename.includes(".")) ? basename : "";
|
|
2996
|
+
}
|
|
2997
|
+
async function renderFilePreviewReadonly(resourceId, state, fileName, options = {}) {
|
|
2350
2998
|
var _a, _b;
|
|
2351
2999
|
const meta = state.resourceIndex.get(resourceId);
|
|
2352
|
-
const actualFileName = (
|
|
2353
|
-
const
|
|
2354
|
-
const
|
|
2355
|
-
|
|
2356
|
-
const
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
}
|
|
2362
|
-
const
|
|
2363
|
-
const
|
|
2364
|
-
|
|
3000
|
+
const actualFileName = resolveFileName(resourceId, meta, fileName);
|
|
3001
|
+
const { canRemove = false, removeHandler = null } = options;
|
|
3002
|
+
const isImage = ((_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("image/")) || Boolean(actualFileName.toLowerCase().match(/\.(jpg|jpeg|png|gif|webp)$/));
|
|
3003
|
+
const isVideo = ((_b = meta == null ? void 0 : meta.type) == null ? void 0 : _b.startsWith("video/")) || Boolean(actualFileName.toLowerCase().match(/\.(mp4|webm|avi|mov)$/));
|
|
3004
|
+
const tile = createFileTile();
|
|
3005
|
+
tile.classList.add("fb-tile-resource");
|
|
3006
|
+
tile.style.cursor = "pointer";
|
|
3007
|
+
if (actualFileName) {
|
|
3008
|
+
tile.title = actualFileName;
|
|
3009
|
+
}
|
|
3010
|
+
const localFileUrl = (meta == null ? void 0 : meta.file) instanceof File ? getLocalFileUrl(meta.file) : null;
|
|
3011
|
+
const resolveOpenUrl = async () => {
|
|
3012
|
+
if (state.config.getDownloadUrl) return state.config.getDownloadUrl(resourceId);
|
|
3013
|
+
if (state.config.getThumbnail) return state.config.getThumbnail(resourceId);
|
|
3014
|
+
return localFileUrl;
|
|
3015
|
+
};
|
|
3016
|
+
tile.onclick = async () => {
|
|
3017
|
+
const url = await resolveOpenUrl();
|
|
3018
|
+
if (url) {
|
|
3019
|
+
window.open(url, "_blank");
|
|
3020
|
+
} else if (state.config.downloadFile) {
|
|
3021
|
+
state.config.downloadFile(resourceId, actualFileName);
|
|
3022
|
+
} else {
|
|
3023
|
+
forceDownload(resourceId, actualFileName, state).catch((err) => {
|
|
3024
|
+
console.error("Download failed:", err);
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3027
|
+
};
|
|
3028
|
+
const actionsEl = createTileActions({
|
|
3029
|
+
canRemove,
|
|
3030
|
+
removeHandler,
|
|
3031
|
+
state,
|
|
3032
|
+
resourceId,
|
|
3033
|
+
fileName: actualFileName,
|
|
3034
|
+
meta
|
|
3035
|
+
});
|
|
3036
|
+
const resolveImageDisplayUrl = async () => {
|
|
2365
3037
|
if (state.config.getThumbnail) {
|
|
2366
3038
|
try {
|
|
2367
|
-
const
|
|
2368
|
-
if (
|
|
2369
|
-
|
|
2370
|
-
} else {
|
|
2371
|
-
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F5BC}\uFE0F</div><div class="text-sm">${escapeHtml(actualFileName)}</div></div></div>`;
|
|
2372
|
-
}
|
|
2373
|
-
} catch (error) {
|
|
2374
|
-
console.warn("getThumbnail failed for", resourceId, error);
|
|
2375
|
-
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F5BC}\uFE0F</div><div class="text-sm">${escapeHtml(actualFileName)}</div></div></div>`;
|
|
3039
|
+
const url = await state.config.getThumbnail(resourceId);
|
|
3040
|
+
if (url) return url;
|
|
3041
|
+
} catch {
|
|
2376
3042
|
}
|
|
3043
|
+
}
|
|
3044
|
+
return localFileUrl;
|
|
3045
|
+
};
|
|
3046
|
+
const renderImageFallback = () => {
|
|
3047
|
+
tile.innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F5BC}\uFE0F</div>`;
|
|
3048
|
+
tile.appendChild(actionsEl);
|
|
3049
|
+
};
|
|
3050
|
+
if (isImage) {
|
|
3051
|
+
const displayUrl = await resolveImageDisplayUrl();
|
|
3052
|
+
if (displayUrl) {
|
|
3053
|
+
const img = document.createElement("img");
|
|
3054
|
+
img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
|
|
3055
|
+
img.alt = actualFileName;
|
|
3056
|
+
img.src = displayUrl;
|
|
3057
|
+
tile.appendChild(img);
|
|
3058
|
+
tile.appendChild(actionsEl);
|
|
3059
|
+
attachZoomHover(tile, displayUrl, actualFileName, actionsEl);
|
|
2377
3060
|
} else {
|
|
2378
|
-
|
|
3061
|
+
renderImageFallback();
|
|
2379
3062
|
}
|
|
2380
3063
|
} else if (isVideo) {
|
|
2381
3064
|
if (state.config.getThumbnail) {
|
|
2382
3065
|
try {
|
|
2383
3066
|
const videoUrl = await state.config.getThumbnail(resourceId);
|
|
2384
3067
|
if (videoUrl) {
|
|
2385
|
-
|
|
2386
|
-
<
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
<div class="absolute inset-0 bg-black bg-opacity-20 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center pointer-events-none">
|
|
2391
|
-
<div class="bg-white bg-opacity-90 rounded-full p-3">
|
|
2392
|
-
<svg class="w-8 h-8 text-gray-800" fill="currentColor" viewBox="0 0 24 24">
|
|
2393
|
-
<path d="M8 5v14l11-7z"/>
|
|
2394
|
-
</svg>
|
|
2395
|
-
</div>
|
|
3068
|
+
tile.innerHTML = `
|
|
3069
|
+
<img style="width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);" alt="${escapeHtml(actualFileName)}" src="${videoUrl}">
|
|
3070
|
+
<div class="fb-video-overlay">
|
|
3071
|
+
<div class="fb-play-btn" style="width:22px;height:22px;">
|
|
3072
|
+
<svg width="10" height="12" viewBox="0 0 10 12" fill="currentColor"><path d="M0 0l10 6-10 6z"/></svg>
|
|
2396
3073
|
</div>
|
|
2397
|
-
</div
|
|
2398
|
-
|
|
3074
|
+
</div>`;
|
|
3075
|
+
tile.appendChild(actionsEl);
|
|
2399
3076
|
} else {
|
|
2400
|
-
|
|
3077
|
+
tile.innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
|
|
3078
|
+
tile.appendChild(actionsEl);
|
|
2401
3079
|
}
|
|
2402
|
-
} catch
|
|
2403
|
-
|
|
2404
|
-
|
|
3080
|
+
} catch {
|
|
3081
|
+
tile.innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
|
|
3082
|
+
tile.appendChild(actionsEl);
|
|
2405
3083
|
}
|
|
2406
3084
|
} else {
|
|
2407
|
-
|
|
3085
|
+
tile.innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
|
|
3086
|
+
tile.appendChild(actionsEl);
|
|
2408
3087
|
}
|
|
2409
3088
|
} else {
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
3089
|
+
if (state.config.getThumbnail) {
|
|
3090
|
+
try {
|
|
3091
|
+
const thumbUrl = await state.config.getThumbnail(resourceId);
|
|
3092
|
+
if (thumbUrl) {
|
|
3093
|
+
const img = document.createElement("img");
|
|
3094
|
+
img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
|
|
3095
|
+
img.alt = actualFileName || resourceId;
|
|
3096
|
+
img.src = thumbUrl;
|
|
3097
|
+
tile.appendChild(img);
|
|
3098
|
+
tile.appendChild(actionsEl);
|
|
3099
|
+
return tile;
|
|
3100
|
+
}
|
|
3101
|
+
} catch {
|
|
3102
|
+
}
|
|
2424
3103
|
}
|
|
3104
|
+
const captionHtml = actualFileName ? `<div class="fb-tile-label">${escapeHtml(actualFileName.length > 10 ? actualFileName.substring(0, 8) + "\u2026" : actualFileName)}</div>
|
|
3105
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M19 9h-4V3H9v6H5l7 7 7-7zm-7 9H5v2h14v-2h-7z"/></svg>` : "";
|
|
3106
|
+
tile.innerHTML = `
|
|
3107
|
+
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:6px;gap:4px;">
|
|
3108
|
+
<div style="font-size:36px;">\u{1F4C1}</div>
|
|
3109
|
+
${captionHtml}
|
|
3110
|
+
</div>`;
|
|
3111
|
+
tile.appendChild(actionsEl);
|
|
2425
3112
|
}
|
|
2426
|
-
|
|
2427
|
-
fileNameElement.className = isPSD ? "hidden" : "text-sm font-medium text-gray-900 text-center";
|
|
2428
|
-
fileNameElement.textContent = actualFileName;
|
|
2429
|
-
const downloadButton = document.createElement("button");
|
|
2430
|
-
downloadButton.className = "w-full px-3 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors";
|
|
2431
|
-
downloadButton.textContent = t("downloadButton", state);
|
|
2432
|
-
downloadButton.onclick = (e) => {
|
|
2433
|
-
e.preventDefault();
|
|
2434
|
-
e.stopPropagation();
|
|
2435
|
-
if (state.config.downloadFile) {
|
|
2436
|
-
state.config.downloadFile(resourceId, actualFileName);
|
|
2437
|
-
} else {
|
|
2438
|
-
forceDownload(resourceId, actualFileName, state);
|
|
2439
|
-
}
|
|
2440
|
-
};
|
|
2441
|
-
fileResult.appendChild(previewContainer);
|
|
2442
|
-
fileResult.appendChild(fileNameElement);
|
|
2443
|
-
fileResult.appendChild(downloadButton);
|
|
2444
|
-
return fileResult;
|
|
3113
|
+
return tile;
|
|
2445
3114
|
}
|
|
2446
|
-
function
|
|
2447
|
-
|
|
2448
|
-
const
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
const slot = document.createElement("div");
|
|
2460
|
-
slot.className = "aspect-square bg-gray-100 border-2 border-dashed border-gray-300 rounded flex items-center justify-center cursor-pointer hover:border-gray-400 transition-colors";
|
|
2461
|
-
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
2462
|
-
svg.setAttribute("class", "w-12 h-12 text-gray-400");
|
|
2463
|
-
svg.setAttribute("fill", "currentColor");
|
|
2464
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
2465
|
-
const path = document.createElementNS(
|
|
2466
|
-
"http://www.w3.org/2000/svg",
|
|
2467
|
-
"path"
|
|
2468
|
-
);
|
|
2469
|
-
path.setAttribute(
|
|
2470
|
-
"d",
|
|
2471
|
-
"M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"
|
|
2472
|
-
);
|
|
2473
|
-
svg.appendChild(path);
|
|
2474
|
-
slot.appendChild(svg);
|
|
2475
|
-
slot.onclick = () => {
|
|
2476
|
-
let filesWrapper = container.parentElement;
|
|
2477
|
-
while (filesWrapper && !filesWrapper.classList.contains("space-y-2")) {
|
|
2478
|
-
filesWrapper = filesWrapper.parentElement;
|
|
2479
|
-
}
|
|
2480
|
-
if (!filesWrapper && container.classList.contains("space-y-2")) {
|
|
2481
|
-
filesWrapper = container;
|
|
2482
|
-
}
|
|
2483
|
-
const fileInput = filesWrapper == null ? void 0 : filesWrapper.querySelector(
|
|
2484
|
-
'input[type="file"]'
|
|
2485
|
-
);
|
|
2486
|
-
if (fileInput) fileInput.click();
|
|
2487
|
-
};
|
|
2488
|
-
gridContainer2.appendChild(slot);
|
|
2489
|
-
}
|
|
2490
|
-
const hintText2 = document.createElement("div");
|
|
2491
|
-
hintText2.className = "text-center text-xs text-gray-500 mt-2";
|
|
2492
|
-
hintText2.textContent = buildHintLine();
|
|
2493
|
-
container.appendChild(gridContainer2);
|
|
2494
|
-
container.appendChild(hintText2);
|
|
2495
|
-
return;
|
|
2496
|
-
}
|
|
2497
|
-
const gridContainer = document.createElement("div");
|
|
2498
|
-
gridContainer.className = "files-list grid grid-cols-4 gap-3";
|
|
2499
|
-
const currentImagesCount = rids ? rids.length : 0;
|
|
2500
|
-
const rowsNeeded = Math.floor(currentImagesCount / 4) + 1;
|
|
2501
|
-
const slotsNeeded = rowsNeeded * 4;
|
|
2502
|
-
for (let i = 0; i < slotsNeeded; i++) {
|
|
2503
|
-
const slot = document.createElement("div");
|
|
2504
|
-
if (rids && i < rids.length) {
|
|
2505
|
-
const rid = rids[i];
|
|
2506
|
-
const meta = state.resourceIndex.get(rid);
|
|
2507
|
-
slot.className = "resource-pill aspect-square bg-gray-100 rounded-lg overflow-hidden relative group border border-gray-300";
|
|
2508
|
-
slot.dataset.resourceId = rid;
|
|
2509
|
-
renderThumbnailForResource(slot, rid, meta, state).catch((err) => {
|
|
2510
|
-
console.error("Failed to render thumbnail:", err);
|
|
2511
|
-
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
2512
|
-
<div class="text-2xl mb-1">\u{1F4C1}</div>
|
|
2513
|
-
<div class="text-xs">${escapeHtml(t("previewError", state))}</div>
|
|
2514
|
-
</div>`;
|
|
2515
|
-
});
|
|
2516
|
-
if (onRemove) {
|
|
2517
|
-
const overlay = document.createElement("div");
|
|
2518
|
-
overlay.className = "absolute inset-0 bg-black bg-opacity-50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center";
|
|
2519
|
-
const removeBtn = document.createElement("button");
|
|
2520
|
-
removeBtn.className = "bg-red-600 text-white px-2 py-1 rounded text-xs";
|
|
2521
|
-
removeBtn.textContent = t("removeElement", state);
|
|
2522
|
-
removeBtn.onclick = (e) => {
|
|
2523
|
-
e.stopPropagation();
|
|
2524
|
-
onRemove(rid);
|
|
2525
|
-
};
|
|
2526
|
-
overlay.appendChild(removeBtn);
|
|
2527
|
-
slot.appendChild(overlay);
|
|
2528
|
-
}
|
|
2529
|
-
} else {
|
|
2530
|
-
slot.className = "aspect-square bg-gray-100 border-2 border-dashed border-gray-300 rounded-lg flex items-center justify-center cursor-pointer hover:border-gray-400 transition-colors";
|
|
2531
|
-
slot.innerHTML = '<svg class="w-12 h-12 text-gray-400" fill="currentColor" viewBox="0 0 24 24"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>';
|
|
2532
|
-
slot.onclick = () => {
|
|
2533
|
-
let filesWrapper = container.parentElement;
|
|
2534
|
-
while (filesWrapper && !filesWrapper.classList.contains("space-y-2")) {
|
|
2535
|
-
filesWrapper = filesWrapper.parentElement;
|
|
2536
|
-
}
|
|
2537
|
-
if (!filesWrapper && container.classList.contains("space-y-2")) {
|
|
2538
|
-
filesWrapper = container;
|
|
2539
|
-
}
|
|
2540
|
-
const fileInput = filesWrapper == null ? void 0 : filesWrapper.querySelector(
|
|
2541
|
-
'input[type="file"]'
|
|
2542
|
-
);
|
|
2543
|
-
if (fileInput) fileInput.click();
|
|
2544
|
-
};
|
|
2545
|
-
}
|
|
2546
|
-
gridContainer.appendChild(slot);
|
|
2547
|
-
}
|
|
2548
|
-
container.appendChild(gridContainer);
|
|
2549
|
-
const hintText = document.createElement("div");
|
|
2550
|
-
hintText.className = "text-center text-xs text-gray-500 mt-2";
|
|
2551
|
-
hintText.textContent = buildHintLine();
|
|
2552
|
-
container.appendChild(hintText);
|
|
2553
|
-
}
|
|
2554
|
-
function renderThumbnailError(slot, state, iconSize = "w-12 h-12") {
|
|
2555
|
-
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
2556
|
-
<svg class="${escapeHtml(iconSize)} text-red-400" fill="currentColor" viewBox="0 0 24 24">
|
|
2557
|
-
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
|
|
2558
|
-
</svg>
|
|
2559
|
-
<div class="text-xs mt-1 text-red-600">${escapeHtml(t("previewError", state))}</div>
|
|
2560
|
-
</div>`;
|
|
3115
|
+
async function renderSingleFileEditTile(fileContainer, resourceId, state, deps) {
|
|
3116
|
+
var _a, _b, _c;
|
|
3117
|
+
const meta = state.resourceIndex.get(resourceId);
|
|
3118
|
+
const fileName = (_b = (_a = meta == null ? void 0 : meta.name) != null ? _a : resourceId.split("/").pop()) != null ? _b : "";
|
|
3119
|
+
const removeHandler = (_c = deps.onRemove) != null ? _c : null;
|
|
3120
|
+
const tile = await renderFilePreviewReadonly(resourceId, state, fileName, {
|
|
3121
|
+
canRemove: true,
|
|
3122
|
+
removeHandler
|
|
3123
|
+
});
|
|
3124
|
+
fileContainer.className = "file-preview-container";
|
|
3125
|
+
fileContainer.removeAttribute("style");
|
|
3126
|
+
clear(fileContainer);
|
|
3127
|
+
fileContainer.appendChild(tile);
|
|
2561
3128
|
}
|
|
2562
|
-
async function
|
|
2563
|
-
var _a, _b;
|
|
2564
|
-
if (
|
|
3129
|
+
async function fillTileContent(tile, rid, meta, state, actionsEl) {
|
|
3130
|
+
var _a, _b, _c;
|
|
3131
|
+
if ((_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("image/")) {
|
|
2565
3132
|
if (meta.file && meta.file instanceof File) {
|
|
2566
3133
|
const img = document.createElement("img");
|
|
2567
|
-
img.
|
|
3134
|
+
img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
|
|
2568
3135
|
img.alt = meta.name;
|
|
2569
3136
|
const reader = new FileReader();
|
|
2570
3137
|
reader.onload = (e) => {
|
|
2571
3138
|
var _a2;
|
|
2572
3139
|
img.src = ((_a2 = e.target) == null ? void 0 : _a2.result) || "";
|
|
3140
|
+
attachZoomHover(tile, img.src, meta.name, actionsEl != null ? actionsEl : null);
|
|
2573
3141
|
};
|
|
2574
3142
|
reader.readAsDataURL(meta.file);
|
|
2575
|
-
|
|
3143
|
+
tile.appendChild(img);
|
|
2576
3144
|
} else if (state.config.getThumbnail) {
|
|
2577
3145
|
try {
|
|
2578
3146
|
const url = await state.config.getThumbnail(rid);
|
|
2579
3147
|
if (url) {
|
|
2580
3148
|
const img = document.createElement("img");
|
|
2581
|
-
img.
|
|
3149
|
+
img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
|
|
2582
3150
|
img.alt = meta.name;
|
|
2583
3151
|
img.src = url;
|
|
2584
|
-
|
|
3152
|
+
tile.appendChild(img);
|
|
3153
|
+
attachZoomHover(tile, url, meta.name, actionsEl != null ? actionsEl : null);
|
|
2585
3154
|
} else {
|
|
2586
|
-
|
|
2587
|
-
<svg class="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
|
|
2588
|
-
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
|
|
2589
|
-
</svg>
|
|
2590
|
-
</div>`;
|
|
3155
|
+
tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F5BC}\uFE0F</div>`;
|
|
2591
3156
|
}
|
|
2592
3157
|
} catch (error) {
|
|
2593
3158
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
2594
|
-
if (state.config.onThumbnailError)
|
|
2595
|
-
|
|
2596
|
-
}
|
|
2597
|
-
renderThumbnailError(slot, state);
|
|
3159
|
+
if (state.config.onThumbnailError) state.config.onThumbnailError(err, rid);
|
|
3160
|
+
tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:16px;color:var(--fb-error-color,#ef4444);">\u2715</div>`;
|
|
2598
3161
|
}
|
|
2599
3162
|
} else {
|
|
2600
|
-
|
|
2601
|
-
<svg class="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
|
|
2602
|
-
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
|
|
2603
|
-
</svg>
|
|
2604
|
-
</div>`;
|
|
3163
|
+
tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F5BC}\uFE0F</div>`;
|
|
2605
3164
|
}
|
|
2606
|
-
|
|
3165
|
+
if (actionsEl) tile.appendChild(actionsEl);
|
|
3166
|
+
} else if ((_b = meta == null ? void 0 : meta.type) == null ? void 0 : _b.startsWith("video/")) {
|
|
2607
3167
|
if (meta.file && meta.file instanceof File) {
|
|
2608
3168
|
const videoUrl = URL.createObjectURL(meta.file);
|
|
2609
|
-
|
|
2610
|
-
<
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
<div class="bg-white bg-opacity-90 rounded-full p-1">
|
|
2615
|
-
<svg class="w-4 h-4 text-gray-800" fill="currentColor" viewBox="0 0 24 24">
|
|
2616
|
-
<path d="M8 5v14l11-7z"/>
|
|
2617
|
-
</svg>
|
|
2618
|
-
</div>
|
|
3169
|
+
tile.innerHTML = `
|
|
3170
|
+
<video style="width:100%;height:100%;" preload="metadata" muted src="${videoUrl}"></video>
|
|
3171
|
+
<div class="fb-video-overlay">
|
|
3172
|
+
<div class="fb-play-btn" style="width:20px;height:20px;">
|
|
3173
|
+
<svg width="8" height="10" viewBox="0 0 8 10" fill="currentColor"><path d="M0 0l8 5-8 5z"/></svg>
|
|
2619
3174
|
</div>
|
|
2620
|
-
</div
|
|
2621
|
-
`;
|
|
3175
|
+
</div>`;
|
|
2622
3176
|
} else if (state.config.getThumbnail) {
|
|
2623
3177
|
try {
|
|
2624
3178
|
const videoUrl = await state.config.getThumbnail(rid);
|
|
2625
3179
|
if (videoUrl) {
|
|
2626
|
-
|
|
2627
|
-
<
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
<div class="bg-white bg-opacity-90 rounded-full p-1">
|
|
2632
|
-
<svg class="w-4 h-4 text-gray-800" fill="currentColor" viewBox="0 0 24 24">
|
|
2633
|
-
<path d="M8 5v14l11-7z"/>
|
|
2634
|
-
</svg>
|
|
2635
|
-
</div>
|
|
3180
|
+
tile.innerHTML = `
|
|
3181
|
+
<img style="width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);" alt="${escapeHtml(meta.name)}" src="${videoUrl}">
|
|
3182
|
+
<div class="fb-video-overlay">
|
|
3183
|
+
<div class="fb-play-btn" style="width:20px;height:20px;">
|
|
3184
|
+
<svg width="8" height="10" viewBox="0 0 8 10" fill="currentColor"><path d="M0 0l8 5-8 5z"/></svg>
|
|
2636
3185
|
</div>
|
|
2637
|
-
</div
|
|
2638
|
-
`;
|
|
3186
|
+
</div>`;
|
|
2639
3187
|
} else {
|
|
2640
|
-
|
|
2641
|
-
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
|
2642
|
-
<path d="M8 5v14l11-7z"/>
|
|
2643
|
-
</svg>
|
|
2644
|
-
<div class="text-xs mt-1">${escapeHtml((meta == null ? void 0 : meta.name) || "Video")}</div>
|
|
2645
|
-
</div>`;
|
|
3188
|
+
tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
|
|
2646
3189
|
}
|
|
2647
3190
|
} catch (error) {
|
|
2648
3191
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
2649
|
-
if (state.config.onThumbnailError)
|
|
2650
|
-
|
|
2651
|
-
}
|
|
2652
|
-
renderThumbnailError(slot, state, "w-8 h-8");
|
|
3192
|
+
if (state.config.onThumbnailError) state.config.onThumbnailError(err, rid);
|
|
3193
|
+
tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:16px;color:var(--fb-error-color,#ef4444);">\u2715</div>`;
|
|
2653
3194
|
}
|
|
2654
3195
|
} else {
|
|
2655
|
-
|
|
2656
|
-
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
|
2657
|
-
<path d="M8 5v14l11-7z"/>
|
|
2658
|
-
</svg>
|
|
2659
|
-
<div class="text-xs mt-1">${escapeHtml((meta == null ? void 0 : meta.name) || "Video")}</div>
|
|
2660
|
-
</div>`;
|
|
3196
|
+
tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
|
|
2661
3197
|
}
|
|
3198
|
+
if (actionsEl) tile.appendChild(actionsEl);
|
|
2662
3199
|
} else {
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
3200
|
+
const name = (_c = meta == null ? void 0 : meta.name) != null ? _c : "";
|
|
3201
|
+
const hasExtension = name.includes(".");
|
|
3202
|
+
const captionHtml = hasExtension ? `<div class="fb-tile-label">${escapeHtml(name.length > 10 ? name.substring(0, 8) + "\u2026" : name)}</div>` : "";
|
|
3203
|
+
tile.innerHTML = `
|
|
3204
|
+
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:6px;gap:4px;">
|
|
3205
|
+
<div style="font-size:36px;">\u{1F4C1}</div>
|
|
3206
|
+
${captionHtml}
|
|
3207
|
+
</div>`;
|
|
3208
|
+
if (actionsEl) tile.appendChild(actionsEl);
|
|
2667
3209
|
}
|
|
2668
3210
|
}
|
|
2669
|
-
function
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
3211
|
+
async function forceDownload(resourceId, fileName, state) {
|
|
3212
|
+
try {
|
|
3213
|
+
let fileUrl = null;
|
|
3214
|
+
if (state.config.getDownloadUrl) {
|
|
3215
|
+
fileUrl = state.config.getDownloadUrl(resourceId);
|
|
3216
|
+
} else if (state.config.getThumbnail) {
|
|
3217
|
+
fileUrl = await state.config.getThumbnail(resourceId);
|
|
3218
|
+
}
|
|
3219
|
+
if (fileUrl) {
|
|
3220
|
+
const finalUrl = fileUrl.startsWith("http") ? fileUrl : new URL(fileUrl, window.location.href).href;
|
|
3221
|
+
const response = await fetch(finalUrl);
|
|
3222
|
+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
|
3223
|
+
const blob = await response.blob();
|
|
3224
|
+
downloadBlob(blob, fileName);
|
|
3225
|
+
} else {
|
|
3226
|
+
throw new Error("No download URL available for resource");
|
|
3227
|
+
}
|
|
3228
|
+
} catch (error) {
|
|
3229
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
3230
|
+
if (state.config.onDownloadError) {
|
|
3231
|
+
state.config.onDownloadError(err, resourceId, fileName);
|
|
3232
|
+
}
|
|
3233
|
+
console.error(`File download failed for ${fileName}:`, err);
|
|
3234
|
+
throw err;
|
|
3235
|
+
}
|
|
2680
3236
|
}
|
|
2681
|
-
function
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
3237
|
+
function downloadBlob(blob, fileName) {
|
|
3238
|
+
try {
|
|
3239
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
3240
|
+
const link = document.createElement("a");
|
|
3241
|
+
link.href = blobUrl;
|
|
3242
|
+
link.download = fileName;
|
|
3243
|
+
link.style.display = "none";
|
|
3244
|
+
document.body.appendChild(link);
|
|
3245
|
+
link.click();
|
|
3246
|
+
document.body.removeChild(link);
|
|
3247
|
+
setTimeout(() => {
|
|
3248
|
+
URL.revokeObjectURL(blobUrl);
|
|
3249
|
+
}, 100);
|
|
3250
|
+
} catch (error) {
|
|
3251
|
+
throw new Error(`Blob download failed: ${error.message}`);
|
|
3252
|
+
}
|
|
2694
3253
|
}
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
if (
|
|
3254
|
+
|
|
3255
|
+
// src/components/file/upload.ts
|
|
3256
|
+
async function uploadSingleFile(file, state) {
|
|
3257
|
+
if (!state.config.uploadFile) {
|
|
3258
|
+
throw new Error(
|
|
3259
|
+
"No upload handler configured. Set uploadHandler via FormBuilder.setUploadHandler()"
|
|
3260
|
+
);
|
|
3261
|
+
}
|
|
3262
|
+
try {
|
|
3263
|
+
const rid = await state.config.uploadFile(file);
|
|
3264
|
+
if (typeof rid !== "string") {
|
|
3265
|
+
throw new Error("Upload handler must return a string resource ID");
|
|
3266
|
+
}
|
|
3267
|
+
return rid;
|
|
3268
|
+
} catch (error) {
|
|
3269
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
3270
|
+
if (state.config.onUploadError) state.config.onUploadError(err, file);
|
|
3271
|
+
throw new Error(`File upload failed: ${err.message}`);
|
|
3272
|
+
}
|
|
2699
3273
|
}
|
|
2700
3274
|
async function handleFileSelect(file, container, fieldName, state, deps = null, instance, allowedExtensions = [], maxSizeMB = Infinity) {
|
|
2701
3275
|
var _a, _b;
|
|
@@ -2715,24 +3289,18 @@ async function handleFileSelect(file, container, fieldName, state, deps = null,
|
|
|
2715
3289
|
return;
|
|
2716
3290
|
}
|
|
2717
3291
|
clearFileError(container);
|
|
3292
|
+
ensureFileStyles();
|
|
3293
|
+
container.innerHTML = `
|
|
3294
|
+
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:6px;padding:6px;">
|
|
3295
|
+
<div class="fb-spinner"></div>
|
|
3296
|
+
<div style="font-size:11px;color:var(--fb-text-secondary-color,#6b7280);text-align:center;">${escapeHtml(t("uploadingFile", state))}</div>
|
|
3297
|
+
</div>`;
|
|
2718
3298
|
let rid;
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
}
|
|
2725
|
-
} catch (error) {
|
|
2726
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
2727
|
-
if (state.config.onUploadError) {
|
|
2728
|
-
state.config.onUploadError(err, file);
|
|
2729
|
-
}
|
|
2730
|
-
throw new Error(`File upload failed: ${err.message}`);
|
|
2731
|
-
}
|
|
2732
|
-
} else {
|
|
2733
|
-
throw new Error(
|
|
2734
|
-
"No upload handler configured. Set uploadHandler via FormBuilder.setUploadHandler()"
|
|
2735
|
-
);
|
|
3299
|
+
try {
|
|
3300
|
+
rid = await uploadSingleFile(file, state);
|
|
3301
|
+
} catch (error) {
|
|
3302
|
+
setEmptyFileContainer(container, state);
|
|
3303
|
+
throw error;
|
|
2736
3304
|
}
|
|
2737
3305
|
state.resourceIndex.set(rid, {
|
|
2738
3306
|
name: file.name,
|
|
@@ -2740,7 +3308,6 @@ async function handleFileSelect(file, container, fieldName, state, deps = null,
|
|
|
2740
3308
|
size: file.size,
|
|
2741
3309
|
uploadedAt: /* @__PURE__ */ new Date(),
|
|
2742
3310
|
file
|
|
2743
|
-
// Store the file object for local preview
|
|
2744
3311
|
});
|
|
2745
3312
|
let hiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
|
|
2746
3313
|
'input[type="hidden"]'
|
|
@@ -2752,693 +3319,712 @@ async function handleFileSelect(file, container, fieldName, state, deps = null,
|
|
|
2752
3319
|
(_b = container.parentElement) == null ? void 0 : _b.appendChild(hiddenInput);
|
|
2753
3320
|
}
|
|
2754
3321
|
hiddenInput.value = rid;
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
3322
|
+
const isVideo = file.type.startsWith("video/");
|
|
3323
|
+
if (!isVideo && deps) {
|
|
3324
|
+
renderSingleFileEditTile(container, rid, state, deps).catch(console.error);
|
|
3325
|
+
} else {
|
|
3326
|
+
renderFilePreview(container, rid, state, {
|
|
3327
|
+
fileName: file.name,
|
|
3328
|
+
isReadonly: false,
|
|
3329
|
+
deps
|
|
3330
|
+
}).catch(console.error);
|
|
3331
|
+
}
|
|
2760
3332
|
if (instance && !state.config.readonly) {
|
|
2761
3333
|
instance.triggerOnChange(fieldName, rid);
|
|
2762
3334
|
}
|
|
2763
3335
|
}
|
|
2764
|
-
function
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
3336
|
+
function filterAndSlice(allFiles, currentCount, constraints, state) {
|
|
3337
|
+
const rejectedByExt = allFiles.filter(
|
|
3338
|
+
(f) => !isFileExtensionAllowed(f.name, constraints.allowedExtensions)
|
|
3339
|
+
);
|
|
3340
|
+
const afterExt = allFiles.filter(
|
|
3341
|
+
(f) => isFileExtensionAllowed(f.name, constraints.allowedExtensions)
|
|
3342
|
+
);
|
|
3343
|
+
const rejectedBySize = afterExt.filter(
|
|
3344
|
+
(f) => !isFileSizeAllowed(f, constraints.maxSize)
|
|
3345
|
+
);
|
|
3346
|
+
const valid = afterExt.filter((f) => isFileSizeAllowed(f, constraints.maxSize));
|
|
3347
|
+
const remaining = constraints.maxCount === Infinity ? valid.length : Math.max(0, constraints.maxCount - currentCount);
|
|
3348
|
+
const accepted = valid.slice(0, remaining);
|
|
3349
|
+
const skippedByCount = valid.length - accepted.length;
|
|
3350
|
+
const errorParts = [];
|
|
3351
|
+
if (rejectedByExt.length > 0) {
|
|
3352
|
+
const formats = constraints.allowedExtensions.join(", ");
|
|
3353
|
+
const names = rejectedByExt.map((f) => f.name).join(", ");
|
|
3354
|
+
errorParts.push(t("invalidFileExtension", state, { name: names, formats }));
|
|
3355
|
+
}
|
|
3356
|
+
if (rejectedBySize.length > 0) {
|
|
3357
|
+
const names = rejectedBySize.map((f) => f.name).join(", ");
|
|
3358
|
+
errorParts.push(
|
|
3359
|
+
t("fileTooLarge", state, { name: names, maxSize: constraints.maxSize })
|
|
3360
|
+
);
|
|
3361
|
+
}
|
|
3362
|
+
if (skippedByCount > 0) {
|
|
3363
|
+
errorParts.push(
|
|
3364
|
+
t("filesLimitExceeded", state, {
|
|
3365
|
+
skipped: skippedByCount,
|
|
3366
|
+
max: constraints.maxCount
|
|
3367
|
+
})
|
|
3368
|
+
);
|
|
3369
|
+
}
|
|
3370
|
+
return { accepted, errorMessage: errorParts.join(" \u2022 ") };
|
|
3371
|
+
}
|
|
3372
|
+
async function uploadBatch(accepted, resourceIds, listEl, state) {
|
|
3373
|
+
await Promise.all(
|
|
3374
|
+
accepted.map(async (file) => {
|
|
3375
|
+
const placeholder = createUploadingTile(file.name, state);
|
|
3376
|
+
if (listEl) {
|
|
3377
|
+
const tilesWrap = ensureTilesWrap(listEl);
|
|
3378
|
+
const addTile = tilesWrap.querySelector(".fb-tile-add");
|
|
3379
|
+
if (addTile) {
|
|
3380
|
+
tilesWrap.insertBefore(placeholder, addTile);
|
|
3381
|
+
} else {
|
|
3382
|
+
tilesWrap.appendChild(placeholder);
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
try {
|
|
3386
|
+
const rid = await uploadSingleFile(file, state);
|
|
3387
|
+
state.resourceIndex.set(rid, {
|
|
3388
|
+
name: file.name,
|
|
3389
|
+
type: file.type,
|
|
3390
|
+
size: file.size,
|
|
3391
|
+
uploadedAt: /* @__PURE__ */ new Date(),
|
|
3392
|
+
file: void 0
|
|
3393
|
+
});
|
|
3394
|
+
resourceIds.push(rid);
|
|
3395
|
+
} finally {
|
|
3396
|
+
placeholder.remove();
|
|
3397
|
+
}
|
|
3398
|
+
})
|
|
3399
|
+
);
|
|
3400
|
+
}
|
|
3401
|
+
function setupFilesDropHandler(filesContainer, resourceIds, state, updateCallback, constraints, pathKey, instance) {
|
|
3402
|
+
setupDragAndDrop(filesContainer, async (files) => {
|
|
2774
3403
|
var _a;
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
3404
|
+
const { accepted, errorMessage } = filterAndSlice(
|
|
3405
|
+
Array.from(files),
|
|
3406
|
+
resourceIds.length,
|
|
3407
|
+
constraints,
|
|
3408
|
+
state
|
|
3409
|
+
);
|
|
3410
|
+
if (errorMessage) {
|
|
3411
|
+
showFileError(filesContainer, errorMessage);
|
|
3412
|
+
} else {
|
|
3413
|
+
clearFileError(filesContainer);
|
|
3414
|
+
}
|
|
3415
|
+
const list = (_a = filesContainer.querySelector(".files-list")) != null ? _a : filesContainer;
|
|
3416
|
+
await uploadBatch(accepted, resourceIds, list, state);
|
|
3417
|
+
updateCallback();
|
|
3418
|
+
if (instance && pathKey && !state.config.readonly) {
|
|
3419
|
+
instance.triggerOnChange(pathKey, resourceIds);
|
|
2779
3420
|
}
|
|
2780
3421
|
});
|
|
2781
3422
|
}
|
|
2782
|
-
function
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
3423
|
+
function setupFilesPickerHandler(filesPicker, resourceIds, state, updateCallback, constraints, pathKey, instance) {
|
|
3424
|
+
filesPicker.onchange = async () => {
|
|
3425
|
+
if (!filesPicker.files) return;
|
|
3426
|
+
const wrapperEl = filesPicker.closest(".space-y-2") || filesPicker.parentElement;
|
|
3427
|
+
const { accepted, errorMessage } = filterAndSlice(
|
|
3428
|
+
Array.from(filesPicker.files),
|
|
3429
|
+
resourceIds.length,
|
|
3430
|
+
constraints,
|
|
3431
|
+
state
|
|
3432
|
+
);
|
|
3433
|
+
if (errorMessage && wrapperEl) {
|
|
3434
|
+
showFileError(wrapperEl, errorMessage);
|
|
3435
|
+
} else if (wrapperEl) {
|
|
3436
|
+
clearFileError(wrapperEl);
|
|
3437
|
+
}
|
|
3438
|
+
const listEl = wrapperEl == null ? void 0 : wrapperEl.querySelector(".files-list");
|
|
3439
|
+
await uploadBatch(accepted, resourceIds, listEl != null ? listEl : null, state);
|
|
3440
|
+
updateCallback();
|
|
3441
|
+
filesPicker.value = "";
|
|
3442
|
+
if (instance && pathKey && !state.config.readonly) {
|
|
3443
|
+
instance.triggerOnChange(pathKey, resourceIds);
|
|
3444
|
+
}
|
|
2795
3445
|
};
|
|
2796
|
-
overlay.appendChild(deleteBtn);
|
|
2797
|
-
container.appendChild(overlay);
|
|
2798
3446
|
}
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
3447
|
+
|
|
3448
|
+
// src/components/file/render-edit.ts
|
|
3449
|
+
function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, state, deps) {
|
|
3450
|
+
var _a, _b;
|
|
3451
|
+
if (!state.resourceIndex.has(initial)) {
|
|
3452
|
+
const filename = initial.split("/").pop() || "file";
|
|
3453
|
+
const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
|
|
3454
|
+
let fileType = "application/octet-stream";
|
|
3455
|
+
if (extension) {
|
|
3456
|
+
if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
|
|
3457
|
+
fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
|
|
3458
|
+
} else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
|
|
3459
|
+
fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
|
|
2811
3460
|
}
|
|
2812
|
-
throw new Error(`File upload failed: ${err.message}`);
|
|
2813
3461
|
}
|
|
3462
|
+
state.resourceIndex.set(initial, {
|
|
3463
|
+
name: filename,
|
|
3464
|
+
type: fileType,
|
|
3465
|
+
size: 0,
|
|
3466
|
+
uploadedAt: /* @__PURE__ */ new Date(),
|
|
3467
|
+
file: void 0
|
|
3468
|
+
});
|
|
3469
|
+
}
|
|
3470
|
+
const meta = state.resourceIndex.get(initial);
|
|
3471
|
+
const isVideo = (_b = meta == null ? void 0 : meta.type) == null ? void 0 : _b.startsWith("video/");
|
|
3472
|
+
if (isVideo) {
|
|
3473
|
+
renderFilePreview(fileContainer, initial, state, {
|
|
3474
|
+
fileName: initial,
|
|
3475
|
+
isReadonly: false,
|
|
3476
|
+
deps
|
|
3477
|
+
}).catch(console.error);
|
|
2814
3478
|
} else {
|
|
2815
|
-
|
|
2816
|
-
"No upload handler configured. Set uploadHandler via FormBuilder.setUploadHandler()"
|
|
2817
|
-
);
|
|
3479
|
+
renderSingleFileEditTile(fileContainer, initial, state, deps).catch(console.error);
|
|
2818
3480
|
}
|
|
3481
|
+
const hiddenInput = document.createElement("input");
|
|
3482
|
+
hiddenInput.type = "hidden";
|
|
3483
|
+
hiddenInput.name = pathKey;
|
|
3484
|
+
hiddenInput.value = initial;
|
|
3485
|
+
fileWrapper.appendChild(hiddenInput);
|
|
2819
3486
|
}
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
3487
|
+
function renderResourcePills(container, rids, state, onRemove, hint, countInfo, maxCount, isReadonly = false) {
|
|
3488
|
+
var _a;
|
|
3489
|
+
ensureFileStyles();
|
|
3490
|
+
const wrapper = container.closest("[data-files-wrapper]");
|
|
3491
|
+
if (wrapper) {
|
|
3492
|
+
wrapper.dataset.resourceIds = JSON.stringify(rids != null ? rids : []);
|
|
3493
|
+
}
|
|
3494
|
+
while (container.firstChild) container.removeChild(container.firstChild);
|
|
3495
|
+
const ridList = rids != null ? rids : [];
|
|
3496
|
+
const atMax = maxCount !== void 0 && ridList.length >= maxCount;
|
|
3497
|
+
const buildSubHint = () => {
|
|
3498
|
+
const parts = [];
|
|
3499
|
+
if (hint) parts.push(hint);
|
|
3500
|
+
if (countInfo) parts.push(countInfo);
|
|
3501
|
+
return parts.join(" \u2022 ");
|
|
3502
|
+
};
|
|
3503
|
+
const openPicker = () => {
|
|
3504
|
+
const picker = findFilePicker(container);
|
|
3505
|
+
if (picker) picker.click();
|
|
3506
|
+
};
|
|
3507
|
+
if (ridList.length === 0) {
|
|
3508
|
+
if (isReadonly) {
|
|
3509
|
+
const emptyEl = document.createElement("div");
|
|
3510
|
+
emptyEl.className = "fb-tile-empty-text";
|
|
3511
|
+
emptyEl.textContent = t("noFilesSelected", state);
|
|
3512
|
+
container.appendChild(emptyEl);
|
|
2836
3513
|
} else {
|
|
2837
|
-
|
|
3514
|
+
const dropzone = document.createElement("div");
|
|
3515
|
+
dropzone.className = "fb-file-dropzone";
|
|
3516
|
+
const subHint2 = buildSubHint();
|
|
3517
|
+
dropzone.innerHTML = `
|
|
3518
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" style="flex-shrink:0;color:var(--fb-file-upload-text-color,#9ca3af);">
|
|
3519
|
+
<path d="M19.35 10.04A7.49 7.49 0 0012 4C9.11 4 6.6 5.64 5.35 8.04A5.994 5.994 0 000 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"/>
|
|
3520
|
+
</svg>
|
|
3521
|
+
<div class="fb-dropzone-primary-text">${escapeHtml(t("clickDragTextMultiple", state))}</div>
|
|
3522
|
+
${subHint2 ? `<div class="fb-dropzone-hint-text">${escapeHtml(subHint2)}</div>` : ""}
|
|
3523
|
+
`;
|
|
3524
|
+
dropzone.onclick = openPicker;
|
|
3525
|
+
container.appendChild(dropzone);
|
|
2838
3526
|
}
|
|
2839
|
-
|
|
2840
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
2841
|
-
if (state.config.onDownloadError) {
|
|
2842
|
-
state.config.onDownloadError(err, resourceId, fileName);
|
|
2843
|
-
}
|
|
2844
|
-
console.error(`File download failed for ${fileName}:`, err);
|
|
2845
|
-
throw err;
|
|
2846
|
-
}
|
|
2847
|
-
}
|
|
2848
|
-
function downloadBlob(blob, fileName) {
|
|
2849
|
-
try {
|
|
2850
|
-
const blobUrl = URL.createObjectURL(blob);
|
|
2851
|
-
const link = document.createElement("a");
|
|
2852
|
-
link.href = blobUrl;
|
|
2853
|
-
link.download = fileName;
|
|
2854
|
-
link.style.display = "none";
|
|
2855
|
-
document.body.appendChild(link);
|
|
2856
|
-
link.click();
|
|
2857
|
-
document.body.removeChild(link);
|
|
2858
|
-
setTimeout(() => {
|
|
2859
|
-
URL.revokeObjectURL(blobUrl);
|
|
2860
|
-
}, 100);
|
|
2861
|
-
} catch (error) {
|
|
2862
|
-
throw new Error(`Blob download failed: ${error.message}`);
|
|
3527
|
+
return;
|
|
2863
3528
|
}
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
state.resourceIndex.set(resourceId, {
|
|
2881
|
-
name: filename,
|
|
2882
|
-
type: fileType,
|
|
2883
|
-
size: 0,
|
|
2884
|
-
uploadedAt: /* @__PURE__ */ new Date(),
|
|
2885
|
-
file: void 0
|
|
2886
|
-
});
|
|
2887
|
-
}
|
|
3529
|
+
const tilesWrap = document.createElement("div");
|
|
3530
|
+
tilesWrap.className = "fb-tiles-wrap";
|
|
3531
|
+
tilesWrap.style.cssText = "display:flex;flex-wrap:wrap;gap:6px;align-items:flex-start;";
|
|
3532
|
+
for (const rid of ridList) {
|
|
3533
|
+
const meta = state.resourceIndex.get(rid);
|
|
3534
|
+
const tile = createFileTile();
|
|
3535
|
+
tile.classList.add("fb-tile-resource", "resource-pill");
|
|
3536
|
+
tile.dataset.resourceId = rid;
|
|
3537
|
+
const actionsEl = createTileActions({
|
|
3538
|
+
canRemove: !isReadonly && onRemove !== null,
|
|
3539
|
+
removeHandler: onRemove ? () => onRemove(rid) : null,
|
|
3540
|
+
state,
|
|
3541
|
+
resourceId: rid,
|
|
3542
|
+
fileName: (_a = meta == null ? void 0 : meta.name) != null ? _a : ""
|
|
2888
3543
|
});
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, state, deps) {
|
|
2892
|
-
var _a;
|
|
2893
|
-
if (!state.resourceIndex.has(initial)) {
|
|
2894
|
-
const filename = initial.split("/").pop() || "file";
|
|
2895
|
-
const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
|
|
2896
|
-
let fileType = "application/octet-stream";
|
|
2897
|
-
if (extension) {
|
|
2898
|
-
if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
|
|
2899
|
-
fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
|
|
2900
|
-
} else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
|
|
2901
|
-
fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
|
|
2902
|
-
}
|
|
2903
|
-
}
|
|
2904
|
-
state.resourceIndex.set(initial, {
|
|
2905
|
-
name: filename,
|
|
2906
|
-
type: fileType,
|
|
2907
|
-
size: 0,
|
|
2908
|
-
uploadedAt: /* @__PURE__ */ new Date(),
|
|
2909
|
-
file: void 0
|
|
3544
|
+
fillTileContent(tile, rid, meta, state, actionsEl).catch((err) => {
|
|
3545
|
+
console.error("Failed to render tile:", err);
|
|
2910
3546
|
});
|
|
3547
|
+
tilesWrap.appendChild(tile);
|
|
3548
|
+
}
|
|
3549
|
+
if (!isReadonly && !atMax) {
|
|
3550
|
+
const addTile = document.createElement("div");
|
|
3551
|
+
addTile.className = "fb-tile fb-tile-add";
|
|
3552
|
+
addTile.innerHTML = "+";
|
|
3553
|
+
addTile.onclick = openPicker;
|
|
3554
|
+
tilesWrap.appendChild(addTile);
|
|
3555
|
+
} else if (!isReadonly && atMax) {
|
|
3556
|
+
const chip = document.createElement("div");
|
|
3557
|
+
chip.className = "fb-tile-counter";
|
|
3558
|
+
chip.textContent = t("filesCounter", state, {
|
|
3559
|
+
count: ridList.length,
|
|
3560
|
+
max: maxCount
|
|
3561
|
+
});
|
|
3562
|
+
tilesWrap.appendChild(chip);
|
|
3563
|
+
}
|
|
3564
|
+
container.appendChild(tilesWrap);
|
|
3565
|
+
const subHint = buildSubHint();
|
|
3566
|
+
if (subHint) {
|
|
3567
|
+
const hintEl = document.createElement("div");
|
|
3568
|
+
hintEl.className = "fb-tile-hint";
|
|
3569
|
+
hintEl.textContent = subHint;
|
|
3570
|
+
container.appendChild(hintEl);
|
|
2911
3571
|
}
|
|
2912
|
-
renderFilePreview(fileContainer, initial, state, {
|
|
2913
|
-
fileName: initial,
|
|
2914
|
-
isReadonly: false,
|
|
2915
|
-
deps
|
|
2916
|
-
}).catch(console.error);
|
|
2917
|
-
const hiddenInput = document.createElement("input");
|
|
2918
|
-
hiddenInput.type = "hidden";
|
|
2919
|
-
hiddenInput.name = pathKey;
|
|
2920
|
-
hiddenInput.value = initial;
|
|
2921
|
-
fileWrapper.appendChild(hiddenInput);
|
|
2922
|
-
}
|
|
2923
|
-
function setupFilesDropHandler(filesContainer, initialFiles, state, updateCallback, constraints, pathKey, instance) {
|
|
2924
|
-
setupDragAndDrop(filesContainer, async (files) => {
|
|
2925
|
-
const allFiles = Array.from(files);
|
|
2926
|
-
const rejectedByExtension = allFiles.filter(
|
|
2927
|
-
(f) => !isFileExtensionAllowed(f.name, constraints.allowedExtensions)
|
|
2928
|
-
);
|
|
2929
|
-
const afterExtension = allFiles.filter(
|
|
2930
|
-
(f) => isFileExtensionAllowed(f.name, constraints.allowedExtensions)
|
|
2931
|
-
);
|
|
2932
|
-
const rejectedBySize = afterExtension.filter(
|
|
2933
|
-
(f) => !isFileSizeAllowed(f, constraints.maxSize)
|
|
2934
|
-
);
|
|
2935
|
-
const validFiles = afterExtension.filter(
|
|
2936
|
-
(f) => isFileSizeAllowed(f, constraints.maxSize)
|
|
2937
|
-
);
|
|
2938
|
-
const remaining = constraints.maxCount === Infinity ? validFiles.length : Math.max(0, constraints.maxCount - initialFiles.length);
|
|
2939
|
-
const arr = validFiles.slice(0, remaining);
|
|
2940
|
-
const skippedByCount = validFiles.length - arr.length;
|
|
2941
|
-
const errorParts = [];
|
|
2942
|
-
if (rejectedByExtension.length > 0) {
|
|
2943
|
-
const formats = constraints.allowedExtensions.join(", ");
|
|
2944
|
-
const names = rejectedByExtension.map((f) => f.name).join(", ");
|
|
2945
|
-
errorParts.push(
|
|
2946
|
-
t("invalidFileExtension", state, { name: names, formats })
|
|
2947
|
-
);
|
|
2948
|
-
}
|
|
2949
|
-
if (rejectedBySize.length > 0) {
|
|
2950
|
-
const names = rejectedBySize.map((f) => f.name).join(", ");
|
|
2951
|
-
errorParts.push(
|
|
2952
|
-
t("fileTooLarge", state, { name: names, maxSize: constraints.maxSize })
|
|
2953
|
-
);
|
|
2954
|
-
}
|
|
2955
|
-
if (skippedByCount > 0) {
|
|
2956
|
-
errorParts.push(
|
|
2957
|
-
t("filesLimitExceeded", state, {
|
|
2958
|
-
skipped: skippedByCount,
|
|
2959
|
-
max: constraints.maxCount
|
|
2960
|
-
})
|
|
2961
|
-
);
|
|
2962
|
-
}
|
|
2963
|
-
if (errorParts.length > 0) {
|
|
2964
|
-
showFileError(filesContainer, errorParts.join(" \u2022 "));
|
|
2965
|
-
} else {
|
|
2966
|
-
clearFileError(filesContainer);
|
|
2967
|
-
}
|
|
2968
|
-
for (const file of arr) {
|
|
2969
|
-
const rid = await uploadSingleFile(file, state);
|
|
2970
|
-
state.resourceIndex.set(rid, {
|
|
2971
|
-
name: file.name,
|
|
2972
|
-
type: file.type,
|
|
2973
|
-
size: file.size,
|
|
2974
|
-
uploadedAt: /* @__PURE__ */ new Date(),
|
|
2975
|
-
file: void 0
|
|
2976
|
-
});
|
|
2977
|
-
initialFiles.push(rid);
|
|
2978
|
-
}
|
|
2979
|
-
updateCallback();
|
|
2980
|
-
if (instance && pathKey && !state.config.readonly) {
|
|
2981
|
-
instance.triggerOnChange(pathKey, initialFiles);
|
|
2982
|
-
}
|
|
2983
|
-
});
|
|
2984
|
-
}
|
|
2985
|
-
function setupFilesPickerHandler(filesPicker, initialFiles, state, updateCallback, constraints, pathKey, instance) {
|
|
2986
|
-
filesPicker.onchange = async () => {
|
|
2987
|
-
if (filesPicker.files) {
|
|
2988
|
-
const allFiles = Array.from(filesPicker.files);
|
|
2989
|
-
const rejectedByExtension = allFiles.filter(
|
|
2990
|
-
(f) => !isFileExtensionAllowed(f.name, constraints.allowedExtensions)
|
|
2991
|
-
);
|
|
2992
|
-
const afterExtension = allFiles.filter(
|
|
2993
|
-
(f) => isFileExtensionAllowed(f.name, constraints.allowedExtensions)
|
|
2994
|
-
);
|
|
2995
|
-
const rejectedBySize = afterExtension.filter(
|
|
2996
|
-
(f) => !isFileSizeAllowed(f, constraints.maxSize)
|
|
2997
|
-
);
|
|
2998
|
-
const validFiles = afterExtension.filter(
|
|
2999
|
-
(f) => isFileSizeAllowed(f, constraints.maxSize)
|
|
3000
|
-
);
|
|
3001
|
-
const remaining = constraints.maxCount === Infinity ? validFiles.length : Math.max(0, constraints.maxCount - initialFiles.length);
|
|
3002
|
-
const arr = validFiles.slice(0, remaining);
|
|
3003
|
-
const skippedByCount = validFiles.length - arr.length;
|
|
3004
|
-
const errorParts = [];
|
|
3005
|
-
if (rejectedByExtension.length > 0) {
|
|
3006
|
-
const formats = constraints.allowedExtensions.join(", ");
|
|
3007
|
-
const names = rejectedByExtension.map((f) => f.name).join(", ");
|
|
3008
|
-
errorParts.push(
|
|
3009
|
-
t("invalidFileExtension", state, { name: names, formats })
|
|
3010
|
-
);
|
|
3011
|
-
}
|
|
3012
|
-
if (rejectedBySize.length > 0) {
|
|
3013
|
-
const names = rejectedBySize.map((f) => f.name).join(", ");
|
|
3014
|
-
errorParts.push(
|
|
3015
|
-
t("fileTooLarge", state, {
|
|
3016
|
-
name: names,
|
|
3017
|
-
maxSize: constraints.maxSize
|
|
3018
|
-
})
|
|
3019
|
-
);
|
|
3020
|
-
}
|
|
3021
|
-
if (skippedByCount > 0) {
|
|
3022
|
-
errorParts.push(
|
|
3023
|
-
t("filesLimitExceeded", state, {
|
|
3024
|
-
skipped: skippedByCount,
|
|
3025
|
-
max: constraints.maxCount
|
|
3026
|
-
})
|
|
3027
|
-
);
|
|
3028
|
-
}
|
|
3029
|
-
const wrapper = filesPicker.closest(".space-y-2") || filesPicker.parentElement;
|
|
3030
|
-
if (errorParts.length > 0 && wrapper) {
|
|
3031
|
-
showFileError(wrapper, errorParts.join(" \u2022 "));
|
|
3032
|
-
} else if (wrapper) {
|
|
3033
|
-
clearFileError(wrapper);
|
|
3034
|
-
}
|
|
3035
|
-
for (const file of arr) {
|
|
3036
|
-
const rid = await uploadSingleFile(file, state);
|
|
3037
|
-
state.resourceIndex.set(rid, {
|
|
3038
|
-
name: file.name,
|
|
3039
|
-
type: file.type,
|
|
3040
|
-
size: file.size,
|
|
3041
|
-
uploadedAt: /* @__PURE__ */ new Date(),
|
|
3042
|
-
file: void 0
|
|
3043
|
-
});
|
|
3044
|
-
initialFiles.push(rid);
|
|
3045
|
-
}
|
|
3046
|
-
}
|
|
3047
|
-
updateCallback();
|
|
3048
|
-
filesPicker.value = "";
|
|
3049
|
-
if (instance && pathKey && !state.config.readonly) {
|
|
3050
|
-
instance.triggerOnChange(pathKey, initialFiles);
|
|
3051
|
-
}
|
|
3052
|
-
};
|
|
3053
3572
|
}
|
|
3054
|
-
function
|
|
3055
|
-
var _a, _b;
|
|
3573
|
+
function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
3574
|
+
var _a, _b, _c;
|
|
3056
3575
|
const state = ctx.state;
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
const emptyState = document.createElement("div");
|
|
3077
|
-
emptyState.className = "aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500";
|
|
3078
|
-
emptyState.innerHTML = `<div class="text-center">${escapeHtml(t("noFileSelected", state))}</div>`;
|
|
3079
|
-
wrapper.appendChild(emptyState);
|
|
3080
|
-
}
|
|
3081
|
-
} else {
|
|
3082
|
-
const fileWrapper = document.createElement("div");
|
|
3083
|
-
fileWrapper.className = "space-y-2";
|
|
3084
|
-
const picker = document.createElement("input");
|
|
3085
|
-
picker.type = "file";
|
|
3086
|
-
picker.name = pathKey;
|
|
3087
|
-
picker.style.display = "none";
|
|
3088
|
-
if (element.accept) {
|
|
3089
|
-
picker.accept = typeof element.accept === "string" ? element.accept : ((_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`).join(",")) || "";
|
|
3090
|
-
}
|
|
3091
|
-
const fileContainer = document.createElement("div");
|
|
3092
|
-
fileContainer.className = "file-preview-container w-full aspect-square max-w-xs bg-gray-100 rounded-lg overflow-hidden relative group cursor-pointer";
|
|
3093
|
-
const initial = ctx.prefill[element.key];
|
|
3094
|
-
const allowedExts = getAllowedExtensions(element.accept);
|
|
3095
|
-
const maxSizeMB = (_b = element.maxSize) != null ? _b : Infinity;
|
|
3096
|
-
const fileUploadHandler = () => picker.click();
|
|
3097
|
-
const dragHandler = (files) => {
|
|
3576
|
+
const fileWrapper = document.createElement("div");
|
|
3577
|
+
fileWrapper.className = "space-y-2";
|
|
3578
|
+
const picker = document.createElement("input");
|
|
3579
|
+
picker.type = "file";
|
|
3580
|
+
picker.name = pathKey;
|
|
3581
|
+
picker.style.display = "none";
|
|
3582
|
+
if (element.accept) {
|
|
3583
|
+
picker.accept = typeof element.accept === "string" ? element.accept : ((_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`).join(",")) || "";
|
|
3584
|
+
}
|
|
3585
|
+
const fileContainer = document.createElement("div");
|
|
3586
|
+
fileContainer.className = "file-preview-container";
|
|
3587
|
+
const initial = ctx.prefill[element.key];
|
|
3588
|
+
const allowedExts = getAllowedExtensions(element.accept);
|
|
3589
|
+
const maxSizeMB = (_b = element.maxSize) != null ? _b : Infinity;
|
|
3590
|
+
const handlers = {
|
|
3591
|
+
fileUploadHandler() {
|
|
3592
|
+
picker.click();
|
|
3593
|
+
},
|
|
3594
|
+
dragHandler(files) {
|
|
3098
3595
|
if (files.length > 0) {
|
|
3099
|
-
const deps = { picker, fileUploadHandler, dragHandler };
|
|
3100
3596
|
handleFileSelect(
|
|
3101
3597
|
files[0],
|
|
3102
3598
|
fileContainer,
|
|
3103
3599
|
pathKey,
|
|
3104
3600
|
state,
|
|
3105
|
-
|
|
3601
|
+
buildDeps(),
|
|
3106
3602
|
ctx.instance,
|
|
3107
3603
|
allowedExts,
|
|
3108
3604
|
maxSizeMB
|
|
3109
3605
|
);
|
|
3110
3606
|
}
|
|
3111
|
-
}
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3607
|
+
},
|
|
3608
|
+
setupDrop(container) {
|
|
3609
|
+
setupDragAndDrop(container, handlers.dragHandler);
|
|
3610
|
+
},
|
|
3611
|
+
restoreDropzone() {
|
|
3612
|
+
const hint = makeFieldHint(element, state);
|
|
3613
|
+
fileContainer.className = "file-preview-container w-full max-w-md bg-gray-100 rounded-lg overflow-hidden relative group cursor-pointer";
|
|
3614
|
+
fileContainer.style.height = "128px";
|
|
3615
|
+
setEmptyFileContainer(fileContainer, state, hint);
|
|
3616
|
+
fileContainer.onclick = handlers.fileUploadHandler;
|
|
3617
|
+
setupDragAndDrop(fileContainer, handlers.dragHandler);
|
|
3618
|
+
},
|
|
3619
|
+
onRemove() {
|
|
3620
|
+
var _a2;
|
|
3621
|
+
const hiddenInput = fileWrapper.querySelector('input[type="hidden"]');
|
|
3622
|
+
const currentRid = hiddenInput == null ? void 0 : hiddenInput.value;
|
|
3623
|
+
if (currentRid) {
|
|
3624
|
+
releaseLocalFileUrl((_a2 = state.resourceIndex.get(currentRid)) == null ? void 0 : _a2.file);
|
|
3625
|
+
}
|
|
3626
|
+
if (hiddenInput) hiddenInput.value = "";
|
|
3627
|
+
handlers.restoreDropzone();
|
|
3628
|
+
}
|
|
3629
|
+
};
|
|
3630
|
+
const buildDeps = () => ({
|
|
3631
|
+
picker,
|
|
3632
|
+
fileUploadHandler: handlers.fileUploadHandler,
|
|
3633
|
+
dragHandler: handlers.dragHandler,
|
|
3634
|
+
setupDrop: handlers.setupDrop,
|
|
3635
|
+
onRemove: handlers.onRemove
|
|
3636
|
+
});
|
|
3637
|
+
if (initial) {
|
|
3638
|
+
handleInitialFileData(
|
|
3639
|
+
initial,
|
|
3640
|
+
fileContainer,
|
|
3641
|
+
pathKey,
|
|
3642
|
+
fileWrapper,
|
|
3643
|
+
state,
|
|
3644
|
+
buildDeps()
|
|
3645
|
+
);
|
|
3646
|
+
const prefillMeta = state.resourceIndex.get(initial);
|
|
3647
|
+
if ((_c = prefillMeta == null ? void 0 : prefillMeta.type) == null ? void 0 : _c.startsWith("video/")) {
|
|
3648
|
+
fileContainer.onclick = handlers.fileUploadHandler;
|
|
3649
|
+
setupDragAndDrop(fileContainer, handlers.dragHandler);
|
|
3650
|
+
}
|
|
3651
|
+
} else {
|
|
3652
|
+
handlers.restoreDropzone();
|
|
3653
|
+
}
|
|
3654
|
+
picker.onchange = () => {
|
|
3655
|
+
if (picker.files && picker.files.length > 0) {
|
|
3656
|
+
handleFileSelect(
|
|
3657
|
+
picker.files[0],
|
|
3115
3658
|
fileContainer,
|
|
3116
3659
|
pathKey,
|
|
3117
|
-
fileWrapper,
|
|
3118
3660
|
state,
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
}
|
|
3661
|
+
buildDeps(),
|
|
3662
|
+
ctx.instance,
|
|
3663
|
+
allowedExts,
|
|
3664
|
+
maxSizeMB
|
|
3124
3665
|
);
|
|
3125
|
-
} else {
|
|
3126
|
-
const hint = makeFieldHint(element, state);
|
|
3127
|
-
setEmptyFileContainer(fileContainer, state, hint);
|
|
3128
3666
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
const deps = { picker, fileUploadHandler, dragHandler };
|
|
3134
|
-
handleFileSelect(
|
|
3135
|
-
picker.files[0],
|
|
3136
|
-
fileContainer,
|
|
3137
|
-
pathKey,
|
|
3138
|
-
state,
|
|
3139
|
-
deps,
|
|
3140
|
-
ctx.instance,
|
|
3141
|
-
allowedExts,
|
|
3142
|
-
maxSizeMB
|
|
3143
|
-
);
|
|
3144
|
-
}
|
|
3145
|
-
};
|
|
3146
|
-
fileWrapper.appendChild(fileContainer);
|
|
3147
|
-
fileWrapper.appendChild(picker);
|
|
3148
|
-
wrapper.appendChild(fileWrapper);
|
|
3149
|
-
}
|
|
3667
|
+
};
|
|
3668
|
+
fileWrapper.appendChild(fileContainer);
|
|
3669
|
+
fileWrapper.appendChild(picker);
|
|
3670
|
+
wrapper.appendChild(fileWrapper);
|
|
3150
3671
|
}
|
|
3151
|
-
function
|
|
3672
|
+
function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
|
|
3152
3673
|
var _a, _b;
|
|
3153
3674
|
const state = ctx.state;
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
list,
|
|
3188
|
-
initialFiles,
|
|
3189
|
-
state,
|
|
3190
|
-
(ridToRemove) => {
|
|
3191
|
-
const index = initialFiles.indexOf(ridToRemove);
|
|
3192
|
-
if (index > -1) {
|
|
3193
|
-
initialFiles.splice(index, 1);
|
|
3194
|
-
}
|
|
3195
|
-
updateFilesList2();
|
|
3196
|
-
},
|
|
3197
|
-
filesFieldHint
|
|
3198
|
-
);
|
|
3199
|
-
};
|
|
3200
|
-
const filesWrapper = document.createElement("div");
|
|
3201
|
-
filesWrapper.className = "space-y-2";
|
|
3202
|
-
filesWrapper.dataset.filesWrapper = pathKey;
|
|
3203
|
-
const filesPicker = document.createElement("input");
|
|
3204
|
-
filesPicker.type = "file";
|
|
3205
|
-
filesPicker.name = pathKey;
|
|
3206
|
-
filesPicker.multiple = true;
|
|
3207
|
-
filesPicker.style.display = "none";
|
|
3208
|
-
if (element.accept) {
|
|
3209
|
-
filesPicker.accept = typeof element.accept === "string" ? element.accept : ((_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`).join(",")) || "";
|
|
3210
|
-
}
|
|
3211
|
-
const filesContainer = document.createElement("div");
|
|
3212
|
-
filesContainer.className = "border-2 border-dashed border-gray-300 rounded-lg p-3 hover:border-gray-400 transition-colors";
|
|
3213
|
-
const list = document.createElement("div");
|
|
3214
|
-
list.className = "files-list";
|
|
3215
|
-
const initialFiles = ctx.prefill[element.key] || [];
|
|
3216
|
-
addPrefillFilesToIndex(initialFiles, state);
|
|
3217
|
-
const filesFieldHint = makeFieldHint(element, state);
|
|
3218
|
-
const filesConstraints = {
|
|
3219
|
-
maxCount: Infinity,
|
|
3220
|
-
allowedExtensions: getAllowedExtensions(element.accept),
|
|
3221
|
-
maxSize: (_b = element.maxSize) != null ? _b : Infinity
|
|
3222
|
-
};
|
|
3223
|
-
updateFilesList2();
|
|
3224
|
-
setupFilesDropHandler(
|
|
3225
|
-
filesContainer,
|
|
3226
|
-
initialFiles,
|
|
3227
|
-
state,
|
|
3228
|
-
updateFilesList2,
|
|
3229
|
-
filesConstraints,
|
|
3230
|
-
pathKey,
|
|
3231
|
-
ctx.instance
|
|
3232
|
-
);
|
|
3233
|
-
setupFilesPickerHandler(
|
|
3234
|
-
filesPicker,
|
|
3675
|
+
const filesWrapper = document.createElement("div");
|
|
3676
|
+
filesWrapper.className = "space-y-2";
|
|
3677
|
+
filesWrapper.dataset.filesWrapper = pathKey;
|
|
3678
|
+
const filesPicker = document.createElement("input");
|
|
3679
|
+
filesPicker.type = "file";
|
|
3680
|
+
filesPicker.name = pathKey;
|
|
3681
|
+
filesPicker.multiple = true;
|
|
3682
|
+
filesPicker.style.display = "none";
|
|
3683
|
+
if (element.accept) {
|
|
3684
|
+
filesPicker.accept = typeof element.accept === "string" ? element.accept : ((_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`).join(",")) || "";
|
|
3685
|
+
}
|
|
3686
|
+
const filesContainer = document.createElement("div");
|
|
3687
|
+
filesContainer.className = "files-list-wrapper";
|
|
3688
|
+
filesContainer.style.cssText = "border:2px dashed var(--fb-file-upload-border-color,#d1d5db);border-radius:var(--fb-border-radius,0.5rem);padding:8px;transition:border-color var(--fb-transition-duration,200ms),background var(--fb-transition-duration,200ms);";
|
|
3689
|
+
const list = document.createElement("div");
|
|
3690
|
+
list.className = "files-list";
|
|
3691
|
+
const initialFiles = ctx.prefill[element.key] || [];
|
|
3692
|
+
addPrefillFilesToIndex(initialFiles, state.resourceIndex);
|
|
3693
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
|
|
3694
|
+
const filesFieldHint = makeFieldHint(element, state);
|
|
3695
|
+
const filesConstraints = {
|
|
3696
|
+
maxCount: Infinity,
|
|
3697
|
+
allowedExtensions: getAllowedExtensions(element.accept),
|
|
3698
|
+
maxSize: (_b = element.maxSize) != null ? _b : Infinity
|
|
3699
|
+
};
|
|
3700
|
+
filesContainer.appendChild(list);
|
|
3701
|
+
filesWrapper.appendChild(filesPicker);
|
|
3702
|
+
filesWrapper.appendChild(filesContainer);
|
|
3703
|
+
wrapper.appendChild(filesWrapper);
|
|
3704
|
+
function updateFilesList() {
|
|
3705
|
+
const currentlyReadonly = isElementReadonly(element, state);
|
|
3706
|
+
renderResourcePills(
|
|
3707
|
+
list,
|
|
3235
3708
|
initialFiles,
|
|
3236
3709
|
state,
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3710
|
+
currentlyReadonly ? null : (ridToRemove) => {
|
|
3711
|
+
var _a2;
|
|
3712
|
+
releaseLocalFileUrl((_a2 = state.resourceIndex.get(ridToRemove)) == null ? void 0 : _a2.file);
|
|
3713
|
+
const index = initialFiles.indexOf(ridToRemove);
|
|
3714
|
+
if (index > -1) initialFiles.splice(index, 1);
|
|
3715
|
+
updateFilesList();
|
|
3716
|
+
},
|
|
3717
|
+
filesFieldHint,
|
|
3718
|
+
void 0,
|
|
3719
|
+
void 0,
|
|
3720
|
+
currentlyReadonly
|
|
3241
3721
|
);
|
|
3242
|
-
filesContainer.appendChild(list);
|
|
3243
|
-
filesWrapper.appendChild(filesContainer);
|
|
3244
|
-
filesWrapper.appendChild(filesPicker);
|
|
3245
|
-
wrapper.appendChild(filesWrapper);
|
|
3246
3722
|
}
|
|
3723
|
+
updateFilesList();
|
|
3724
|
+
setupFilesDropHandler(
|
|
3725
|
+
filesContainer,
|
|
3726
|
+
initialFiles,
|
|
3727
|
+
state,
|
|
3728
|
+
updateFilesList,
|
|
3729
|
+
filesConstraints,
|
|
3730
|
+
pathKey,
|
|
3731
|
+
ctx.instance
|
|
3732
|
+
);
|
|
3733
|
+
setupFilesPickerHandler(
|
|
3734
|
+
filesPicker,
|
|
3735
|
+
initialFiles,
|
|
3736
|
+
state,
|
|
3737
|
+
updateFilesList,
|
|
3738
|
+
filesConstraints,
|
|
3739
|
+
pathKey,
|
|
3740
|
+
ctx.instance
|
|
3741
|
+
);
|
|
3247
3742
|
}
|
|
3248
|
-
function
|
|
3743
|
+
function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
3249
3744
|
var _a, _b, _c, _d;
|
|
3250
3745
|
const state = ctx.state;
|
|
3251
3746
|
const minFiles = (_a = element.minCount) != null ? _a : 0;
|
|
3252
3747
|
const maxFiles = (_b = element.maxCount) != null ? _b : Infinity;
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
const
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
filesPicker.multiple = true;
|
|
3291
|
-
filesPicker.style.display = "none";
|
|
3292
|
-
if (element.accept) {
|
|
3293
|
-
filesPicker.accept = typeof element.accept === "string" ? element.accept : ((_c = element.accept.extensions) == null ? void 0 : _c.map((ext) => `.${ext}`).join(",")) || "";
|
|
3294
|
-
}
|
|
3295
|
-
const filesContainer = document.createElement("div");
|
|
3296
|
-
filesContainer.className = "files-list space-y-2";
|
|
3297
|
-
filesWrapper.appendChild(filesPicker);
|
|
3298
|
-
filesWrapper.appendChild(filesContainer);
|
|
3299
|
-
const initialFiles = Array.isArray(ctx.prefill[element.key]) ? [...ctx.prefill[element.key]] : [];
|
|
3300
|
-
addPrefillFilesToIndex(initialFiles, state);
|
|
3301
|
-
const multipleFilesHint = makeFieldHint(element, state);
|
|
3302
|
-
const multipleConstraints = {
|
|
3303
|
-
maxCount: maxFiles,
|
|
3304
|
-
allowedExtensions: getAllowedExtensions(element.accept),
|
|
3305
|
-
maxSize: (_d = element.maxSize) != null ? _d : Infinity
|
|
3306
|
-
};
|
|
3307
|
-
const buildCountInfo = () => {
|
|
3308
|
-
const countText = initialFiles.length === 1 ? t("fileCountSingle", state, { count: initialFiles.length }) : t("fileCountPlural", state, { count: initialFiles.length });
|
|
3309
|
-
const minMaxText = minFiles > 0 || maxFiles < Infinity ? ` ${t("fileCountRange", state, { min: minFiles, max: maxFiles })}` : "";
|
|
3310
|
-
return countText + minMaxText;
|
|
3311
|
-
};
|
|
3312
|
-
const updateFilesDisplay = () => {
|
|
3313
|
-
renderResourcePills(
|
|
3314
|
-
filesContainer,
|
|
3315
|
-
initialFiles,
|
|
3316
|
-
state,
|
|
3317
|
-
(index) => {
|
|
3318
|
-
initialFiles.splice(initialFiles.indexOf(index), 1);
|
|
3319
|
-
updateFilesDisplay();
|
|
3320
|
-
},
|
|
3321
|
-
multipleFilesHint,
|
|
3322
|
-
buildCountInfo()
|
|
3323
|
-
);
|
|
3324
|
-
};
|
|
3325
|
-
setupFilesDropHandler(
|
|
3326
|
-
filesContainer,
|
|
3748
|
+
const filesWrapper = document.createElement("div");
|
|
3749
|
+
filesWrapper.className = "space-y-2";
|
|
3750
|
+
filesWrapper.dataset.filesWrapper = pathKey;
|
|
3751
|
+
const filesPicker = document.createElement("input");
|
|
3752
|
+
filesPicker.type = "file";
|
|
3753
|
+
filesPicker.name = pathKey;
|
|
3754
|
+
filesPicker.multiple = true;
|
|
3755
|
+
filesPicker.style.display = "none";
|
|
3756
|
+
if (element.accept) {
|
|
3757
|
+
filesPicker.accept = typeof element.accept === "string" ? element.accept : ((_c = element.accept.extensions) == null ? void 0 : _c.map((ext) => `.${ext}`).join(",")) || "";
|
|
3758
|
+
}
|
|
3759
|
+
const filesContainer = document.createElement("div");
|
|
3760
|
+
filesContainer.className = "files-list-wrapper";
|
|
3761
|
+
filesContainer.style.cssText = "border:2px dashed var(--fb-file-upload-border-color,#d1d5db);border-radius:var(--fb-border-radius,0.5rem);padding:8px;transition:border-color var(--fb-transition-duration,200ms),background var(--fb-transition-duration,200ms);";
|
|
3762
|
+
const list = document.createElement("div");
|
|
3763
|
+
list.className = "files-list";
|
|
3764
|
+
filesWrapper.appendChild(filesPicker);
|
|
3765
|
+
filesWrapper.appendChild(filesContainer);
|
|
3766
|
+
filesContainer.appendChild(list);
|
|
3767
|
+
const initialFiles = Array.isArray(ctx.prefill[element.key]) ? [...ctx.prefill[element.key]] : [];
|
|
3768
|
+
addPrefillFilesToIndex(initialFiles, state.resourceIndex);
|
|
3769
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
|
|
3770
|
+
const multipleFilesHint = makeFieldHint(element, state);
|
|
3771
|
+
const multipleConstraints = {
|
|
3772
|
+
maxCount: maxFiles,
|
|
3773
|
+
allowedExtensions: getAllowedExtensions(element.accept),
|
|
3774
|
+
maxSize: (_d = element.maxSize) != null ? _d : Infinity
|
|
3775
|
+
};
|
|
3776
|
+
const buildCountInfo = () => {
|
|
3777
|
+
const countText = initialFiles.length === 1 ? t("fileCountSingle", state, { count: initialFiles.length }) : t("fileCountPlural", state, { count: initialFiles.length });
|
|
3778
|
+
const minMaxText = minFiles > 0 || maxFiles < Infinity ? ` ${t("fileCountRange", state, { min: minFiles, max: maxFiles })}` : "";
|
|
3779
|
+
return countText + minMaxText;
|
|
3780
|
+
};
|
|
3781
|
+
const updateFilesDisplay = () => {
|
|
3782
|
+
const currentlyReadonly = isElementReadonly(element, state);
|
|
3783
|
+
renderResourcePills(
|
|
3784
|
+
list,
|
|
3327
3785
|
initialFiles,
|
|
3328
3786
|
state,
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3787
|
+
currentlyReadonly ? null : (index) => {
|
|
3788
|
+
var _a2;
|
|
3789
|
+
releaseLocalFileUrl((_a2 = state.resourceIndex.get(index)) == null ? void 0 : _a2.file);
|
|
3790
|
+
initialFiles.splice(initialFiles.indexOf(index), 1);
|
|
3791
|
+
updateFilesDisplay();
|
|
3792
|
+
},
|
|
3793
|
+
multipleFilesHint,
|
|
3794
|
+
buildCountInfo(),
|
|
3795
|
+
maxFiles < Infinity ? maxFiles : void 0,
|
|
3796
|
+
currentlyReadonly
|
|
3333
3797
|
);
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3798
|
+
};
|
|
3799
|
+
setupFilesDropHandler(
|
|
3800
|
+
filesContainer,
|
|
3801
|
+
initialFiles,
|
|
3802
|
+
state,
|
|
3803
|
+
updateFilesDisplay,
|
|
3804
|
+
multipleConstraints,
|
|
3805
|
+
pathKey,
|
|
3806
|
+
ctx.instance
|
|
3807
|
+
);
|
|
3808
|
+
setupFilesPickerHandler(
|
|
3809
|
+
filesPicker,
|
|
3810
|
+
initialFiles,
|
|
3811
|
+
state,
|
|
3812
|
+
updateFilesDisplay,
|
|
3813
|
+
multipleConstraints,
|
|
3814
|
+
pathKey,
|
|
3815
|
+
ctx.instance
|
|
3816
|
+
);
|
|
3817
|
+
updateFilesDisplay();
|
|
3818
|
+
wrapper.appendChild(filesWrapper);
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
// src/components/file/validate.ts
|
|
3822
|
+
function readMultiFileResourceIds(scopeRoot, fullKey) {
|
|
3823
|
+
const wrapper = scopeRoot.querySelector(
|
|
3824
|
+
`[data-files-wrapper="${fullKey}"]`
|
|
3825
|
+
);
|
|
3826
|
+
if (!wrapper) return [];
|
|
3827
|
+
const encoded = wrapper.dataset.resourceIds;
|
|
3828
|
+
if (encoded === void 0) {
|
|
3829
|
+
throw new Error(
|
|
3830
|
+
`readMultiFileResourceIds: [data-files-wrapper="${fullKey}"] is missing data-resource-ids attribute. This is a render bug.`
|
|
3342
3831
|
);
|
|
3343
|
-
updateFilesDisplay();
|
|
3344
|
-
wrapper.appendChild(filesWrapper);
|
|
3345
3832
|
}
|
|
3833
|
+
const parsed = JSON.parse(encoded);
|
|
3834
|
+
if (!Array.isArray(parsed)) {
|
|
3835
|
+
throw new Error(
|
|
3836
|
+
`readMultiFileResourceIds: data-resource-ids on [data-files-wrapper="${fullKey}"] is not a JSON array. Got: ${encoded}`
|
|
3837
|
+
);
|
|
3838
|
+
}
|
|
3839
|
+
return parsed;
|
|
3346
3840
|
}
|
|
3347
|
-
function
|
|
3841
|
+
function validateFileCount(key, resourceIds, element, state, errors) {
|
|
3842
|
+
var _a, _b;
|
|
3843
|
+
const minFiles = "minCount" in element ? (_a = element.minCount) != null ? _a : 0 : 0;
|
|
3844
|
+
const maxFiles = "maxCount" in element ? (_b = element.maxCount) != null ? _b : Infinity : Infinity;
|
|
3845
|
+
if (element.required && resourceIds.length === 0) {
|
|
3846
|
+
errors.push(`${key}: ${t("required", state)}`);
|
|
3847
|
+
}
|
|
3848
|
+
if (resourceIds.length < minFiles) {
|
|
3849
|
+
errors.push(`${key}: ${t("minFiles", state, { min: minFiles })}`);
|
|
3850
|
+
}
|
|
3851
|
+
if (resourceIds.length > maxFiles) {
|
|
3852
|
+
errors.push(`${key}: ${t("maxFiles", state, { max: maxFiles })}`);
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
function validateFileExtensions(key, resourceIds, element, state, errors) {
|
|
3348
3856
|
var _a;
|
|
3349
|
-
const
|
|
3350
|
-
const
|
|
3351
|
-
|
|
3352
|
-
const
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
const
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
}
|
|
3361
|
-
if (resourceIds.length < minFiles) {
|
|
3362
|
-
errors.push(`${key2}: ${t("minFiles", state, { min: minFiles })}`);
|
|
3363
|
-
}
|
|
3364
|
-
if (resourceIds.length > maxFiles) {
|
|
3365
|
-
errors.push(`${key2}: ${t("maxFiles", state, { max: maxFiles })}`);
|
|
3366
|
-
}
|
|
3367
|
-
};
|
|
3368
|
-
const validateFileExtensions = (key2, resourceIds, element2) => {
|
|
3369
|
-
var _a2;
|
|
3370
|
-
if (skipValidation) return;
|
|
3371
|
-
const { state } = context;
|
|
3372
|
-
const acceptField = "accept" in element2 ? element2.accept : void 0;
|
|
3373
|
-
const allowedExtensions = getAllowedExtensions(acceptField);
|
|
3374
|
-
if (allowedExtensions.length === 0) return;
|
|
3375
|
-
const formats = allowedExtensions.join(", ");
|
|
3376
|
-
for (const rid of resourceIds) {
|
|
3377
|
-
const meta = state.resourceIndex.get(rid);
|
|
3378
|
-
const fileName = (_a2 = meta == null ? void 0 : meta.name) != null ? _a2 : rid;
|
|
3379
|
-
if (!isFileExtensionAllowed(fileName, allowedExtensions)) {
|
|
3380
|
-
errors.push(
|
|
3381
|
-
`${key2}: ${t("invalidFileExtension", state, { name: fileName, formats })}`
|
|
3382
|
-
);
|
|
3383
|
-
}
|
|
3857
|
+
const acceptField = "accept" in element ? element.accept : void 0;
|
|
3858
|
+
const allowedExtensions = getAllowedExtensions(acceptField);
|
|
3859
|
+
if (allowedExtensions.length === 0) return;
|
|
3860
|
+
const formats = allowedExtensions.join(", ");
|
|
3861
|
+
for (const rid of resourceIds) {
|
|
3862
|
+
const meta = state.resourceIndex.get(rid);
|
|
3863
|
+
const fileName = (_a = meta == null ? void 0 : meta.name) != null ? _a : rid;
|
|
3864
|
+
if (!isFileExtensionAllowed(fileName, allowedExtensions)) {
|
|
3865
|
+
errors.push(
|
|
3866
|
+
`${key}: ${t("invalidFileExtension", state, { name: fileName, formats })}`
|
|
3867
|
+
);
|
|
3384
3868
|
}
|
|
3385
|
-
}
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
);
|
|
3399
|
-
}
|
|
3869
|
+
}
|
|
3870
|
+
}
|
|
3871
|
+
function validateFileSizes(key, resourceIds, element, state, errors) {
|
|
3872
|
+
var _a;
|
|
3873
|
+
const maxSizeMB = "maxSize" in element ? (_a = element.maxSize) != null ? _a : Infinity : Infinity;
|
|
3874
|
+
if (maxSizeMB === Infinity) return;
|
|
3875
|
+
for (const rid of resourceIds) {
|
|
3876
|
+
const meta = state.resourceIndex.get(rid);
|
|
3877
|
+
if (!meta) continue;
|
|
3878
|
+
if (meta.size > maxSizeMB * 1024 * 1024) {
|
|
3879
|
+
errors.push(
|
|
3880
|
+
`${key}: ${t("fileTooLarge", state, { name: meta.name, maxSize: maxSizeMB })}`
|
|
3881
|
+
);
|
|
3400
3882
|
}
|
|
3401
|
-
}
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
function validateMultiFile(element, key, context) {
|
|
3886
|
+
const { scopeRoot, skipValidation, path, state } = context;
|
|
3887
|
+
const errors = [];
|
|
3888
|
+
const fullKey = pathJoin(path, key);
|
|
3889
|
+
const resourceIds = readMultiFileResourceIds(scopeRoot, fullKey);
|
|
3890
|
+
if (!skipValidation) {
|
|
3891
|
+
validateFileCount(key, resourceIds, element, state, errors);
|
|
3892
|
+
validateFileExtensions(key, resourceIds, element, state, errors);
|
|
3893
|
+
validateFileSizes(key, resourceIds, element, state, errors);
|
|
3894
|
+
}
|
|
3895
|
+
return { value: resourceIds, errors };
|
|
3896
|
+
}
|
|
3897
|
+
function validateSingleFile(element, key, context) {
|
|
3898
|
+
var _a;
|
|
3899
|
+
const { scopeRoot, skipValidation, state } = context;
|
|
3900
|
+
const errors = [];
|
|
3901
|
+
const input = scopeRoot.querySelector(
|
|
3902
|
+
`input[name$="${key}"][type="hidden"]`
|
|
3903
|
+
);
|
|
3904
|
+
const rid = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
3905
|
+
if (!skipValidation && element.required && rid === "") {
|
|
3906
|
+
errors.push(`${key}: ${t("required", state)}`);
|
|
3907
|
+
return { value: null, errors };
|
|
3908
|
+
}
|
|
3909
|
+
if (!skipValidation && rid !== "") {
|
|
3910
|
+
validateFileExtensions(key, [rid], element, state, errors);
|
|
3911
|
+
validateFileSizes(key, [rid], element, state, errors);
|
|
3912
|
+
}
|
|
3913
|
+
return { value: rid || null, errors };
|
|
3914
|
+
}
|
|
3915
|
+
function validateFileElement(element, key, context) {
|
|
3916
|
+
const isMultipleField = element.type === "files" || "multiple" in element && Boolean(element.multiple);
|
|
3402
3917
|
if (isMultipleField) {
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3918
|
+
return validateMultiFile(element, key, context);
|
|
3919
|
+
}
|
|
3920
|
+
return validateSingleFile(element, key, context);
|
|
3921
|
+
}
|
|
3922
|
+
|
|
3923
|
+
// src/components/file/render-readonly.ts
|
|
3924
|
+
function renderFileElementReadonly(element, ctx, wrapper, pathKey) {
|
|
3925
|
+
const state = ctx.state;
|
|
3926
|
+
const rawInitial = ctx.prefill[element.key];
|
|
3927
|
+
const initial = typeof rawInitial === "string" ? rawInitial : "";
|
|
3928
|
+
if (initial) {
|
|
3929
|
+
addPrefillFilesToIndex([initial], state.resourceIndex);
|
|
3930
|
+
const hiddenInput = document.createElement("input");
|
|
3931
|
+
hiddenInput.type = "hidden";
|
|
3932
|
+
hiddenInput.name = pathKey;
|
|
3933
|
+
hiddenInput.value = initial;
|
|
3934
|
+
wrapper.appendChild(hiddenInput);
|
|
3935
|
+
renderFilePreviewReadonly(initial, state).then((filePreview) => {
|
|
3936
|
+
wrapper.appendChild(filePreview);
|
|
3937
|
+
}).catch((err) => {
|
|
3938
|
+
console.error("Failed to render file preview:", err);
|
|
3939
|
+
wrapper.appendChild(buildEmptyReadonlyTile(state));
|
|
3940
|
+
});
|
|
3422
3941
|
} else {
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
}
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3942
|
+
wrapper.appendChild(buildEmptyReadonlyTile(state));
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
function buildEmptyReadonlyTile(state) {
|
|
3946
|
+
const emptyState = document.createElement("div");
|
|
3947
|
+
emptyState.style.cssText = `
|
|
3948
|
+
width:${TILE_SIZE};
|
|
3949
|
+
height:${TILE_SIZE};
|
|
3950
|
+
display:flex;
|
|
3951
|
+
align-items:center;
|
|
3952
|
+
justify-content:center;
|
|
3953
|
+
background:var(--fb-file-upload-bg-color,#f3f4f6);
|
|
3954
|
+
border-radius:var(--fb-border-radius,0.5rem);
|
|
3955
|
+
border:1px solid var(--fb-file-upload-border-color,#d1d5db);
|
|
3956
|
+
`;
|
|
3957
|
+
emptyState.innerHTML = `<div style="font-size:11px;text-align:center;color:var(--fb-text-secondary-color,#6b7280);">${escapeHtml(t("noFileSelected", state))}</div>`;
|
|
3958
|
+
return emptyState;
|
|
3959
|
+
}
|
|
3960
|
+
function renderMultiFileReadonly(rids, state, wrapper, pathKey, marginTop) {
|
|
3961
|
+
addPrefillFilesToIndex(rids, state.resourceIndex);
|
|
3962
|
+
const filesWrapper = document.createElement("div");
|
|
3963
|
+
filesWrapper.dataset.filesWrapper = pathKey;
|
|
3964
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(rids);
|
|
3965
|
+
wrapper.appendChild(filesWrapper);
|
|
3966
|
+
if (rids.length === 0) {
|
|
3967
|
+
const emptyEl = document.createElement("div");
|
|
3968
|
+
emptyEl.className = "fb-tile-empty-text";
|
|
3969
|
+
emptyEl.textContent = t("noFilesSelected", state);
|
|
3970
|
+
filesWrapper.appendChild(emptyEl);
|
|
3971
|
+
return;
|
|
3972
|
+
}
|
|
3973
|
+
const tilesWrap = document.createElement("div");
|
|
3974
|
+
tilesWrap.style.cssText = `display:flex;flex-wrap:wrap;gap:6px;${marginTop ? `margin-top:${marginTop};` : ""}`;
|
|
3975
|
+
filesWrapper.appendChild(tilesWrap);
|
|
3976
|
+
const placeholders = rids.map(() => {
|
|
3977
|
+
const placeholder = document.createElement("div");
|
|
3978
|
+
placeholder.style.cssText = `width:${TILE_SIZE};height:${TILE_SIZE};`;
|
|
3979
|
+
tilesWrap.appendChild(placeholder);
|
|
3980
|
+
return placeholder;
|
|
3981
|
+
});
|
|
3982
|
+
for (let i = 0; i < rids.length; i++) {
|
|
3983
|
+
const resourceId = rids[i];
|
|
3984
|
+
const placeholder = placeholders[i];
|
|
3985
|
+
renderFilePreviewReadonly(resourceId, state).then((tileEl) => {
|
|
3986
|
+
placeholder.replaceWith(tileEl);
|
|
3987
|
+
}).catch((err) => {
|
|
3988
|
+
console.error("Failed to render readonly tile:", err);
|
|
3989
|
+
});
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
function renderFilesElementReadonly(element, ctx, wrapper, pathKey) {
|
|
3993
|
+
const rawPrefill = ctx.prefill[element.key];
|
|
3994
|
+
const initialFiles = Array.isArray(rawPrefill) ? rawPrefill : [];
|
|
3995
|
+
renderMultiFileReadonly(initialFiles, ctx.state, wrapper, pathKey);
|
|
3996
|
+
}
|
|
3997
|
+
function renderMultipleFileElementReadonly(element, ctx, wrapper, pathKey) {
|
|
3998
|
+
const rawPrefill = ctx.prefill[element.key];
|
|
3999
|
+
const initialFiles = Array.isArray(rawPrefill) ? rawPrefill : [];
|
|
4000
|
+
renderMultiFileReadonly(initialFiles, ctx.state, wrapper, pathKey, "4px");
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
// src/components/file.ts
|
|
4004
|
+
function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
4005
|
+
if (isElementReadonly(element, ctx.state, ctx)) {
|
|
4006
|
+
renderFileElementReadonly(element, ctx, wrapper, pathKey);
|
|
4007
|
+
} else {
|
|
4008
|
+
renderFileElementEdit(element, ctx, wrapper, pathKey);
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
4012
|
+
if (isElementReadonly(element, ctx.state, ctx)) {
|
|
4013
|
+
renderFilesElementReadonly(element, ctx, wrapper, pathKey);
|
|
4014
|
+
} else {
|
|
4015
|
+
renderFilesElementEdit(element, ctx, wrapper, pathKey);
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
|
|
4019
|
+
if (isElementReadonly(element, ctx.state, ctx)) {
|
|
4020
|
+
renderMultipleFileElementReadonly(element, ctx, wrapper, pathKey);
|
|
4021
|
+
} else {
|
|
4022
|
+
renderMultipleFileElementEdit(element, ctx, wrapper, pathKey);
|
|
3436
4023
|
}
|
|
3437
4024
|
}
|
|
3438
4025
|
function updateFileField(element, fieldPath, value, context) {
|
|
3439
|
-
var _a;
|
|
3440
4026
|
const { scopeRoot, state } = context;
|
|
3441
|
-
if ("multiple" in element && element.multiple) {
|
|
4027
|
+
if (element.type === "files" || "multiple" in element && element.multiple) {
|
|
3442
4028
|
if (!Array.isArray(value)) {
|
|
3443
4029
|
console.warn(
|
|
3444
4030
|
`updateFileField: Expected array for multiple file field "${fieldPath}", got ${typeof value}`
|
|
@@ -3446,32 +4032,22 @@ function updateFileField(element, fieldPath, value, context) {
|
|
|
3446
4032
|
return;
|
|
3447
4033
|
}
|
|
3448
4034
|
value.forEach((resourceId) => {
|
|
3449
|
-
var _a2;
|
|
3450
4035
|
if (resourceId && typeof resourceId === "string") {
|
|
3451
4036
|
if (!state.resourceIndex.has(resourceId)) {
|
|
3452
|
-
|
|
3453
|
-
const extension = (_a2 = filename.split(".").pop()) == null ? void 0 : _a2.toLowerCase();
|
|
3454
|
-
let fileType = "application/octet-stream";
|
|
3455
|
-
if (extension) {
|
|
3456
|
-
if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
|
|
3457
|
-
fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
|
|
3458
|
-
} else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
|
|
3459
|
-
fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
|
|
3460
|
-
}
|
|
3461
|
-
}
|
|
3462
|
-
state.resourceIndex.set(resourceId, {
|
|
3463
|
-
name: filename,
|
|
3464
|
-
type: fileType,
|
|
3465
|
-
size: 0,
|
|
3466
|
-
uploadedAt: /* @__PURE__ */ new Date(),
|
|
3467
|
-
file: void 0
|
|
3468
|
-
});
|
|
4037
|
+
addResourceToIndex(resourceId, state);
|
|
3469
4038
|
}
|
|
3470
4039
|
}
|
|
3471
4040
|
});
|
|
3472
|
-
|
|
3473
|
-
`
|
|
4041
|
+
const filesWrapper = scopeRoot.querySelector(
|
|
4042
|
+
`[data-files-wrapper="${fieldPath}"]`
|
|
3474
4043
|
);
|
|
4044
|
+
if (filesWrapper) {
|
|
4045
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(value);
|
|
4046
|
+
} else {
|
|
4047
|
+
console.warn(
|
|
4048
|
+
`updateFileField: [data-files-wrapper="${fieldPath}"] not found in DOM; data-resource-ids not updated`
|
|
4049
|
+
);
|
|
4050
|
+
}
|
|
3475
4051
|
} else {
|
|
3476
4052
|
const hiddenInput = scopeRoot.querySelector(
|
|
3477
4053
|
`input[name="${fieldPath}"][type="hidden"]`
|
|
@@ -3485,23 +4061,7 @@ function updateFileField(element, fieldPath, value, context) {
|
|
|
3485
4061
|
hiddenInput.value = value != null ? String(value) : "";
|
|
3486
4062
|
if (value && typeof value === "string") {
|
|
3487
4063
|
if (!state.resourceIndex.has(value)) {
|
|
3488
|
-
|
|
3489
|
-
const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
|
|
3490
|
-
let fileType = "application/octet-stream";
|
|
3491
|
-
if (extension) {
|
|
3492
|
-
if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
|
|
3493
|
-
fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
|
|
3494
|
-
} else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
|
|
3495
|
-
fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
|
|
3496
|
-
}
|
|
3497
|
-
}
|
|
3498
|
-
state.resourceIndex.set(value, {
|
|
3499
|
-
name: filename,
|
|
3500
|
-
type: fileType,
|
|
3501
|
-
size: 0,
|
|
3502
|
-
uploadedAt: /* @__PURE__ */ new Date(),
|
|
3503
|
-
file: void 0
|
|
3504
|
-
});
|
|
4064
|
+
addResourceToIndex(value, state);
|
|
3505
4065
|
}
|
|
3506
4066
|
console.info(
|
|
3507
4067
|
`updateFileField: File field "${fieldPath}" updated. Preview update requires re-render.`
|
|
@@ -3509,6 +4069,26 @@ function updateFileField(element, fieldPath, value, context) {
|
|
|
3509
4069
|
}
|
|
3510
4070
|
}
|
|
3511
4071
|
}
|
|
4072
|
+
function addResourceToIndex(resourceId, state) {
|
|
4073
|
+
var _a;
|
|
4074
|
+
const filename = resourceId.split("/").pop() || "file";
|
|
4075
|
+
const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
|
|
4076
|
+
let fileType = "application/octet-stream";
|
|
4077
|
+
if (extension) {
|
|
4078
|
+
if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
|
|
4079
|
+
fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
|
|
4080
|
+
} else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
|
|
4081
|
+
fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
state.resourceIndex.set(resourceId, {
|
|
4085
|
+
name: filename,
|
|
4086
|
+
type: fileType,
|
|
4087
|
+
size: 0,
|
|
4088
|
+
uploadedAt: /* @__PURE__ */ new Date(),
|
|
4089
|
+
file: void 0
|
|
4090
|
+
});
|
|
4091
|
+
}
|
|
3512
4092
|
|
|
3513
4093
|
// src/components/colour.ts
|
|
3514
4094
|
function normalizeColourValue(value) {
|
|
@@ -8256,6 +8836,8 @@ var defaultConfig = {
|
|
|
8256
8836
|
noFileSelected: "No file selected",
|
|
8257
8837
|
noFilesSelected: "No files selected",
|
|
8258
8838
|
downloadButton: "Download",
|
|
8839
|
+
downloadFile: "Download",
|
|
8840
|
+
openInNewTab: "Open in new tab",
|
|
8259
8841
|
changeButton: "Change",
|
|
8260
8842
|
placeholderText: "Enter text",
|
|
8261
8843
|
previewAlt: "Preview",
|
|
@@ -8277,6 +8859,8 @@ var defaultConfig = {
|
|
|
8277
8859
|
fileCountSingle: "{count} file",
|
|
8278
8860
|
fileCountPlural: "{count} files",
|
|
8279
8861
|
fileCountRange: "({min}-{max})",
|
|
8862
|
+
uploadingFile: "Uploading\u2026",
|
|
8863
|
+
filesCounter: "{count}/{max}",
|
|
8280
8864
|
// Validation errors
|
|
8281
8865
|
required: "Required",
|
|
8282
8866
|
minItems: "Minimum {min} items required",
|
|
@@ -8318,6 +8902,8 @@ var defaultConfig = {
|
|
|
8318
8902
|
noFileSelected: "\u0424\u0430\u0439\u043B \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D",
|
|
8319
8903
|
noFilesSelected: "\u041D\u0435\u0442 \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
8320
8904
|
downloadButton: "\u0421\u043A\u0430\u0447\u0430\u0442\u044C",
|
|
8905
|
+
downloadFile: "\u0421\u043A\u0430\u0447\u0430\u0442\u044C",
|
|
8906
|
+
openInNewTab: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 \u043D\u043E\u0432\u043E\u0439 \u0432\u043A\u043B\u0430\u0434\u043A\u0435",
|
|
8321
8907
|
changeButton: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C",
|
|
8322
8908
|
placeholderText: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442",
|
|
8323
8909
|
previewAlt: "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440",
|
|
@@ -8339,6 +8925,8 @@ var defaultConfig = {
|
|
|
8339
8925
|
fileCountSingle: "{count} \u0444\u0430\u0439\u043B",
|
|
8340
8926
|
fileCountPlural: "{count} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
8341
8927
|
fileCountRange: "({min}-{max})",
|
|
8928
|
+
uploadingFile: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430\u2026",
|
|
8929
|
+
filesCounter: "{count}/{max}",
|
|
8342
8930
|
// Validation errors
|
|
8343
8931
|
required: "\u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u043F\u043E\u043B\u0435",
|
|
8344
8932
|
minItems: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432",
|