@dmitryvim/form-builder 0.2.26 → 0.2.28
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 +60 -4
- package/dist/browser/formbuilder.min.js +534 -184
- package/dist/browser/formbuilder.v0.2.28.min.js +956 -0
- package/dist/cjs/index.cjs +2002 -1005
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +1976 -992
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +534 -184
- package/dist/types/components/file/constraints.d.ts +62 -0
- package/dist/types/components/file/dom.d.ts +44 -0
- package/dist/types/components/file/library.d.ts +49 -0
- package/dist/types/components/file/preview.d.ts +69 -0
- package/dist/types/components/file/render-edit.d.ts +18 -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 +25 -0
- package/dist/types/components/file/validate.d.ts +13 -0
- package/dist/types/components/file.d.ts +5 -27
- package/dist/types/index.d.ts +1 -1
- package/dist/types/types/config.d.ts +37 -0
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/schema.d.ts +13 -6
- 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,775 @@ 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
|
-
function
|
|
2139
|
+
function isSizeWithinLimit(bytes, maxSizeMB) {
|
|
2140
2140
|
if (maxSizeMB === Infinity) return true;
|
|
2141
|
-
return
|
|
2141
|
+
return bytes <= maxSizeMB * 1024 * 1024;
|
|
2142
|
+
}
|
|
2143
|
+
function isFileSizeAllowed(file, maxSizeMB) {
|
|
2144
|
+
return isSizeWithinLimit(file.size, maxSizeMB);
|
|
2145
|
+
}
|
|
2146
|
+
function getAllowedMimes(accept) {
|
|
2147
|
+
if (!accept) return [];
|
|
2148
|
+
if (typeof accept === "string") return [];
|
|
2149
|
+
if (!Array.isArray(accept.mime)) return [];
|
|
2150
|
+
return accept.mime.map((m) => m.toLowerCase());
|
|
2151
|
+
}
|
|
2152
|
+
function isMimeAllowed(mimeType, allowedMimes) {
|
|
2153
|
+
if (allowedMimes.length === 0) return true;
|
|
2154
|
+
const normalizedType = mimeType.toLowerCase();
|
|
2155
|
+
return allowedMimes.some((allowed) => {
|
|
2156
|
+
if (allowed.endsWith("/*")) {
|
|
2157
|
+
const prefix = allowed.slice(0, -1);
|
|
2158
|
+
return normalizedType.startsWith(prefix);
|
|
2159
|
+
}
|
|
2160
|
+
return normalizedType === allowed;
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
function inferMimeFromResourceId(resourceId) {
|
|
2164
|
+
var _a;
|
|
2165
|
+
const filename = resourceId.split("/").pop() || "file";
|
|
2166
|
+
const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
|
|
2167
|
+
if (extension) {
|
|
2168
|
+
if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
|
|
2169
|
+
return `image/${extension === "jpg" ? "jpeg" : extension}`;
|
|
2170
|
+
}
|
|
2171
|
+
if (["mp4", "webm", "mov", "avi"].includes(extension)) {
|
|
2172
|
+
return `video/${extension === "mov" ? "quicktime" : extension}`;
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
return "application/octet-stream";
|
|
2176
|
+
}
|
|
2177
|
+
function seedInferredResource(resourceId, resourceIndex) {
|
|
2178
|
+
if (resourceIndex.has(resourceId)) return;
|
|
2179
|
+
const filename = resourceId.split("/").pop() || "file";
|
|
2180
|
+
resourceIndex.set(resourceId, {
|
|
2181
|
+
name: filename,
|
|
2182
|
+
type: inferMimeFromResourceId(resourceId),
|
|
2183
|
+
size: 0,
|
|
2184
|
+
uploadedAt: /* @__PURE__ */ new Date(),
|
|
2185
|
+
file: void 0,
|
|
2186
|
+
inferredFromExtension: true
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
function addPrefillFilesToIndex(initialFiles, resourceIndex) {
|
|
2190
|
+
for (const resourceId of initialFiles) {
|
|
2191
|
+
seedInferredResource(resourceId, resourceIndex);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
// src/components/file/styles.ts
|
|
2196
|
+
var STYLE_ID = "fb-file-styles";
|
|
2197
|
+
function ensureFileStyles() {
|
|
2198
|
+
if (typeof document === "undefined") return;
|
|
2199
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
2200
|
+
const style = document.createElement("style");
|
|
2201
|
+
style.id = STYLE_ID;
|
|
2202
|
+
style.setAttribute("data-fb-file-styles", "true");
|
|
2203
|
+
style.textContent = `
|
|
2204
|
+
@keyframes fb-spin { to { transform: rotate(360deg); } }
|
|
2205
|
+
|
|
2206
|
+
/* Spinner used during single-file and multi-file upload */
|
|
2207
|
+
.fb-spinner {
|
|
2208
|
+
width: 36px;
|
|
2209
|
+
height: 36px;
|
|
2210
|
+
border: 3px solid rgba(0,0,0,0.12);
|
|
2211
|
+
border-top-color: var(--fb-text-secondary-color, #6b7280);
|
|
2212
|
+
border-radius: 50%;
|
|
2213
|
+
animation: fb-spin 0.7s linear infinite;
|
|
2214
|
+
flex-shrink: 0;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
/* Base tile: fixed 160\xD7160 square, theme-aware background */
|
|
2218
|
+
.fb-tile {
|
|
2219
|
+
width: var(--fb-tile-size, 160px);
|
|
2220
|
+
height: var(--fb-tile-size, 160px);
|
|
2221
|
+
flex-shrink: 0;
|
|
2222
|
+
position: relative;
|
|
2223
|
+
overflow: hidden;
|
|
2224
|
+
border-radius: var(--fb-border-radius, 0.5rem);
|
|
2225
|
+
background: var(--fb-file-upload-bg-color, #f3f4f6);
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
/* Uploaded resource tile \u2014 adds a visible border */
|
|
2229
|
+
.fb-tile-resource {
|
|
2230
|
+
border: 1px solid var(--fb-file-upload-border-color, #d1d5db);
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
/* Uploading placeholder tile \u2014 dashed border, uploading indicator */
|
|
2234
|
+
.fb-tile-uploading {
|
|
2235
|
+
border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
/* "+" add-more tile */
|
|
2239
|
+
.fb-tile-add {
|
|
2240
|
+
border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
|
|
2241
|
+
display: flex;
|
|
2242
|
+
align-items: center;
|
|
2243
|
+
justify-content: center;
|
|
2244
|
+
cursor: pointer;
|
|
2245
|
+
font-size: 32px;
|
|
2246
|
+
color: var(--fb-file-upload-text-color, #9ca3af);
|
|
2247
|
+
transition:
|
|
2248
|
+
border-color var(--fb-transition-duration, 200ms),
|
|
2249
|
+
color var(--fb-transition-duration, 200ms);
|
|
2250
|
+
}
|
|
2251
|
+
.fb-tile-add:hover {
|
|
2252
|
+
border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
|
|
2253
|
+
color: var(--fb-text-color, #1f2937);
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
/* Count chip shown when at maxCount */
|
|
2257
|
+
.fb-tile-counter {
|
|
2258
|
+
font-size: 11px;
|
|
2259
|
+
color: var(--fb-text-secondary-color, #6b7280);
|
|
2260
|
+
background: var(--fb-file-upload-bg-color, #f3f4f6);
|
|
2261
|
+
border: 1px solid var(--fb-file-upload-border-color, #d1d5db);
|
|
2262
|
+
border-radius: 4px;
|
|
2263
|
+
padding: 2px 6px;
|
|
2264
|
+
align-self: flex-end;
|
|
2265
|
+
margin-bottom: 4px;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
/* Empty-state dropzone */
|
|
2269
|
+
.fb-file-dropzone {
|
|
2270
|
+
width: 100%;
|
|
2271
|
+
height: 128px;
|
|
2272
|
+
border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
|
|
2273
|
+
border-radius: var(--fb-border-radius, 0.5rem);
|
|
2274
|
+
display: flex;
|
|
2275
|
+
flex-direction: column;
|
|
2276
|
+
align-items: center;
|
|
2277
|
+
justify-content: center;
|
|
2278
|
+
gap: 4px;
|
|
2279
|
+
cursor: pointer;
|
|
2280
|
+
transition:
|
|
2281
|
+
border-color var(--fb-transition-duration, 200ms),
|
|
2282
|
+
background var(--fb-transition-duration, 200ms);
|
|
2283
|
+
}
|
|
2284
|
+
.fb-file-dropzone:hover {
|
|
2285
|
+
border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
|
|
2286
|
+
background: var(--fb-background-hover-color, #f9fafb);
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
/* Inline text inside tiles */
|
|
2290
|
+
.fb-tile-label {
|
|
2291
|
+
font-size: 9px;
|
|
2292
|
+
color: var(--fb-text-secondary-color, #6b7280);
|
|
2293
|
+
text-align: center;
|
|
2294
|
+
overflow: hidden;
|
|
2295
|
+
word-break: break-all;
|
|
2296
|
+
max-height: 28px;
|
|
2297
|
+
}
|
|
2298
|
+
.fb-tile-uploading-text {
|
|
2299
|
+
font-size: 8px;
|
|
2300
|
+
color: var(--fb-file-upload-text-color, #9ca3af);
|
|
2301
|
+
}
|
|
2302
|
+
.fb-tile-hint {
|
|
2303
|
+
font-size: 11px;
|
|
2304
|
+
color: var(--fb-file-upload-text-color, #9ca3af);
|
|
2305
|
+
margin-top: 4px;
|
|
2306
|
+
}
|
|
2307
|
+
.fb-tile-empty-text {
|
|
2308
|
+
font-size: 12px;
|
|
2309
|
+
color: var(--fb-text-secondary-color, #6b7280);
|
|
2310
|
+
padding: 4px 0;
|
|
2311
|
+
}
|
|
2312
|
+
.fb-dropzone-primary-text {
|
|
2313
|
+
font-size: 13px;
|
|
2314
|
+
color: var(--fb-text-secondary-color, #6b7280);
|
|
2315
|
+
}
|
|
2316
|
+
.fb-dropzone-hint-text {
|
|
2317
|
+
font-size: 11px;
|
|
2318
|
+
color: var(--fb-file-upload-text-color, #9ca3af);
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
/* Hover overlay + X-button on resource tiles */
|
|
2322
|
+
.fb-tile-overlay {
|
|
2323
|
+
position: absolute;
|
|
2324
|
+
inset: 0;
|
|
2325
|
+
background: transparent;
|
|
2326
|
+
transition: background var(--fb-transition-duration, 200ms);
|
|
2327
|
+
display: flex;
|
|
2328
|
+
align-items: flex-start;
|
|
2329
|
+
justify-content: flex-end;
|
|
2330
|
+
}
|
|
2331
|
+
.fb-tile-resource:hover .fb-tile-overlay {
|
|
2332
|
+
background: var(--fb-tile-hover-overlay-color, rgba(0,0,0,0.4));
|
|
2333
|
+
}
|
|
2334
|
+
.fb-tile-x-btn {
|
|
2335
|
+
margin: 3px;
|
|
2336
|
+
width: 18px;
|
|
2337
|
+
height: 18px;
|
|
2338
|
+
background: var(--fb-error-color, #ef4444);
|
|
2339
|
+
color: var(--fb-file-bg-color, #fff);
|
|
2340
|
+
border: none;
|
|
2341
|
+
border-radius: 50%;
|
|
2342
|
+
font-size: 11px;
|
|
2343
|
+
line-height: 1;
|
|
2344
|
+
cursor: pointer;
|
|
2345
|
+
display: flex;
|
|
2346
|
+
align-items: center;
|
|
2347
|
+
justify-content: center;
|
|
2348
|
+
opacity: 0;
|
|
2349
|
+
transition: opacity var(--fb-transition-duration, 200ms);
|
|
2350
|
+
}
|
|
2351
|
+
.fb-tile-resource:hover .fb-tile-x-btn {
|
|
2352
|
+
opacity: 1;
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
/* Video play button overlay (readonly tiles with video thumbnails) */
|
|
2356
|
+
.fb-video-overlay {
|
|
2357
|
+
position: absolute;
|
|
2358
|
+
inset: 0;
|
|
2359
|
+
display: flex;
|
|
2360
|
+
align-items: center;
|
|
2361
|
+
justify-content: center;
|
|
2362
|
+
background: var(--fb-tile-hover-overlay-color, rgba(0,0,0,0.25));
|
|
2363
|
+
}
|
|
2364
|
+
.fb-play-btn {
|
|
2365
|
+
background: var(--fb-file-bg-color, rgba(255,255,255,0.9));
|
|
2366
|
+
border-radius: 50%;
|
|
2367
|
+
display: flex;
|
|
2368
|
+
align-items: center;
|
|
2369
|
+
justify-content: center;
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
/* Edit-mode local video preview wrapper */
|
|
2373
|
+
.fb-video-preview-wrap {
|
|
2374
|
+
position: relative;
|
|
2375
|
+
width: 100%;
|
|
2376
|
+
height: 100%;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
/* Hover overlay for edit-mode local video (Remove / Change buttons) */
|
|
2380
|
+
.fb-video-btn-overlay {
|
|
2381
|
+
position: absolute;
|
|
2382
|
+
top: 8px;
|
|
2383
|
+
right: 8px;
|
|
2384
|
+
z-index: 10;
|
|
2385
|
+
display: flex;
|
|
2386
|
+
gap: 4px;
|
|
2387
|
+
opacity: 0;
|
|
2388
|
+
transition: opacity var(--fb-transition-duration, 200ms);
|
|
2389
|
+
pointer-events: none;
|
|
2390
|
+
}
|
|
2391
|
+
.fb-video-preview-wrap:hover .fb-video-btn-overlay {
|
|
2392
|
+
opacity: 1;
|
|
2393
|
+
pointer-events: auto;
|
|
2394
|
+
}
|
|
2395
|
+
.fb-video-btn {
|
|
2396
|
+
border: none;
|
|
2397
|
+
border-radius: var(--fb-border-radius, 4px);
|
|
2398
|
+
font-size: 11px;
|
|
2399
|
+
padding: 4px 8px;
|
|
2400
|
+
cursor: pointer;
|
|
2401
|
+
color: #fff;
|
|
2402
|
+
line-height: 1.2;
|
|
2403
|
+
}
|
|
2404
|
+
.fb-video-btn-delete {
|
|
2405
|
+
background: rgba(220, 38, 38, 0.85);
|
|
2406
|
+
}
|
|
2407
|
+
.fb-video-btn-delete:hover {
|
|
2408
|
+
background: rgba(185, 28, 28, 0.95);
|
|
2409
|
+
}
|
|
2410
|
+
.fb-video-btn-change {
|
|
2411
|
+
background: rgba(31, 41, 55, 0.85);
|
|
2412
|
+
}
|
|
2413
|
+
.fb-video-btn-change:hover {
|
|
2414
|
+
background: rgba(17, 24, 39, 0.95);
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
/* Tile action icon buttons (download / open / remove) \u2014 shown on tile hover */
|
|
2418
|
+
.fb-tile-actions {
|
|
2419
|
+
position: absolute;
|
|
2420
|
+
top: 3px;
|
|
2421
|
+
right: 3px;
|
|
2422
|
+
display: flex;
|
|
2423
|
+
flex-direction: row;
|
|
2424
|
+
gap: 3px;
|
|
2425
|
+
opacity: 0;
|
|
2426
|
+
transition: opacity var(--fb-transition-duration, 200ms);
|
|
2427
|
+
z-index: 10;
|
|
2428
|
+
}
|
|
2429
|
+
.fb-tile-resource:hover .fb-tile-actions {
|
|
2430
|
+
opacity: 1;
|
|
2431
|
+
}
|
|
2432
|
+
.fb-tile-action-btn {
|
|
2433
|
+
width: 28px;
|
|
2434
|
+
height: 28px;
|
|
2435
|
+
display: flex;
|
|
2436
|
+
align-items: center;
|
|
2437
|
+
justify-content: center;
|
|
2438
|
+
border: none;
|
|
2439
|
+
border-radius: 50%;
|
|
2440
|
+
cursor: pointer;
|
|
2441
|
+
background: rgba(31, 41, 55, 0.75);
|
|
2442
|
+
color: #fff;
|
|
2443
|
+
padding: 0;
|
|
2444
|
+
flex-shrink: 0;
|
|
2445
|
+
transition:
|
|
2446
|
+
background var(--fb-transition-duration, 200ms),
|
|
2447
|
+
opacity var(--fb-transition-duration, 200ms);
|
|
2448
|
+
}
|
|
2449
|
+
.fb-tile-action-btn:hover {
|
|
2450
|
+
background: rgba(17, 24, 39, 0.95);
|
|
2451
|
+
}
|
|
2452
|
+
.fb-tile-action-remove {
|
|
2453
|
+
background: rgba(220, 38, 38, 0.8);
|
|
2454
|
+
}
|
|
2455
|
+
.fb-tile-action-remove:hover {
|
|
2456
|
+
background: rgba(185, 28, 28, 0.95);
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
/* Actions row inside zoom popup \u2014 always visible while popup is shown */
|
|
2460
|
+
.fb-tile-zoom-preview .fb-tile-actions {
|
|
2461
|
+
position: absolute;
|
|
2462
|
+
top: 6px;
|
|
2463
|
+
right: 6px;
|
|
2464
|
+
opacity: 1;
|
|
2465
|
+
z-index: 10000;
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
/* Two-card empty-state layout (upload card + library card) */
|
|
2469
|
+
.fb-file-card-row {
|
|
2470
|
+
display: flex;
|
|
2471
|
+
gap: 8px;
|
|
2472
|
+
align-items: stretch;
|
|
2473
|
+
}
|
|
2474
|
+
.fb-file-card-row .fb-file-dropzone,
|
|
2475
|
+
.fb-file-card-row .fb-file-library-card {
|
|
2476
|
+
flex: 1;
|
|
2477
|
+
min-width: 0;
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
/* Library picker card \u2014 mirrors .fb-file-dropzone styling */
|
|
2481
|
+
.fb-file-library-card {
|
|
2482
|
+
height: 128px;
|
|
2483
|
+
border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
|
|
2484
|
+
border-radius: var(--fb-border-radius, 0.5rem);
|
|
2485
|
+
display: flex;
|
|
2486
|
+
flex-direction: column;
|
|
2487
|
+
align-items: center;
|
|
2488
|
+
justify-content: center;
|
|
2489
|
+
gap: 4px;
|
|
2490
|
+
cursor: pointer;
|
|
2491
|
+
background: none;
|
|
2492
|
+
padding: 0;
|
|
2493
|
+
transition:
|
|
2494
|
+
border-color var(--fb-transition-duration, 200ms),
|
|
2495
|
+
background var(--fb-transition-duration, 200ms);
|
|
2496
|
+
width: 100%;
|
|
2497
|
+
}
|
|
2498
|
+
.fb-file-library-card:hover,
|
|
2499
|
+
.fb-file-library-card:focus-visible {
|
|
2500
|
+
border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
|
|
2501
|
+
background: var(--fb-background-hover-color, #f9fafb);
|
|
2502
|
+
outline: none;
|
|
2503
|
+
}
|
|
2504
|
+
.fb-file-library-card-icon {
|
|
2505
|
+
font-size: 24px;
|
|
2506
|
+
line-height: 1;
|
|
2507
|
+
flex-shrink: 0;
|
|
2508
|
+
}
|
|
2509
|
+
.fb-file-library-card-label {
|
|
2510
|
+
font-size: 13px;
|
|
2511
|
+
color: var(--fb-text-secondary-color, #6b7280);
|
|
2512
|
+
}
|
|
2513
|
+
.fb-file-library-card-hint {
|
|
2514
|
+
font-size: 11px;
|
|
2515
|
+
color: var(--fb-file-upload-text-color, #9ca3af);
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
/* Library "\u{1F4DA}" add-tile \u2014 same size/style as the "+" add tile */
|
|
2519
|
+
.fb-tile-add-library {
|
|
2520
|
+
border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
|
|
2521
|
+
display: flex;
|
|
2522
|
+
align-items: center;
|
|
2523
|
+
justify-content: center;
|
|
2524
|
+
cursor: pointer;
|
|
2525
|
+
font-size: 24px;
|
|
2526
|
+
color: var(--fb-file-upload-text-color, #9ca3af);
|
|
2527
|
+
transition:
|
|
2528
|
+
border-color var(--fb-transition-duration, 200ms),
|
|
2529
|
+
color var(--fb-transition-duration, 200ms);
|
|
2530
|
+
background: none;
|
|
2531
|
+
padding: 0;
|
|
2532
|
+
width: var(--fb-tile-size, 160px);
|
|
2533
|
+
height: var(--fb-tile-size, 160px);
|
|
2534
|
+
flex-shrink: 0;
|
|
2535
|
+
position: relative;
|
|
2536
|
+
overflow: hidden;
|
|
2537
|
+
border-radius: var(--fb-border-radius, 0.5rem);
|
|
2538
|
+
}
|
|
2539
|
+
.fb-tile-add-library:hover,
|
|
2540
|
+
.fb-tile-add-library:focus-visible {
|
|
2541
|
+
border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
|
|
2542
|
+
color: var(--fb-text-color, #1f2937);
|
|
2543
|
+
outline: none;
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
/* Hover zoom preview popup for image tiles \u2014 appended to document.body (fixed) */
|
|
2547
|
+
.fb-tile-zoom-preview {
|
|
2548
|
+
position: fixed;
|
|
2549
|
+
z-index: 9999;
|
|
2550
|
+
background: var(--fb-background-color, #fff);
|
|
2551
|
+
border: 1px solid var(--fb-file-upload-border-color, #d1d5db);
|
|
2552
|
+
border-radius: var(--fb-border-radius, 0.5rem);
|
|
2553
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.18);
|
|
2554
|
+
padding: 4px;
|
|
2555
|
+
width: 350px;
|
|
2556
|
+
height: 350px;
|
|
2557
|
+
pointer-events: none;
|
|
2558
|
+
opacity: 0;
|
|
2559
|
+
transition: opacity 150ms ease;
|
|
2560
|
+
}
|
|
2561
|
+
.fb-tile-zoom-preview.fb-tile-zoom-preview--visible {
|
|
2562
|
+
opacity: 1;
|
|
2563
|
+
}
|
|
2564
|
+
.fb-tile-zoom-preview-img {
|
|
2565
|
+
width: 100%;
|
|
2566
|
+
height: 100%;
|
|
2567
|
+
object-fit: contain;
|
|
2568
|
+
display: block;
|
|
2569
|
+
background: var(--fb-file-upload-bg-color, #f3f4f6);
|
|
2570
|
+
border-radius: calc(var(--fb-border-radius, 0.5rem) - 2px);
|
|
2571
|
+
}
|
|
2572
|
+
`;
|
|
2573
|
+
document.head.appendChild(style);
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
// src/components/file/dom.ts
|
|
2577
|
+
var TILE_SIZE = "160px";
|
|
2578
|
+
function createFileTile() {
|
|
2579
|
+
ensureFileStyles();
|
|
2580
|
+
const tile = document.createElement("div");
|
|
2581
|
+
tile.className = "fb-tile";
|
|
2582
|
+
return tile;
|
|
2583
|
+
}
|
|
2584
|
+
function showFileError(container, message) {
|
|
2585
|
+
var _a, _b;
|
|
2586
|
+
const existing = (_a = container.closest(".space-y-2")) == null ? void 0 : _a.querySelector(".file-error-message");
|
|
2587
|
+
if (existing) existing.remove();
|
|
2588
|
+
const errorEl = document.createElement("div");
|
|
2589
|
+
errorEl.className = "file-error-message error-message";
|
|
2590
|
+
errorEl.style.cssText = `
|
|
2591
|
+
color: var(--fb-error-color);
|
|
2592
|
+
font-size: var(--fb-font-size-small);
|
|
2593
|
+
margin-top: 0.25rem;
|
|
2594
|
+
`;
|
|
2595
|
+
errorEl.textContent = message;
|
|
2596
|
+
(_b = container.closest(".space-y-2")) == null ? void 0 : _b.appendChild(errorEl);
|
|
2597
|
+
}
|
|
2598
|
+
function clearFileError(container) {
|
|
2599
|
+
var _a;
|
|
2600
|
+
const existing = (_a = container.closest(".space-y-2")) == null ? void 0 : _a.querySelector(".file-error-message");
|
|
2601
|
+
if (existing) existing.remove();
|
|
2602
|
+
}
|
|
2603
|
+
function addDeleteButton(container, state, onDelete) {
|
|
2604
|
+
const existingOverlay = container.querySelector(".delete-overlay");
|
|
2605
|
+
if (existingOverlay) existingOverlay.remove();
|
|
2606
|
+
const overlay = document.createElement("div");
|
|
2607
|
+
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";
|
|
2608
|
+
const deleteBtn = document.createElement("button");
|
|
2609
|
+
deleteBtn.className = "bg-red-600 text-white px-3 py-1 rounded text-sm hover:bg-red-700 transition-colors";
|
|
2610
|
+
deleteBtn.textContent = t("removeElement", state);
|
|
2611
|
+
deleteBtn.onclick = (e) => {
|
|
2612
|
+
e.stopPropagation();
|
|
2613
|
+
onDelete();
|
|
2614
|
+
};
|
|
2615
|
+
overlay.appendChild(deleteBtn);
|
|
2616
|
+
container.appendChild(overlay);
|
|
2617
|
+
}
|
|
2618
|
+
function findFilePicker(container) {
|
|
2619
|
+
var _a;
|
|
2620
|
+
let el = container.parentElement;
|
|
2621
|
+
while (el && !el.dataset.filesWrapper) {
|
|
2622
|
+
el = el.parentElement;
|
|
2623
|
+
}
|
|
2624
|
+
return (_a = el == null ? void 0 : el.querySelector('input[type="file"]')) != null ? _a : null;
|
|
2625
|
+
}
|
|
2626
|
+
function createUploadingTile(fileName, state) {
|
|
2627
|
+
ensureFileStyles();
|
|
2628
|
+
const tile = createFileTile();
|
|
2629
|
+
tile.classList.add("fb-tile-uploading");
|
|
2630
|
+
tile.className += " fb-uploading-tile";
|
|
2631
|
+
const label = fileName.length > 10 ? fileName.substring(0, 8) + "\u2026" : fileName;
|
|
2632
|
+
tile.innerHTML = `
|
|
2633
|
+
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:6px;padding:4px;">
|
|
2634
|
+
<div class="fb-spinner"></div>
|
|
2635
|
+
<div class="fb-tile-label">${escapeHtml(label)}</div>
|
|
2636
|
+
<div class="fb-tile-uploading-text">${escapeHtml(t("uploadingFile", state))}</div>
|
|
2637
|
+
</div>`;
|
|
2638
|
+
return tile;
|
|
2639
|
+
}
|
|
2640
|
+
function ensureTilesWrap(list) {
|
|
2641
|
+
const existing = list.querySelector(".fb-tiles-wrap");
|
|
2642
|
+
if (existing) return existing;
|
|
2643
|
+
const dropzone = list.querySelector(".fb-file-dropzone");
|
|
2644
|
+
if (dropzone) dropzone.remove();
|
|
2645
|
+
const tilesWrap = document.createElement("div");
|
|
2646
|
+
tilesWrap.className = "fb-tiles-wrap";
|
|
2647
|
+
tilesWrap.style.cssText = "display:flex;flex-wrap:wrap;gap:6px;align-items:flex-start;";
|
|
2648
|
+
const addTile = document.createElement("div");
|
|
2649
|
+
addTile.className = "fb-tile fb-tile-add";
|
|
2650
|
+
addTile.innerHTML = "+";
|
|
2651
|
+
tilesWrap.appendChild(addTile);
|
|
2652
|
+
list.appendChild(tilesWrap);
|
|
2653
|
+
return tilesWrap;
|
|
2654
|
+
}
|
|
2655
|
+
function setEmptyFileContainer(fileContainer, state, hint) {
|
|
2656
|
+
const hintHtml = hint ? `<div class="text-xs text-gray-500 mt-1">${escapeHtml(hint)}</div>` : "";
|
|
2657
|
+
fileContainer.innerHTML = `
|
|
2658
|
+
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
2659
|
+
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
2660
|
+
<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"/>
|
|
2661
|
+
</svg>
|
|
2662
|
+
<div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
|
|
2663
|
+
${hintHtml}
|
|
2664
|
+
</div>
|
|
2665
|
+
`;
|
|
2666
|
+
}
|
|
2667
|
+
function setupDragAndDrop(element, dropHandler) {
|
|
2668
|
+
element.addEventListener("dragover", (e) => {
|
|
2669
|
+
e.preventDefault();
|
|
2670
|
+
element.classList.add("border-blue-500", "bg-blue-50");
|
|
2671
|
+
});
|
|
2672
|
+
element.addEventListener("dragleave", (e) => {
|
|
2673
|
+
e.preventDefault();
|
|
2674
|
+
element.classList.remove("border-blue-500", "bg-blue-50");
|
|
2675
|
+
});
|
|
2676
|
+
element.addEventListener("drop", (e) => {
|
|
2677
|
+
var _a;
|
|
2678
|
+
e.preventDefault();
|
|
2679
|
+
element.classList.remove("border-blue-500", "bg-blue-50");
|
|
2680
|
+
if ((_a = e.dataTransfer) == null ? void 0 : _a.files) {
|
|
2681
|
+
dropHandler(e.dataTransfer.files);
|
|
2682
|
+
}
|
|
2683
|
+
});
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
// src/components/file/preview.ts
|
|
2687
|
+
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>`;
|
|
2688
|
+
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>`;
|
|
2689
|
+
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>`;
|
|
2690
|
+
function canDownload(state, meta) {
|
|
2691
|
+
return Boolean(
|
|
2692
|
+
state.config.downloadFile || state.config.getDownloadUrl || state.config.getThumbnail || (meta == null ? void 0 : meta.file)
|
|
2693
|
+
);
|
|
2694
|
+
}
|
|
2695
|
+
function canOpenInTab(state, meta) {
|
|
2696
|
+
return Boolean(
|
|
2697
|
+
state.config.getDownloadUrl || state.config.getThumbnail || (meta == null ? void 0 : meta.file)
|
|
2698
|
+
);
|
|
2699
|
+
}
|
|
2700
|
+
function createTileActions(options) {
|
|
2701
|
+
const { canRemove, removeHandler, state, resourceId, fileName, meta } = options;
|
|
2702
|
+
const group = document.createElement("div");
|
|
2703
|
+
group.className = "fb-tile-actions";
|
|
2704
|
+
const makeBtn = (icon, label, cls) => {
|
|
2705
|
+
const btn = document.createElement("button");
|
|
2706
|
+
btn.type = "button";
|
|
2707
|
+
btn.className = `fb-tile-action-btn ${cls}`;
|
|
2708
|
+
btn.innerHTML = icon;
|
|
2709
|
+
btn.title = label;
|
|
2710
|
+
btn.setAttribute("aria-label", label);
|
|
2711
|
+
btn.addEventListener("click", (e) => {
|
|
2712
|
+
e.stopPropagation();
|
|
2713
|
+
});
|
|
2714
|
+
return btn;
|
|
2715
|
+
};
|
|
2716
|
+
if (canDownload(state, meta)) {
|
|
2717
|
+
const dlBtn = makeBtn(ICON_DOWNLOAD, t("downloadFile", state), "fb-tile-action-download");
|
|
2718
|
+
dlBtn.addEventListener("click", () => {
|
|
2719
|
+
triggerTileDownload(resourceId, fileName, state, meta);
|
|
2720
|
+
});
|
|
2721
|
+
group.appendChild(dlBtn);
|
|
2722
|
+
}
|
|
2723
|
+
if (canOpenInTab(state, meta)) {
|
|
2724
|
+
const openBtn = makeBtn(ICON_OPEN, t("openInNewTab", state), "fb-tile-action-open");
|
|
2725
|
+
openBtn.addEventListener("click", () => {
|
|
2726
|
+
triggerTileOpen(resourceId, state, meta).catch((err) => {
|
|
2727
|
+
console.error("Open failed:", err);
|
|
2728
|
+
});
|
|
2729
|
+
});
|
|
2730
|
+
group.appendChild(openBtn);
|
|
2731
|
+
}
|
|
2732
|
+
if (canRemove && removeHandler) {
|
|
2733
|
+
const rmBtn = makeBtn(ICON_REMOVE, t("removeElement", state), "fb-tile-action-remove");
|
|
2734
|
+
rmBtn.addEventListener("click", () => {
|
|
2735
|
+
removeHandler();
|
|
2736
|
+
});
|
|
2737
|
+
group.appendChild(rmBtn);
|
|
2738
|
+
}
|
|
2739
|
+
return group;
|
|
2740
|
+
}
|
|
2741
|
+
var localFileUrlCache = /* @__PURE__ */ new WeakMap();
|
|
2742
|
+
function getLocalFileUrl(file) {
|
|
2743
|
+
let url = localFileUrlCache.get(file);
|
|
2744
|
+
if (!url) {
|
|
2745
|
+
url = URL.createObjectURL(file);
|
|
2746
|
+
localFileUrlCache.set(file, url);
|
|
2747
|
+
}
|
|
2748
|
+
return url;
|
|
2749
|
+
}
|
|
2750
|
+
function releaseLocalFileUrl(file) {
|
|
2751
|
+
if (!file) return;
|
|
2752
|
+
const url = localFileUrlCache.get(file);
|
|
2753
|
+
if (url) {
|
|
2754
|
+
URL.revokeObjectURL(url);
|
|
2755
|
+
localFileUrlCache.delete(file);
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
function triggerTileDownload(resourceId, fileName, state, meta) {
|
|
2759
|
+
if (state.config.downloadFile) {
|
|
2760
|
+
state.config.downloadFile(resourceId, fileName);
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
if ((meta == null ? void 0 : meta.file) instanceof File) {
|
|
2764
|
+
downloadBlob(meta.file, fileName || meta.file.name);
|
|
2765
|
+
return;
|
|
2766
|
+
}
|
|
2767
|
+
forceDownload(resourceId, fileName, state).catch((err) => {
|
|
2768
|
+
console.error("Download failed:", err);
|
|
2769
|
+
});
|
|
2770
|
+
}
|
|
2771
|
+
async function triggerTileOpen(resourceId, state, meta) {
|
|
2772
|
+
let url = null;
|
|
2773
|
+
if (state.config.getDownloadUrl) {
|
|
2774
|
+
url = state.config.getDownloadUrl(resourceId);
|
|
2775
|
+
} else if (state.config.getThumbnail) {
|
|
2776
|
+
url = await state.config.getThumbnail(resourceId);
|
|
2777
|
+
} else if ((meta == null ? void 0 : meta.file) instanceof File) {
|
|
2778
|
+
url = getLocalFileUrl(meta.file);
|
|
2779
|
+
}
|
|
2780
|
+
if (url) {
|
|
2781
|
+
window.open(url, "_blank");
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
var sharedZoomPopup = null;
|
|
2785
|
+
var zoomTimer = null;
|
|
2786
|
+
var zoomHideTimer = null;
|
|
2787
|
+
var zoomOwner = null;
|
|
2788
|
+
function getOrCreateZoomPopup() {
|
|
2789
|
+
if (!sharedZoomPopup) {
|
|
2790
|
+
sharedZoomPopup = document.createElement("div");
|
|
2791
|
+
sharedZoomPopup.className = "fb-tile-zoom-preview";
|
|
2792
|
+
const img = document.createElement("img");
|
|
2793
|
+
img.className = "fb-tile-zoom-preview-img";
|
|
2794
|
+
sharedZoomPopup.appendChild(img);
|
|
2795
|
+
sharedZoomPopup.addEventListener("mouseenter", cancelHideZoomPopup);
|
|
2796
|
+
sharedZoomPopup.addEventListener("mouseleave", scheduleHideZoomPopup);
|
|
2797
|
+
}
|
|
2798
|
+
return sharedZoomPopup;
|
|
2799
|
+
}
|
|
2800
|
+
function positionZoomPopup(popup, tile) {
|
|
2801
|
+
const tileRect = tile.getBoundingClientRect();
|
|
2802
|
+
const popupSize = 350;
|
|
2803
|
+
const margin = 6;
|
|
2804
|
+
const padding = 8;
|
|
2805
|
+
let top;
|
|
2806
|
+
if (tileRect.top - popupSize - margin >= padding) {
|
|
2807
|
+
top = tileRect.top - popupSize - margin;
|
|
2808
|
+
} else if (tileRect.bottom + margin + popupSize + padding <= window.innerHeight) {
|
|
2809
|
+
top = tileRect.bottom + margin;
|
|
2810
|
+
} else {
|
|
2811
|
+
top = Math.max(padding, Math.min(window.innerHeight - popupSize - padding, tileRect.top));
|
|
2812
|
+
}
|
|
2813
|
+
const tileCenterX = tileRect.left + tileRect.width / 2;
|
|
2814
|
+
let left = tileCenterX - popupSize / 2;
|
|
2815
|
+
left = Math.max(padding, Math.min(window.innerWidth - popupSize - padding, left));
|
|
2816
|
+
popup.style.top = `${top}px`;
|
|
2817
|
+
popup.style.left = `${left}px`;
|
|
2818
|
+
}
|
|
2819
|
+
function scheduleHideZoomPopup() {
|
|
2820
|
+
if (zoomHideTimer !== null) {
|
|
2821
|
+
clearTimeout(zoomHideTimer);
|
|
2822
|
+
}
|
|
2823
|
+
zoomHideTimer = setTimeout(() => {
|
|
2824
|
+
zoomHideTimer = null;
|
|
2825
|
+
removeZoomPopupNow();
|
|
2826
|
+
}, 100);
|
|
2827
|
+
}
|
|
2828
|
+
function cancelHideZoomPopup() {
|
|
2829
|
+
if (zoomHideTimer !== null) {
|
|
2830
|
+
clearTimeout(zoomHideTimer);
|
|
2831
|
+
zoomHideTimer = null;
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
function removeZoomPopupNow() {
|
|
2835
|
+
if (zoomTimer !== null) {
|
|
2836
|
+
clearTimeout(zoomTimer);
|
|
2837
|
+
zoomTimer = null;
|
|
2838
|
+
}
|
|
2839
|
+
if (sharedZoomPopup && sharedZoomPopup.parentNode) {
|
|
2840
|
+
sharedZoomPopup.classList.remove("fb-tile-zoom-preview--visible");
|
|
2841
|
+
sharedZoomPopup.parentNode.removeChild(sharedZoomPopup);
|
|
2842
|
+
}
|
|
2843
|
+
zoomOwner = null;
|
|
2844
|
+
}
|
|
2845
|
+
function attachZoomHover(tile, src, alt, actionsEl) {
|
|
2846
|
+
tile.dataset.zoomSrc = src;
|
|
2847
|
+
tile.dataset.zoomAlt = alt;
|
|
2848
|
+
tile.addEventListener("mouseenter", () => {
|
|
2849
|
+
cancelHideZoomPopup();
|
|
2850
|
+
if (zoomOwner !== tile) {
|
|
2851
|
+
removeZoomPopupNow();
|
|
2852
|
+
}
|
|
2853
|
+
zoomOwner = tile;
|
|
2854
|
+
zoomTimer = setTimeout(() => {
|
|
2855
|
+
zoomTimer = null;
|
|
2856
|
+
const popup = getOrCreateZoomPopup();
|
|
2857
|
+
const existingActions = popup.querySelector(".fb-tile-actions");
|
|
2858
|
+
if (existingActions) existingActions.remove();
|
|
2859
|
+
const img = popup.querySelector(".fb-tile-zoom-preview-img");
|
|
2860
|
+
img.src = src;
|
|
2861
|
+
img.alt = alt;
|
|
2862
|
+
if (actionsEl) {
|
|
2863
|
+
popup.appendChild(actionsEl.cloneNode(true));
|
|
2864
|
+
attachClonedActionListeners(
|
|
2865
|
+
popup.querySelector(".fb-tile-actions"),
|
|
2866
|
+
actionsEl
|
|
2867
|
+
);
|
|
2868
|
+
}
|
|
2869
|
+
popup.style.pointerEvents = "auto";
|
|
2870
|
+
positionZoomPopup(popup, tile);
|
|
2871
|
+
document.body.appendChild(popup);
|
|
2872
|
+
popup.getBoundingClientRect();
|
|
2873
|
+
popup.classList.add("fb-tile-zoom-preview--visible");
|
|
2874
|
+
}, 200);
|
|
2875
|
+
});
|
|
2876
|
+
tile.addEventListener("mouseleave", () => {
|
|
2877
|
+
if (zoomTimer !== null) {
|
|
2878
|
+
clearTimeout(zoomTimer);
|
|
2879
|
+
zoomTimer = null;
|
|
2880
|
+
zoomOwner = null;
|
|
2881
|
+
} else {
|
|
2882
|
+
scheduleHideZoomPopup();
|
|
2883
|
+
}
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
function attachClonedActionListeners(cloned, original) {
|
|
2887
|
+
const originalBtns = Array.from(original.querySelectorAll(".fb-tile-action-btn"));
|
|
2888
|
+
const clonedBtns = Array.from(cloned.querySelectorAll(".fb-tile-action-btn"));
|
|
2889
|
+
clonedBtns.forEach((clonedBtn, i) => {
|
|
2890
|
+
const origBtn = originalBtns[i];
|
|
2891
|
+
if (origBtn) {
|
|
2892
|
+
clonedBtn.addEventListener("click", (e) => {
|
|
2893
|
+
e.stopPropagation();
|
|
2894
|
+
origBtn.click();
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
});
|
|
2142
2898
|
}
|
|
2143
2899
|
function renderLocalImagePreview(container, file, fileName, state) {
|
|
2144
2900
|
const img = document.createElement("img");
|
|
2145
2901
|
img.className = "w-full h-full object-contain";
|
|
2902
|
+
img.style.background = "var(--fb-file-upload-bg-color,#f3f4f6)";
|
|
2146
2903
|
img.alt = fileName || t("previewAlt", state);
|
|
2147
2904
|
const reader = new FileReader();
|
|
2148
2905
|
reader.onload = (e) => {
|
|
@@ -2152,23 +2909,27 @@ function renderLocalImagePreview(container, file, fileName, state) {
|
|
|
2152
2909
|
reader.readAsDataURL(file);
|
|
2153
2910
|
container.appendChild(img);
|
|
2154
2911
|
}
|
|
2155
|
-
function
|
|
2156
|
-
const videoUrl = URL.createObjectURL(file);
|
|
2912
|
+
function setupDragDropless(container, _deps) {
|
|
2157
2913
|
container.onclick = null;
|
|
2158
2914
|
const newContainer = container.cloneNode(false);
|
|
2159
2915
|
if (container.parentNode) {
|
|
2160
2916
|
container.parentNode.replaceChild(newContainer, container);
|
|
2161
2917
|
}
|
|
2918
|
+
return newContainer;
|
|
2919
|
+
}
|
|
2920
|
+
function renderLocalVideoPreview(container, file, videoType, resourceId, state, deps) {
|
|
2921
|
+
const videoUrl = URL.createObjectURL(file);
|
|
2922
|
+
const newContainer = setupDragDropless(container);
|
|
2162
2923
|
newContainer.innerHTML = `
|
|
2163
|
-
<div class="
|
|
2924
|
+
<div class="fb-video-preview-wrap">
|
|
2164
2925
|
<video class="w-full h-full object-contain" controls preload="auto" muted src="${videoUrl}">
|
|
2165
2926
|
${escapeHtml(t("videoNotSupported", state))}
|
|
2166
2927
|
</video>
|
|
2167
|
-
<div class="
|
|
2168
|
-
<button class="
|
|
2928
|
+
<div class="fb-video-btn-overlay">
|
|
2929
|
+
<button class="fb-video-btn fb-video-btn-delete delete-file-btn">
|
|
2169
2930
|
${escapeHtml(t("removeElement", state))}
|
|
2170
2931
|
</button>
|
|
2171
|
-
<button class="
|
|
2932
|
+
<button class="fb-video-btn fb-video-btn-change change-file-btn">
|
|
2172
2933
|
${escapeHtml(t("changeButton", state))}
|
|
2173
2934
|
</button>
|
|
2174
2935
|
</div>
|
|
@@ -2178,20 +2939,15 @@ function renderLocalVideoPreview(container, file, videoType, resourceId, state,
|
|
|
2178
2939
|
return newContainer;
|
|
2179
2940
|
}
|
|
2180
2941
|
function attachVideoButtonHandlers(container, resourceId, state, deps) {
|
|
2181
|
-
const changeBtn = container.querySelector(
|
|
2182
|
-
".change-file-btn"
|
|
2183
|
-
);
|
|
2942
|
+
const changeBtn = container.querySelector(".change-file-btn");
|
|
2184
2943
|
if (changeBtn) {
|
|
2185
2944
|
changeBtn.onclick = (e) => {
|
|
2945
|
+
var _a;
|
|
2186
2946
|
e.stopPropagation();
|
|
2187
|
-
|
|
2188
|
-
deps.picker.click();
|
|
2189
|
-
}
|
|
2947
|
+
(_a = deps == null ? void 0 : deps.picker) == null ? void 0 : _a.click();
|
|
2190
2948
|
};
|
|
2191
2949
|
}
|
|
2192
|
-
const deleteBtn = container.querySelector(
|
|
2193
|
-
".delete-file-btn"
|
|
2194
|
-
);
|
|
2950
|
+
const deleteBtn = container.querySelector(".delete-file-btn");
|
|
2195
2951
|
if (deleteBtn) {
|
|
2196
2952
|
deleteBtn.onclick = (e) => {
|
|
2197
2953
|
e.stopPropagation();
|
|
@@ -2211,9 +2967,6 @@ function handleVideoDelete(container, resourceId, state, deps) {
|
|
|
2211
2967
|
if (deps == null ? void 0 : deps.fileUploadHandler) {
|
|
2212
2968
|
container.onclick = deps.fileUploadHandler;
|
|
2213
2969
|
}
|
|
2214
|
-
if (deps == null ? void 0 : deps.dragHandler) {
|
|
2215
|
-
setupDragAndDrop(container, deps.dragHandler);
|
|
2216
|
-
}
|
|
2217
2970
|
container.innerHTML = `
|
|
2218
2971
|
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
2219
2972
|
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
@@ -2222,16 +2975,9 @@ function handleVideoDelete(container, resourceId, state, deps) {
|
|
|
2222
2975
|
<div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
|
|
2223
2976
|
</div>
|
|
2224
2977
|
`;
|
|
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);
|
|
2978
|
+
if (deps == null ? void 0 : deps.setupDrop) {
|
|
2979
|
+
deps.setupDrop(container);
|
|
2980
|
+
}
|
|
2235
2981
|
}
|
|
2236
2982
|
function renderDeleteButton(container, resourceId, state) {
|
|
2237
2983
|
addDeleteButton(container, state, () => {
|
|
@@ -2254,13 +3000,12 @@ function renderDeleteButton(container, resourceId, state) {
|
|
|
2254
3000
|
});
|
|
2255
3001
|
}
|
|
2256
3002
|
async function renderLocalFilePreview(container, meta, fileName, resourceId, isReadonly, state, deps) {
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
if (meta.type && meta.type.startsWith("image/")) {
|
|
3003
|
+
var _a, _b, _c;
|
|
3004
|
+
if (!meta.file || !(meta.file instanceof File)) return;
|
|
3005
|
+
if ((_a = meta.type) == null ? void 0 : _a.startsWith("image/")) {
|
|
2261
3006
|
renderLocalImagePreview(container, meta.file, fileName, state);
|
|
2262
|
-
} else if (meta.type
|
|
2263
|
-
|
|
3007
|
+
} else if ((_b = meta.type) == null ? void 0 : _b.startsWith("video/")) {
|
|
3008
|
+
container = renderLocalVideoPreview(
|
|
2264
3009
|
container,
|
|
2265
3010
|
meta.file,
|
|
2266
3011
|
meta.type,
|
|
@@ -2268,15 +3013,25 @@ async function renderLocalFilePreview(container, meta, fileName, resourceId, isR
|
|
|
2268
3013
|
state,
|
|
2269
3014
|
deps
|
|
2270
3015
|
);
|
|
2271
|
-
container = newContainer;
|
|
2272
3016
|
} else {
|
|
2273
|
-
container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div
|
|
3017
|
+
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
3018
|
}
|
|
2275
|
-
if (!isReadonly && !(meta.type
|
|
3019
|
+
if (!isReadonly && !((_c = meta.type) == null ? void 0 : _c.startsWith("video/"))) {
|
|
2276
3020
|
renderDeleteButton(container, resourceId, state);
|
|
2277
3021
|
}
|
|
2278
3022
|
}
|
|
3023
|
+
function renderUploadedVideoPreview(container, thumbnailUrl, state) {
|
|
3024
|
+
const video = document.createElement("video");
|
|
3025
|
+
video.className = "w-full h-full object-contain";
|
|
3026
|
+
video.controls = true;
|
|
3027
|
+
video.preload = "metadata";
|
|
3028
|
+
video.muted = true;
|
|
3029
|
+
video.src = thumbnailUrl;
|
|
3030
|
+
video.appendChild(document.createTextNode(t("videoNotSupported", state)));
|
|
3031
|
+
container.appendChild(video);
|
|
3032
|
+
}
|
|
2279
3033
|
async function renderUploadedFilePreview(container, resourceId, fileName, meta, state) {
|
|
3034
|
+
var _a;
|
|
2280
3035
|
if (!state.config.getThumbnail) {
|
|
2281
3036
|
setEmptyFileContainer(container, state);
|
|
2282
3037
|
return;
|
|
@@ -2285,11 +3040,12 @@ async function renderUploadedFilePreview(container, resourceId, fileName, meta,
|
|
|
2285
3040
|
const thumbnailUrl = await state.config.getThumbnail(resourceId);
|
|
2286
3041
|
if (thumbnailUrl) {
|
|
2287
3042
|
clear(container);
|
|
2288
|
-
if (meta
|
|
2289
|
-
renderUploadedVideoPreview(container, thumbnailUrl,
|
|
3043
|
+
if ((_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("video/")) {
|
|
3044
|
+
renderUploadedVideoPreview(container, thumbnailUrl, state);
|
|
2290
3045
|
} else {
|
|
2291
3046
|
const img = document.createElement("img");
|
|
2292
3047
|
img.className = "w-full h-full object-contain";
|
|
3048
|
+
img.style.background = "var(--fb-file-upload-bg-color,#f3f4f6)";
|
|
2293
3049
|
img.alt = fileName || t("previewAlt", state);
|
|
2294
3050
|
img.src = thumbnailUrl;
|
|
2295
3051
|
container.appendChild(img);
|
|
@@ -2318,9 +3074,6 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
|
|
|
2318
3074
|
);
|
|
2319
3075
|
}
|
|
2320
3076
|
clear(container);
|
|
2321
|
-
if (isReadonly) {
|
|
2322
|
-
container.classList.add("cursor-pointer");
|
|
2323
|
-
}
|
|
2324
3077
|
const meta = state.resourceIndex.get(resourceId);
|
|
2325
3078
|
if (meta && meta.file && meta.file instanceof File) {
|
|
2326
3079
|
await renderLocalFilePreview(
|
|
@@ -2333,372 +3086,310 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
|
|
|
2333
3086
|
deps
|
|
2334
3087
|
);
|
|
2335
3088
|
} else {
|
|
2336
|
-
await renderUploadedFilePreview(
|
|
2337
|
-
container,
|
|
2338
|
-
resourceId,
|
|
2339
|
-
fileName,
|
|
2340
|
-
meta,
|
|
2341
|
-
state
|
|
2342
|
-
);
|
|
3089
|
+
await renderUploadedFilePreview(container, resourceId, fileName, meta, state);
|
|
2343
3090
|
const isVideo = (_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("video/");
|
|
2344
3091
|
if (!isReadonly && !isVideo) {
|
|
2345
3092
|
renderDeleteButton(container, resourceId, state);
|
|
2346
3093
|
}
|
|
2347
3094
|
}
|
|
2348
3095
|
}
|
|
2349
|
-
|
|
3096
|
+
function resolveFileName(resourceId, meta, fileName) {
|
|
3097
|
+
var _a;
|
|
3098
|
+
if (fileName) return fileName;
|
|
3099
|
+
if ((_a = meta == null ? void 0 : meta.name) == null ? void 0 : _a.includes(".")) return meta.name;
|
|
3100
|
+
const basename = resourceId.includes("/") ? resourceId.split("/").pop() : resourceId;
|
|
3101
|
+
return (basename == null ? void 0 : basename.includes(".")) ? basename : "";
|
|
3102
|
+
}
|
|
3103
|
+
async function renderFilePreviewReadonly(resourceId, state, fileName, options = {}) {
|
|
2350
3104
|
var _a, _b;
|
|
2351
3105
|
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
|
-
|
|
3106
|
+
const actualFileName = resolveFileName(resourceId, meta, fileName);
|
|
3107
|
+
const { canRemove = false, removeHandler = null } = options;
|
|
3108
|
+
const isImage = ((_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("image/")) || Boolean(actualFileName.toLowerCase().match(/\.(jpg|jpeg|png|gif|webp)$/));
|
|
3109
|
+
const isVideo = ((_b = meta == null ? void 0 : meta.type) == null ? void 0 : _b.startsWith("video/")) || Boolean(actualFileName.toLowerCase().match(/\.(mp4|webm|avi|mov)$/));
|
|
3110
|
+
const tile = createFileTile();
|
|
3111
|
+
tile.classList.add("fb-tile-resource");
|
|
3112
|
+
tile.style.cursor = "pointer";
|
|
3113
|
+
if (actualFileName) {
|
|
3114
|
+
tile.title = actualFileName;
|
|
3115
|
+
}
|
|
3116
|
+
const localFileUrl = (meta == null ? void 0 : meta.file) instanceof File ? getLocalFileUrl(meta.file) : null;
|
|
3117
|
+
const resolveOpenUrl = async () => {
|
|
3118
|
+
if (state.config.getDownloadUrl) return state.config.getDownloadUrl(resourceId);
|
|
3119
|
+
if (state.config.getThumbnail) return state.config.getThumbnail(resourceId);
|
|
3120
|
+
return localFileUrl;
|
|
3121
|
+
};
|
|
3122
|
+
tile.onclick = async () => {
|
|
3123
|
+
const url = await resolveOpenUrl();
|
|
3124
|
+
if (url) {
|
|
3125
|
+
window.open(url, "_blank");
|
|
3126
|
+
} else if (state.config.downloadFile) {
|
|
3127
|
+
state.config.downloadFile(resourceId, actualFileName);
|
|
3128
|
+
} else {
|
|
3129
|
+
forceDownload(resourceId, actualFileName, state).catch((err) => {
|
|
3130
|
+
console.error("Download failed:", err);
|
|
3131
|
+
});
|
|
3132
|
+
}
|
|
3133
|
+
};
|
|
3134
|
+
const actionsEl = createTileActions({
|
|
3135
|
+
canRemove,
|
|
3136
|
+
removeHandler,
|
|
3137
|
+
state,
|
|
3138
|
+
resourceId,
|
|
3139
|
+
fileName: actualFileName,
|
|
3140
|
+
meta
|
|
3141
|
+
});
|
|
3142
|
+
const resolveImageDisplayUrl = async () => {
|
|
2365
3143
|
if (state.config.getThumbnail) {
|
|
2366
3144
|
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>`;
|
|
3145
|
+
const url = await state.config.getThumbnail(resourceId);
|
|
3146
|
+
if (url) return url;
|
|
3147
|
+
} catch {
|
|
2376
3148
|
}
|
|
3149
|
+
}
|
|
3150
|
+
return localFileUrl;
|
|
3151
|
+
};
|
|
3152
|
+
const renderImageFallback = () => {
|
|
3153
|
+
tile.innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F5BC}\uFE0F</div>`;
|
|
3154
|
+
tile.appendChild(actionsEl);
|
|
3155
|
+
};
|
|
3156
|
+
if (isImage) {
|
|
3157
|
+
const displayUrl = await resolveImageDisplayUrl();
|
|
3158
|
+
if (displayUrl) {
|
|
3159
|
+
const img = document.createElement("img");
|
|
3160
|
+
img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
|
|
3161
|
+
img.alt = actualFileName;
|
|
3162
|
+
img.src = displayUrl;
|
|
3163
|
+
tile.appendChild(img);
|
|
3164
|
+
tile.appendChild(actionsEl);
|
|
3165
|
+
attachZoomHover(tile, displayUrl, actualFileName, actionsEl);
|
|
2377
3166
|
} else {
|
|
2378
|
-
|
|
3167
|
+
renderImageFallback();
|
|
2379
3168
|
}
|
|
2380
3169
|
} else if (isVideo) {
|
|
2381
3170
|
if (state.config.getThumbnail) {
|
|
2382
3171
|
try {
|
|
2383
3172
|
const videoUrl = await state.config.getThumbnail(resourceId);
|
|
2384
3173
|
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>
|
|
3174
|
+
tile.innerHTML = `
|
|
3175
|
+
<img style="width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);" alt="${escapeHtml(actualFileName)}" src="${videoUrl}">
|
|
3176
|
+
<div class="fb-video-overlay">
|
|
3177
|
+
<div class="fb-play-btn" style="width:22px;height:22px;">
|
|
3178
|
+
<svg width="10" height="12" viewBox="0 0 10 12" fill="currentColor"><path d="M0 0l10 6-10 6z"/></svg>
|
|
2396
3179
|
</div>
|
|
2397
|
-
</div
|
|
2398
|
-
|
|
3180
|
+
</div>`;
|
|
3181
|
+
tile.appendChild(actionsEl);
|
|
2399
3182
|
} else {
|
|
2400
|
-
|
|
3183
|
+
tile.innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
|
|
3184
|
+
tile.appendChild(actionsEl);
|
|
2401
3185
|
}
|
|
2402
|
-
} catch
|
|
2403
|
-
|
|
2404
|
-
|
|
3186
|
+
} catch {
|
|
3187
|
+
tile.innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
|
|
3188
|
+
tile.appendChild(actionsEl);
|
|
2405
3189
|
}
|
|
2406
3190
|
} else {
|
|
2407
|
-
|
|
3191
|
+
tile.innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
|
|
3192
|
+
tile.appendChild(actionsEl);
|
|
2408
3193
|
}
|
|
2409
3194
|
} else {
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
3195
|
+
if (state.config.getThumbnail) {
|
|
3196
|
+
try {
|
|
3197
|
+
const thumbUrl = await state.config.getThumbnail(resourceId);
|
|
3198
|
+
if (thumbUrl) {
|
|
3199
|
+
const img = document.createElement("img");
|
|
3200
|
+
img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
|
|
3201
|
+
img.alt = actualFileName || resourceId;
|
|
3202
|
+
img.src = thumbUrl;
|
|
3203
|
+
tile.appendChild(img);
|
|
3204
|
+
tile.appendChild(actionsEl);
|
|
3205
|
+
return tile;
|
|
3206
|
+
}
|
|
3207
|
+
} catch {
|
|
3208
|
+
}
|
|
2424
3209
|
}
|
|
3210
|
+
const captionHtml = actualFileName ? `<div class="fb-tile-label">${escapeHtml(actualFileName.length > 10 ? actualFileName.substring(0, 8) + "\u2026" : actualFileName)}</div>
|
|
3211
|
+
<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>` : "";
|
|
3212
|
+
tile.innerHTML = `
|
|
3213
|
+
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:6px;gap:4px;">
|
|
3214
|
+
<div style="font-size:36px;">\u{1F4C1}</div>
|
|
3215
|
+
${captionHtml}
|
|
3216
|
+
</div>`;
|
|
3217
|
+
tile.appendChild(actionsEl);
|
|
2425
3218
|
}
|
|
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;
|
|
3219
|
+
return tile;
|
|
2445
3220
|
}
|
|
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>`;
|
|
3221
|
+
async function renderSingleFileEditTile(fileContainer, resourceId, state, deps) {
|
|
3222
|
+
var _a, _b, _c;
|
|
3223
|
+
const meta = state.resourceIndex.get(resourceId);
|
|
3224
|
+
const fileName = (_b = (_a = meta == null ? void 0 : meta.name) != null ? _a : resourceId.split("/").pop()) != null ? _b : "";
|
|
3225
|
+
const removeHandler = (_c = deps.onRemove) != null ? _c : null;
|
|
3226
|
+
const tile = await renderFilePreviewReadonly(resourceId, state, fileName, {
|
|
3227
|
+
canRemove: true,
|
|
3228
|
+
removeHandler
|
|
3229
|
+
});
|
|
3230
|
+
fileContainer.className = "file-preview-container";
|
|
3231
|
+
fileContainer.removeAttribute("style");
|
|
3232
|
+
clear(fileContainer);
|
|
3233
|
+
fileContainer.appendChild(tile);
|
|
2561
3234
|
}
|
|
2562
|
-
async function
|
|
2563
|
-
var _a, _b;
|
|
2564
|
-
if (
|
|
3235
|
+
async function fillTileContent(tile, rid, meta, state, actionsEl) {
|
|
3236
|
+
var _a, _b, _c;
|
|
3237
|
+
if ((_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("image/")) {
|
|
2565
3238
|
if (meta.file && meta.file instanceof File) {
|
|
2566
3239
|
const img = document.createElement("img");
|
|
2567
|
-
img.
|
|
3240
|
+
img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
|
|
2568
3241
|
img.alt = meta.name;
|
|
2569
3242
|
const reader = new FileReader();
|
|
2570
3243
|
reader.onload = (e) => {
|
|
2571
3244
|
var _a2;
|
|
2572
3245
|
img.src = ((_a2 = e.target) == null ? void 0 : _a2.result) || "";
|
|
3246
|
+
attachZoomHover(tile, img.src, meta.name, actionsEl != null ? actionsEl : null);
|
|
2573
3247
|
};
|
|
2574
3248
|
reader.readAsDataURL(meta.file);
|
|
2575
|
-
|
|
3249
|
+
tile.appendChild(img);
|
|
2576
3250
|
} else if (state.config.getThumbnail) {
|
|
2577
3251
|
try {
|
|
2578
3252
|
const url = await state.config.getThumbnail(rid);
|
|
2579
3253
|
if (url) {
|
|
2580
3254
|
const img = document.createElement("img");
|
|
2581
|
-
img.
|
|
3255
|
+
img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
|
|
2582
3256
|
img.alt = meta.name;
|
|
2583
3257
|
img.src = url;
|
|
2584
|
-
|
|
3258
|
+
tile.appendChild(img);
|
|
3259
|
+
attachZoomHover(tile, url, meta.name, actionsEl != null ? actionsEl : null);
|
|
2585
3260
|
} 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>`;
|
|
3261
|
+
tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F5BC}\uFE0F</div>`;
|
|
2591
3262
|
}
|
|
2592
3263
|
} catch (error) {
|
|
2593
3264
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
2594
|
-
if (state.config.onThumbnailError)
|
|
2595
|
-
|
|
2596
|
-
}
|
|
2597
|
-
renderThumbnailError(slot, state);
|
|
3265
|
+
if (state.config.onThumbnailError) state.config.onThumbnailError(err, rid);
|
|
3266
|
+
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
3267
|
}
|
|
2599
3268
|
} 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>`;
|
|
3269
|
+
tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F5BC}\uFE0F</div>`;
|
|
2605
3270
|
}
|
|
2606
|
-
|
|
3271
|
+
if (actionsEl) tile.appendChild(actionsEl);
|
|
3272
|
+
} else if ((_b = meta == null ? void 0 : meta.type) == null ? void 0 : _b.startsWith("video/")) {
|
|
2607
3273
|
if (meta.file && meta.file instanceof File) {
|
|
2608
3274
|
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>
|
|
3275
|
+
tile.innerHTML = `
|
|
3276
|
+
<video style="width:100%;height:100%;" preload="metadata" muted src="${videoUrl}"></video>
|
|
3277
|
+
<div class="fb-video-overlay">
|
|
3278
|
+
<div class="fb-play-btn" style="width:20px;height:20px;">
|
|
3279
|
+
<svg width="8" height="10" viewBox="0 0 8 10" fill="currentColor"><path d="M0 0l8 5-8 5z"/></svg>
|
|
2619
3280
|
</div>
|
|
2620
|
-
</div
|
|
2621
|
-
`;
|
|
3281
|
+
</div>`;
|
|
2622
3282
|
} else if (state.config.getThumbnail) {
|
|
2623
3283
|
try {
|
|
2624
3284
|
const videoUrl = await state.config.getThumbnail(rid);
|
|
2625
3285
|
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>
|
|
3286
|
+
tile.innerHTML = `
|
|
3287
|
+
<img style="width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);" alt="${escapeHtml(meta.name)}" src="${videoUrl}">
|
|
3288
|
+
<div class="fb-video-overlay">
|
|
3289
|
+
<div class="fb-play-btn" style="width:20px;height:20px;">
|
|
3290
|
+
<svg width="8" height="10" viewBox="0 0 8 10" fill="currentColor"><path d="M0 0l8 5-8 5z"/></svg>
|
|
2636
3291
|
</div>
|
|
2637
|
-
</div
|
|
2638
|
-
`;
|
|
3292
|
+
</div>`;
|
|
2639
3293
|
} 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>`;
|
|
3294
|
+
tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
|
|
2646
3295
|
}
|
|
2647
3296
|
} catch (error) {
|
|
2648
3297
|
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");
|
|
3298
|
+
if (state.config.onThumbnailError) state.config.onThumbnailError(err, rid);
|
|
3299
|
+
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
3300
|
}
|
|
2654
3301
|
} 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>`;
|
|
3302
|
+
tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:36px;">\u{1F3A5}</div>`;
|
|
2661
3303
|
}
|
|
3304
|
+
if (actionsEl) tile.appendChild(actionsEl);
|
|
2662
3305
|
} else {
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
3306
|
+
const name = (_c = meta == null ? void 0 : meta.name) != null ? _c : "";
|
|
3307
|
+
const hasExtension = name.includes(".");
|
|
3308
|
+
const captionHtml = hasExtension ? `<div class="fb-tile-label">${escapeHtml(name.length > 10 ? name.substring(0, 8) + "\u2026" : name)}</div>` : "";
|
|
3309
|
+
tile.innerHTML = `
|
|
3310
|
+
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:6px;gap:4px;">
|
|
3311
|
+
<div style="font-size:36px;">\u{1F4C1}</div>
|
|
3312
|
+
${captionHtml}
|
|
3313
|
+
</div>`;
|
|
3314
|
+
if (actionsEl) tile.appendChild(actionsEl);
|
|
2667
3315
|
}
|
|
2668
3316
|
}
|
|
2669
|
-
function
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
3317
|
+
async function forceDownload(resourceId, fileName, state) {
|
|
3318
|
+
try {
|
|
3319
|
+
let fileUrl = null;
|
|
3320
|
+
if (state.config.getDownloadUrl) {
|
|
3321
|
+
fileUrl = state.config.getDownloadUrl(resourceId);
|
|
3322
|
+
} else if (state.config.getThumbnail) {
|
|
3323
|
+
fileUrl = await state.config.getThumbnail(resourceId);
|
|
3324
|
+
}
|
|
3325
|
+
if (fileUrl) {
|
|
3326
|
+
const finalUrl = fileUrl.startsWith("http") ? fileUrl : new URL(fileUrl, window.location.href).href;
|
|
3327
|
+
const response = await fetch(finalUrl);
|
|
3328
|
+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
|
3329
|
+
const blob = await response.blob();
|
|
3330
|
+
downloadBlob(blob, fileName);
|
|
3331
|
+
} else {
|
|
3332
|
+
throw new Error("No download URL available for resource");
|
|
3333
|
+
}
|
|
3334
|
+
} catch (error) {
|
|
3335
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
3336
|
+
if (state.config.onDownloadError) {
|
|
3337
|
+
state.config.onDownloadError(err, resourceId, fileName);
|
|
3338
|
+
}
|
|
3339
|
+
console.error(`File download failed for ${fileName}:`, err);
|
|
3340
|
+
throw err;
|
|
3341
|
+
}
|
|
2680
3342
|
}
|
|
2681
|
-
function
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
3343
|
+
function downloadBlob(blob, fileName) {
|
|
3344
|
+
try {
|
|
3345
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
3346
|
+
const link = document.createElement("a");
|
|
3347
|
+
link.href = blobUrl;
|
|
3348
|
+
link.download = fileName;
|
|
3349
|
+
link.style.display = "none";
|
|
3350
|
+
document.body.appendChild(link);
|
|
3351
|
+
link.click();
|
|
3352
|
+
document.body.removeChild(link);
|
|
3353
|
+
setTimeout(() => {
|
|
3354
|
+
URL.revokeObjectURL(blobUrl);
|
|
3355
|
+
}, 100);
|
|
3356
|
+
} catch (error) {
|
|
3357
|
+
throw new Error(`Blob download failed: ${error.message}`);
|
|
3358
|
+
}
|
|
2694
3359
|
}
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
if (
|
|
3360
|
+
|
|
3361
|
+
// src/components/file/upload.ts
|
|
3362
|
+
async function uploadSingleFile(file, state) {
|
|
3363
|
+
if (!state.config.uploadFile) {
|
|
3364
|
+
throw new Error(
|
|
3365
|
+
"No upload handler configured. Set uploadHandler via FormBuilder.setUploadHandler()"
|
|
3366
|
+
);
|
|
3367
|
+
}
|
|
3368
|
+
try {
|
|
3369
|
+
const rid = await state.config.uploadFile(file);
|
|
3370
|
+
if (typeof rid !== "string") {
|
|
3371
|
+
throw new Error("Upload handler must return a string resource ID");
|
|
3372
|
+
}
|
|
3373
|
+
return rid;
|
|
3374
|
+
} catch (error) {
|
|
3375
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
3376
|
+
if (state.config.onUploadError) state.config.onUploadError(err, file);
|
|
3377
|
+
throw new Error(`File upload failed: ${err.message}`);
|
|
3378
|
+
}
|
|
2699
3379
|
}
|
|
2700
|
-
async function handleFileSelect(
|
|
3380
|
+
async function handleFileSelect(opts) {
|
|
2701
3381
|
var _a, _b;
|
|
3382
|
+
const {
|
|
3383
|
+
file,
|
|
3384
|
+
container,
|
|
3385
|
+
fieldName,
|
|
3386
|
+
state,
|
|
3387
|
+
deps = null,
|
|
3388
|
+
instance = null,
|
|
3389
|
+
allowedExtensions = [],
|
|
3390
|
+
allowedMimes = [],
|
|
3391
|
+
maxSizeMB = Infinity
|
|
3392
|
+
} = opts;
|
|
2702
3393
|
if (!isFileExtensionAllowed(file.name, allowedExtensions)) {
|
|
2703
3394
|
const formats = allowedExtensions.join(", ");
|
|
2704
3395
|
showFileError(
|
|
@@ -2707,6 +3398,14 @@ async function handleFileSelect(file, container, fieldName, state, deps = null,
|
|
|
2707
3398
|
);
|
|
2708
3399
|
return;
|
|
2709
3400
|
}
|
|
3401
|
+
if (!isMimeAllowed(file.type, allowedMimes)) {
|
|
3402
|
+
const mimes = allowedMimes.join(", ");
|
|
3403
|
+
showFileError(
|
|
3404
|
+
container,
|
|
3405
|
+
t("invalidFileMime", state, { name: file.name, type: file.type, mimes })
|
|
3406
|
+
);
|
|
3407
|
+
return;
|
|
3408
|
+
}
|
|
2710
3409
|
if (!isFileSizeAllowed(file, maxSizeMB)) {
|
|
2711
3410
|
showFileError(
|
|
2712
3411
|
container,
|
|
@@ -2715,24 +3414,18 @@ async function handleFileSelect(file, container, fieldName, state, deps = null,
|
|
|
2715
3414
|
return;
|
|
2716
3415
|
}
|
|
2717
3416
|
clearFileError(container);
|
|
3417
|
+
ensureFileStyles();
|
|
3418
|
+
container.innerHTML = `
|
|
3419
|
+
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:6px;padding:6px;">
|
|
3420
|
+
<div class="fb-spinner"></div>
|
|
3421
|
+
<div style="font-size:11px;color:var(--fb-text-secondary-color,#6b7280);text-align:center;">${escapeHtml(t("uploadingFile", state))}</div>
|
|
3422
|
+
</div>`;
|
|
2718
3423
|
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
|
-
);
|
|
3424
|
+
try {
|
|
3425
|
+
rid = await uploadSingleFile(file, state);
|
|
3426
|
+
} catch (error) {
|
|
3427
|
+
setEmptyFileContainer(container, state);
|
|
3428
|
+
throw error;
|
|
2736
3429
|
}
|
|
2737
3430
|
state.resourceIndex.set(rid, {
|
|
2738
3431
|
name: file.name,
|
|
@@ -2740,7 +3433,6 @@ async function handleFileSelect(file, container, fieldName, state, deps = null,
|
|
|
2740
3433
|
size: file.size,
|
|
2741
3434
|
uploadedAt: /* @__PURE__ */ new Date(),
|
|
2742
3435
|
file
|
|
2743
|
-
// Store the file object for local preview
|
|
2744
3436
|
});
|
|
2745
3437
|
let hiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
|
|
2746
3438
|
'input[type="hidden"]'
|
|
@@ -2752,693 +3444,1009 @@ async function handleFileSelect(file, container, fieldName, state, deps = null,
|
|
|
2752
3444
|
(_b = container.parentElement) == null ? void 0 : _b.appendChild(hiddenInput);
|
|
2753
3445
|
}
|
|
2754
3446
|
hiddenInput.value = rid;
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
3447
|
+
const isVideo = file.type.startsWith("video/");
|
|
3448
|
+
if (!isVideo && deps) {
|
|
3449
|
+
renderSingleFileEditTile(container, rid, state, deps).catch(console.error);
|
|
3450
|
+
} else {
|
|
3451
|
+
renderFilePreview(container, rid, state, {
|
|
3452
|
+
fileName: file.name,
|
|
3453
|
+
isReadonly: false,
|
|
3454
|
+
deps
|
|
3455
|
+
}).catch(console.error);
|
|
3456
|
+
}
|
|
2760
3457
|
if (instance && !state.config.readonly) {
|
|
2761
3458
|
instance.triggerOnChange(fieldName, rid);
|
|
2762
3459
|
}
|
|
2763
3460
|
}
|
|
2764
|
-
function
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
3461
|
+
function filterAndSlice(allFiles, currentCount, constraints, state) {
|
|
3462
|
+
const rejectedByExt = allFiles.filter(
|
|
3463
|
+
(f) => !isFileExtensionAllowed(f.name, constraints.allowedExtensions)
|
|
3464
|
+
);
|
|
3465
|
+
const afterExt = allFiles.filter(
|
|
3466
|
+
(f) => isFileExtensionAllowed(f.name, constraints.allowedExtensions)
|
|
3467
|
+
);
|
|
3468
|
+
const rejectedByMime = afterExt.filter(
|
|
3469
|
+
(f) => !isMimeAllowed(f.type, constraints.allowedMimes)
|
|
3470
|
+
);
|
|
3471
|
+
const afterMime = afterExt.filter(
|
|
3472
|
+
(f) => isMimeAllowed(f.type, constraints.allowedMimes)
|
|
3473
|
+
);
|
|
3474
|
+
const rejectedBySize = afterMime.filter(
|
|
3475
|
+
(f) => !isFileSizeAllowed(f, constraints.maxSize)
|
|
3476
|
+
);
|
|
3477
|
+
const valid = afterMime.filter((f) => isFileSizeAllowed(f, constraints.maxSize));
|
|
3478
|
+
const remaining = constraints.maxCount === Infinity ? valid.length : Math.max(0, constraints.maxCount - currentCount);
|
|
3479
|
+
const accepted = valid.slice(0, remaining);
|
|
3480
|
+
const skippedByCount = valid.length - accepted.length;
|
|
3481
|
+
const errorParts = [];
|
|
3482
|
+
if (rejectedByExt.length > 0) {
|
|
3483
|
+
const formats = constraints.allowedExtensions.join(", ");
|
|
3484
|
+
const names = rejectedByExt.map((f) => f.name).join(", ");
|
|
3485
|
+
errorParts.push(t("invalidFileExtension", state, { name: names, formats }));
|
|
3486
|
+
}
|
|
3487
|
+
if (rejectedByMime.length > 0) {
|
|
3488
|
+
const mimes = constraints.allowedMimes.join(", ");
|
|
3489
|
+
const names = rejectedByMime.map((f) => f.name).join(", ");
|
|
3490
|
+
errorParts.push(t("invalidFileMime", state, { name: names, type: rejectedByMime.map((f) => f.type).join(", "), mimes }));
|
|
3491
|
+
}
|
|
3492
|
+
if (rejectedBySize.length > 0) {
|
|
3493
|
+
const names = rejectedBySize.map((f) => f.name).join(", ");
|
|
3494
|
+
errorParts.push(
|
|
3495
|
+
t("fileTooLarge", state, { name: names, maxSize: constraints.maxSize })
|
|
3496
|
+
);
|
|
3497
|
+
}
|
|
3498
|
+
if (skippedByCount > 0) {
|
|
3499
|
+
errorParts.push(
|
|
3500
|
+
t("filesLimitExceeded", state, {
|
|
3501
|
+
skipped: skippedByCount,
|
|
3502
|
+
max: constraints.maxCount
|
|
3503
|
+
})
|
|
3504
|
+
);
|
|
3505
|
+
}
|
|
3506
|
+
return { accepted, errorMessage: errorParts.join(" \u2022 ") };
|
|
3507
|
+
}
|
|
3508
|
+
async function uploadBatch(accepted, resourceIds, listEl, state) {
|
|
3509
|
+
await Promise.all(
|
|
3510
|
+
accepted.map(async (file) => {
|
|
3511
|
+
const placeholder = createUploadingTile(file.name, state);
|
|
3512
|
+
if (listEl) {
|
|
3513
|
+
const tilesWrap = ensureTilesWrap(listEl);
|
|
3514
|
+
const addTile = tilesWrap.querySelector(".fb-tile-add");
|
|
3515
|
+
if (addTile) {
|
|
3516
|
+
tilesWrap.insertBefore(placeholder, addTile);
|
|
3517
|
+
} else {
|
|
3518
|
+
tilesWrap.appendChild(placeholder);
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
try {
|
|
3522
|
+
const rid = await uploadSingleFile(file, state);
|
|
3523
|
+
state.resourceIndex.set(rid, {
|
|
3524
|
+
name: file.name,
|
|
3525
|
+
type: file.type,
|
|
3526
|
+
size: file.size,
|
|
3527
|
+
uploadedAt: /* @__PURE__ */ new Date(),
|
|
3528
|
+
file: void 0
|
|
3529
|
+
});
|
|
3530
|
+
resourceIds.push(rid);
|
|
3531
|
+
} finally {
|
|
3532
|
+
placeholder.remove();
|
|
3533
|
+
}
|
|
3534
|
+
})
|
|
3535
|
+
);
|
|
3536
|
+
}
|
|
3537
|
+
function setupFilesDropHandler(filesContainer, resourceIds, state, updateCallback, constraints, pathKey, instance) {
|
|
3538
|
+
setupDragAndDrop(filesContainer, async (files) => {
|
|
2774
3539
|
var _a;
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
3540
|
+
const { accepted, errorMessage } = filterAndSlice(
|
|
3541
|
+
Array.from(files),
|
|
3542
|
+
resourceIds.length,
|
|
3543
|
+
constraints,
|
|
3544
|
+
state
|
|
3545
|
+
);
|
|
3546
|
+
if (errorMessage) {
|
|
3547
|
+
showFileError(filesContainer, errorMessage);
|
|
3548
|
+
} else {
|
|
3549
|
+
clearFileError(filesContainer);
|
|
3550
|
+
}
|
|
3551
|
+
const list = (_a = filesContainer.querySelector(".files-list")) != null ? _a : filesContainer;
|
|
3552
|
+
await uploadBatch(accepted, resourceIds, list, state);
|
|
3553
|
+
updateCallback();
|
|
3554
|
+
if (instance && pathKey && !state.config.readonly) {
|
|
3555
|
+
instance.triggerOnChange(pathKey, resourceIds);
|
|
2779
3556
|
}
|
|
2780
3557
|
});
|
|
2781
3558
|
}
|
|
2782
|
-
function
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
3559
|
+
function setupFilesPickerHandler(filesPicker, resourceIds, state, updateCallback, constraints, pathKey, instance) {
|
|
3560
|
+
filesPicker.onchange = async () => {
|
|
3561
|
+
if (!filesPicker.files) return;
|
|
3562
|
+
const wrapperEl = filesPicker.closest(".space-y-2") || filesPicker.parentElement;
|
|
3563
|
+
const { accepted, errorMessage } = filterAndSlice(
|
|
3564
|
+
Array.from(filesPicker.files),
|
|
3565
|
+
resourceIds.length,
|
|
3566
|
+
constraints,
|
|
3567
|
+
state
|
|
3568
|
+
);
|
|
3569
|
+
if (errorMessage && wrapperEl) {
|
|
3570
|
+
showFileError(wrapperEl, errorMessage);
|
|
3571
|
+
} else if (wrapperEl) {
|
|
3572
|
+
clearFileError(wrapperEl);
|
|
3573
|
+
}
|
|
3574
|
+
const listEl = wrapperEl == null ? void 0 : wrapperEl.querySelector(".files-list");
|
|
3575
|
+
await uploadBatch(accepted, resourceIds, listEl != null ? listEl : null, state);
|
|
3576
|
+
updateCallback();
|
|
3577
|
+
filesPicker.value = "";
|
|
3578
|
+
if (instance && pathKey && !state.config.readonly) {
|
|
3579
|
+
instance.triggerOnChange(pathKey, resourceIds);
|
|
3580
|
+
}
|
|
2795
3581
|
};
|
|
2796
|
-
overlay.appendChild(deleteBtn);
|
|
2797
|
-
container.appendChild(overlay);
|
|
2798
3582
|
}
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
3583
|
+
|
|
3584
|
+
// src/components/file/library.ts
|
|
3585
|
+
function buildAcceptContext(element) {
|
|
3586
|
+
var _a, _b;
|
|
3587
|
+
if (!element.accept) return void 0;
|
|
3588
|
+
if (typeof element.accept === "string") {
|
|
3589
|
+
const exts2 = getAllowedExtensions(element.accept);
|
|
3590
|
+
return exts2.length > 0 ? { extensions: exts2 } : void 0;
|
|
3591
|
+
}
|
|
3592
|
+
const exts = (_a = element.accept.extensions) != null ? _a : [];
|
|
3593
|
+
const mime = (_b = element.accept.mime) != null ? _b : [];
|
|
3594
|
+
const normalizedExts = exts.map((e) => e.toLowerCase());
|
|
3595
|
+
if (normalizedExts.length === 0 && mime.length === 0) return void 0;
|
|
3596
|
+
const result = {};
|
|
3597
|
+
if (normalizedExts.length > 0) result.extensions = normalizedExts;
|
|
3598
|
+
if (mime.length > 0) result.mime = mime;
|
|
3599
|
+
return result;
|
|
3600
|
+
}
|
|
3601
|
+
function validatePickedResource(resource, allowedExtensions, allowedMimes, maxSizeMB, state) {
|
|
3602
|
+
if (!isFileExtensionAllowed(resource.name, allowedExtensions)) {
|
|
3603
|
+
const formats = allowedExtensions.join(", ");
|
|
3604
|
+
return t("invalidFileExtension", state, { name: resource.name, formats });
|
|
3605
|
+
}
|
|
3606
|
+
if (!isMimeAllowed(resource.type, allowedMimes)) {
|
|
3607
|
+
const mimes = allowedMimes.join(", ");
|
|
3608
|
+
return t("invalidFileMime", state, { name: resource.name, type: resource.type, mimes });
|
|
3609
|
+
}
|
|
3610
|
+
if (!isSizeWithinLimit(resource.size, maxSizeMB)) {
|
|
3611
|
+
return t("fileTooLarge", state, { name: resource.name, maxSize: maxSizeMB });
|
|
2818
3612
|
}
|
|
3613
|
+
return null;
|
|
2819
3614
|
}
|
|
2820
|
-
|
|
3615
|
+
function readCurrentResourceIds(wrapper) {
|
|
3616
|
+
const raw = wrapper.dataset.resourceIds;
|
|
3617
|
+
if (!raw) return [];
|
|
2821
3618
|
try {
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
fileUrl = await state.config.getThumbnail(resourceId);
|
|
2827
|
-
}
|
|
2828
|
-
if (fileUrl) {
|
|
2829
|
-
const finalUrl = fileUrl.startsWith("http") ? fileUrl : new URL(fileUrl, window.location.href).href;
|
|
2830
|
-
const response = await fetch(finalUrl);
|
|
2831
|
-
if (!response.ok) {
|
|
2832
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
2833
|
-
}
|
|
2834
|
-
const blob = await response.blob();
|
|
2835
|
-
downloadBlob(blob, fileName);
|
|
2836
|
-
} else {
|
|
2837
|
-
throw new Error("No download URL available for resource");
|
|
2838
|
-
}
|
|
2839
|
-
} catch (error) {
|
|
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;
|
|
3619
|
+
const parsed = JSON.parse(raw);
|
|
3620
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
3621
|
+
} catch {
|
|
3622
|
+
return [];
|
|
2846
3623
|
}
|
|
2847
3624
|
}
|
|
2848
|
-
function
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
3625
|
+
function registerPickedResource(resource, state) {
|
|
3626
|
+
var _a;
|
|
3627
|
+
const existing = state.resourceIndex.get(resource.resourceId);
|
|
3628
|
+
state.resourceIndex.set(resource.resourceId, {
|
|
3629
|
+
name: resource.name,
|
|
3630
|
+
type: resource.type,
|
|
3631
|
+
size: resource.size,
|
|
3632
|
+
uploadedAt: (_a = existing == null ? void 0 : existing.uploadedAt) != null ? _a : /* @__PURE__ */ new Date(),
|
|
3633
|
+
file: existing == null ? void 0 : existing.file
|
|
3634
|
+
});
|
|
3635
|
+
}
|
|
3636
|
+
function extractPickerError(error, state) {
|
|
3637
|
+
if (error instanceof Error && error.message) return error.message;
|
|
3638
|
+
return t("pickerError", state);
|
|
3639
|
+
}
|
|
3640
|
+
async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resourceIds, maxCount, updateCallback, instance) {
|
|
3641
|
+
var _a;
|
|
3642
|
+
if (!state.config.pickExistingFiles) return;
|
|
3643
|
+
const allowedExtensions = getAllowedExtensions(element.accept);
|
|
3644
|
+
const allowedMimes = getAllowedMimes(element.accept);
|
|
3645
|
+
const maxSizeMB = (_a = element.maxSize) != null ? _a : Infinity;
|
|
3646
|
+
const currentIds = readCurrentResourceIds(wrapper);
|
|
3647
|
+
const remaining = maxCount === Infinity ? Infinity : Math.max(0, maxCount - currentIds.length);
|
|
3648
|
+
let picked;
|
|
3649
|
+
try {
|
|
3650
|
+
picked = await state.config.pickExistingFiles({
|
|
3651
|
+
fieldPath,
|
|
3652
|
+
mode: "multiple",
|
|
3653
|
+
accept: buildAcceptContext(element),
|
|
3654
|
+
maxSizeMB: maxSizeMB === Infinity ? void 0 : maxSizeMB,
|
|
3655
|
+
remainingSlots: remaining === Infinity ? void 0 : remaining,
|
|
3656
|
+
selectedResourceIds: [...currentIds]
|
|
3657
|
+
});
|
|
2861
3658
|
} catch (error) {
|
|
2862
|
-
|
|
3659
|
+
showFileError(wrapper, extractPickerError(error, state));
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
if (picked.length === 0) return;
|
|
3663
|
+
const existingSet = new Set(currentIds);
|
|
3664
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3665
|
+
const deduped = picked.filter((r) => {
|
|
3666
|
+
if (existingSet.has(r.resourceId)) return false;
|
|
3667
|
+
if (seen.has(r.resourceId)) return false;
|
|
3668
|
+
seen.add(r.resourceId);
|
|
3669
|
+
return true;
|
|
3670
|
+
});
|
|
3671
|
+
const validItems = deduped.filter((r) => {
|
|
3672
|
+
const err = validatePickedResource(r, allowedExtensions, allowedMimes, maxSizeMB, state);
|
|
3673
|
+
return err === null;
|
|
3674
|
+
});
|
|
3675
|
+
const freshRemaining = maxCount === Infinity ? validItems.length : Math.max(0, maxCount - resourceIds.length);
|
|
3676
|
+
const accepted = validItems.slice(0, freshRemaining);
|
|
3677
|
+
const skipped = validItems.length - accepted.length;
|
|
3678
|
+
if (accepted.length === 0) return;
|
|
3679
|
+
clearFileError(wrapper);
|
|
3680
|
+
if (skipped > 0) {
|
|
3681
|
+
showFileError(
|
|
3682
|
+
wrapper,
|
|
3683
|
+
t("filesLimitExceeded", state, { skipped, max: maxCount })
|
|
3684
|
+
);
|
|
3685
|
+
}
|
|
3686
|
+
for (const resource of accepted) {
|
|
3687
|
+
registerPickedResource(resource, state);
|
|
3688
|
+
resourceIds.push(resource.resourceId);
|
|
3689
|
+
}
|
|
3690
|
+
wrapper.dataset.resourceIds = JSON.stringify(resourceIds);
|
|
3691
|
+
updateCallback();
|
|
3692
|
+
if (!state.config.readonly) {
|
|
3693
|
+
instance.triggerOnChange(fieldPath, resourceIds);
|
|
2863
3694
|
}
|
|
2864
3695
|
}
|
|
2865
|
-
function
|
|
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
|
-
}
|
|
3696
|
+
async function handleLibraryPickSingle(state, element, container, fileWrapper, pathKey, fieldPath, renderCallback, instance) {
|
|
3697
|
+
var _a;
|
|
3698
|
+
if (!state.config.pickExistingFiles) return;
|
|
3699
|
+
const allowedExtensions = getAllowedExtensions(element.accept);
|
|
3700
|
+
const allowedMimes = getAllowedMimes(element.accept);
|
|
3701
|
+
const maxSizeMB = (_a = element.maxSize) != null ? _a : Infinity;
|
|
3702
|
+
let picked;
|
|
3703
|
+
try {
|
|
3704
|
+
picked = await state.config.pickExistingFiles({
|
|
3705
|
+
fieldPath,
|
|
3706
|
+
mode: "single",
|
|
3707
|
+
accept: buildAcceptContext(element),
|
|
3708
|
+
maxSizeMB: maxSizeMB === Infinity ? void 0 : maxSizeMB,
|
|
3709
|
+
selectedResourceIds: []
|
|
2888
3710
|
});
|
|
3711
|
+
} catch (error) {
|
|
3712
|
+
showFileError(container, extractPickerError(error, state));
|
|
3713
|
+
return;
|
|
3714
|
+
}
|
|
3715
|
+
if (picked.length === 0) return;
|
|
3716
|
+
const first = picked[0];
|
|
3717
|
+
const validationError = validatePickedResource(first, allowedExtensions, allowedMimes, maxSizeMB, state);
|
|
3718
|
+
if (validationError !== null) {
|
|
3719
|
+
showFileError(container, validationError);
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
clearFileError(container);
|
|
3723
|
+
registerPickedResource(first, state);
|
|
3724
|
+
let hiddenInput = fileWrapper.querySelector('input[type="hidden"]');
|
|
3725
|
+
if (!hiddenInput) {
|
|
3726
|
+
hiddenInput = document.createElement("input");
|
|
3727
|
+
hiddenInput.type = "hidden";
|
|
3728
|
+
hiddenInput.name = pathKey;
|
|
3729
|
+
fileWrapper.appendChild(hiddenInput);
|
|
3730
|
+
}
|
|
3731
|
+
hiddenInput.value = first.resourceId;
|
|
3732
|
+
await renderCallback(first.resourceId);
|
|
3733
|
+
if (!state.config.readonly) {
|
|
3734
|
+
instance.triggerOnChange(fieldPath, first.resourceId);
|
|
2889
3735
|
}
|
|
2890
3736
|
}
|
|
3737
|
+
|
|
3738
|
+
// src/components/file/render-edit.ts
|
|
2891
3739
|
function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, state, deps) {
|
|
2892
3740
|
var _a;
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
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
|
|
2910
|
-
});
|
|
3741
|
+
seedInferredResource(initial, state.resourceIndex);
|
|
3742
|
+
const meta = state.resourceIndex.get(initial);
|
|
3743
|
+
const isVideo = (_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("video/");
|
|
3744
|
+
if (isVideo) {
|
|
3745
|
+
renderFilePreview(fileContainer, initial, state, {
|
|
3746
|
+
fileName: initial,
|
|
3747
|
+
isReadonly: false,
|
|
3748
|
+
deps
|
|
3749
|
+
}).catch(console.error);
|
|
3750
|
+
} else {
|
|
3751
|
+
renderSingleFileEditTile(fileContainer, initial, state, deps).catch(console.error);
|
|
2911
3752
|
}
|
|
2912
|
-
renderFilePreview(fileContainer, initial, state, {
|
|
2913
|
-
fileName: initial,
|
|
2914
|
-
isReadonly: false,
|
|
2915
|
-
deps
|
|
2916
|
-
}).catch(console.error);
|
|
2917
3753
|
const hiddenInput = document.createElement("input");
|
|
2918
3754
|
hiddenInput.type = "hidden";
|
|
2919
3755
|
hiddenInput.name = pathKey;
|
|
2920
3756
|
hiddenInput.value = initial;
|
|
2921
3757
|
fileWrapper.appendChild(hiddenInput);
|
|
2922
3758
|
}
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
)
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
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
|
-
});
|
|
3759
|
+
var UPLOAD_SVG = `<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" style="flex-shrink:0;color:var(--fb-file-upload-text-color,#9ca3af);">
|
|
3760
|
+
<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"/>
|
|
3761
|
+
</svg>`;
|
|
3762
|
+
function buildEmptyDropzone(state, primaryText, subHint, openPicker) {
|
|
3763
|
+
const dropzone = document.createElement("div");
|
|
3764
|
+
dropzone.className = "fb-file-dropzone";
|
|
3765
|
+
dropzone.innerHTML = `
|
|
3766
|
+
${UPLOAD_SVG}
|
|
3767
|
+
<div class="fb-dropzone-primary-text">${escapeHtml(primaryText)}</div>
|
|
3768
|
+
${subHint ? `<div class="fb-dropzone-hint-text">${escapeHtml(subHint)}</div>` : ""}
|
|
3769
|
+
`;
|
|
3770
|
+
dropzone.onclick = openPicker;
|
|
3771
|
+
return dropzone;
|
|
2984
3772
|
}
|
|
2985
|
-
function
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
3773
|
+
function buildLibraryButton(variant, state, onClick) {
|
|
3774
|
+
const btn = document.createElement("button");
|
|
3775
|
+
btn.type = "button";
|
|
3776
|
+
btn.className = variant === "card" ? "fb-file-library-card" : "fb-tile fb-tile-add-library";
|
|
3777
|
+
if (variant === "card") {
|
|
3778
|
+
btn.innerHTML = `
|
|
3779
|
+
<span class="fb-file-library-card-icon" aria-hidden="true">\u{1F4DA}</span>
|
|
3780
|
+
<span class="fb-file-library-card-label">${escapeHtml(t("fromLibrary", state))}</span>
|
|
3781
|
+
<span class="fb-file-library-card-hint">${escapeHtml(t("libraryHint", state))}</span>
|
|
3782
|
+
`;
|
|
3783
|
+
} else {
|
|
3784
|
+
btn.innerHTML = `<span aria-hidden="true">\u{1F4DA}</span>`;
|
|
3785
|
+
btn.title = t("fromLibrary", state);
|
|
3786
|
+
btn.setAttribute("aria-label", t("fromLibrary", state));
|
|
3787
|
+
}
|
|
3788
|
+
btn.addEventListener("click", onClick);
|
|
3789
|
+
return btn;
|
|
3790
|
+
}
|
|
3791
|
+
function renderResourcePills(opts) {
|
|
3792
|
+
var _a;
|
|
3793
|
+
const {
|
|
3794
|
+
container,
|
|
3795
|
+
rids,
|
|
3796
|
+
state,
|
|
3797
|
+
onRemove,
|
|
3798
|
+
hint,
|
|
3799
|
+
countInfo,
|
|
3800
|
+
maxCount,
|
|
3801
|
+
isReadonly = false,
|
|
3802
|
+
onLibraryPick
|
|
3803
|
+
} = opts;
|
|
3804
|
+
ensureFileStyles();
|
|
3805
|
+
const wrapper = container.closest("[data-files-wrapper]");
|
|
3806
|
+
if (wrapper) {
|
|
3807
|
+
wrapper.dataset.resourceIds = JSON.stringify(rids != null ? rids : []);
|
|
3808
|
+
}
|
|
3809
|
+
while (container.firstChild) container.removeChild(container.firstChild);
|
|
3810
|
+
const ridList = rids != null ? rids : [];
|
|
3811
|
+
const atMax = maxCount !== void 0 && ridList.length >= maxCount;
|
|
3812
|
+
const hasLibrary = !isReadonly && typeof onLibraryPick === "function";
|
|
3813
|
+
const buildSubHint = () => {
|
|
3814
|
+
const parts = [];
|
|
3815
|
+
if (hint) parts.push(hint);
|
|
3816
|
+
if (countInfo) parts.push(countInfo);
|
|
3817
|
+
return parts.join(" \u2022 ");
|
|
3818
|
+
};
|
|
3819
|
+
const openPicker = () => {
|
|
3820
|
+
const picker = findFilePicker(container);
|
|
3821
|
+
if (picker) picker.click();
|
|
3822
|
+
};
|
|
3823
|
+
if (ridList.length === 0) {
|
|
3824
|
+
if (isReadonly) {
|
|
3825
|
+
const emptyEl = document.createElement("div");
|
|
3826
|
+
emptyEl.className = "fb-tile-empty-text";
|
|
3827
|
+
emptyEl.textContent = t("noFilesSelected", state);
|
|
3828
|
+
container.appendChild(emptyEl);
|
|
3829
|
+
} else if (hasLibrary) {
|
|
3830
|
+
const row = document.createElement("div");
|
|
3831
|
+
row.className = "fb-file-card-row";
|
|
3832
|
+
const dropzone = buildEmptyDropzone(
|
|
3833
|
+
state,
|
|
3834
|
+
t("clickDragTextMultiple", state),
|
|
3835
|
+
buildSubHint(),
|
|
3836
|
+
openPicker
|
|
2997
3837
|
);
|
|
2998
|
-
const
|
|
2999
|
-
|
|
3838
|
+
const libraryBtn = buildLibraryButton("card", state, onLibraryPick);
|
|
3839
|
+
row.appendChild(dropzone);
|
|
3840
|
+
row.appendChild(libraryBtn);
|
|
3841
|
+
container.appendChild(row);
|
|
3842
|
+
} else {
|
|
3843
|
+
const dropzone = buildEmptyDropzone(
|
|
3844
|
+
state,
|
|
3845
|
+
t("clickDragTextMultiple", state),
|
|
3846
|
+
buildSubHint(),
|
|
3847
|
+
openPicker
|
|
3000
3848
|
);
|
|
3001
|
-
|
|
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);
|
|
3849
|
+
container.appendChild(dropzone);
|
|
3051
3850
|
}
|
|
3052
|
-
|
|
3851
|
+
return;
|
|
3852
|
+
}
|
|
3853
|
+
const tilesWrap = document.createElement("div");
|
|
3854
|
+
tilesWrap.className = "fb-tiles-wrap";
|
|
3855
|
+
tilesWrap.style.cssText = "display:flex;flex-wrap:wrap;gap:6px;align-items:flex-start;";
|
|
3856
|
+
for (const rid of ridList) {
|
|
3857
|
+
const meta = state.resourceIndex.get(rid);
|
|
3858
|
+
const tile = createFileTile();
|
|
3859
|
+
tile.classList.add("fb-tile-resource", "resource-pill");
|
|
3860
|
+
tile.dataset.resourceId = rid;
|
|
3861
|
+
const actionsEl = createTileActions({
|
|
3862
|
+
canRemove: !isReadonly && onRemove !== null,
|
|
3863
|
+
removeHandler: onRemove ? () => onRemove(rid) : null,
|
|
3864
|
+
state,
|
|
3865
|
+
resourceId: rid,
|
|
3866
|
+
fileName: (_a = meta == null ? void 0 : meta.name) != null ? _a : ""
|
|
3867
|
+
});
|
|
3868
|
+
fillTileContent(tile, rid, meta, state, actionsEl).catch((err) => {
|
|
3869
|
+
console.error("Failed to render tile:", err);
|
|
3870
|
+
});
|
|
3871
|
+
tilesWrap.appendChild(tile);
|
|
3872
|
+
}
|
|
3873
|
+
if (!isReadonly && !atMax) {
|
|
3874
|
+
const addTile = document.createElement("div");
|
|
3875
|
+
addTile.className = "fb-tile fb-tile-add";
|
|
3876
|
+
addTile.innerHTML = "+";
|
|
3877
|
+
addTile.onclick = openPicker;
|
|
3878
|
+
tilesWrap.appendChild(addTile);
|
|
3879
|
+
if (hasLibrary) {
|
|
3880
|
+
const libraryTile = buildLibraryButton("tile", state, onLibraryPick);
|
|
3881
|
+
tilesWrap.appendChild(libraryTile);
|
|
3882
|
+
}
|
|
3883
|
+
} else if (!isReadonly && atMax) {
|
|
3884
|
+
const chip = document.createElement("div");
|
|
3885
|
+
chip.className = "fb-tile-counter";
|
|
3886
|
+
chip.textContent = t("filesCounter", state, {
|
|
3887
|
+
count: ridList.length,
|
|
3888
|
+
max: maxCount
|
|
3889
|
+
});
|
|
3890
|
+
tilesWrap.appendChild(chip);
|
|
3891
|
+
}
|
|
3892
|
+
container.appendChild(tilesWrap);
|
|
3893
|
+
const subHint = buildSubHint();
|
|
3894
|
+
if (subHint) {
|
|
3895
|
+
const hintEl = document.createElement("div");
|
|
3896
|
+
hintEl.className = "fb-tile-hint";
|
|
3897
|
+
hintEl.textContent = subHint;
|
|
3898
|
+
container.appendChild(hintEl);
|
|
3899
|
+
}
|
|
3053
3900
|
}
|
|
3054
|
-
function
|
|
3055
|
-
var _a, _b;
|
|
3901
|
+
function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
3902
|
+
var _a, _b, _c, _d, _e;
|
|
3056
3903
|
const state = ctx.state;
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
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) => {
|
|
3904
|
+
const fileWrapper = document.createElement("div");
|
|
3905
|
+
fileWrapper.className = "space-y-2";
|
|
3906
|
+
const picker = document.createElement("input");
|
|
3907
|
+
picker.type = "file";
|
|
3908
|
+
picker.name = pathKey;
|
|
3909
|
+
picker.style.display = "none";
|
|
3910
|
+
if (element.accept) {
|
|
3911
|
+
picker.accept = typeof element.accept === "string" ? element.accept : [
|
|
3912
|
+
...(_b = (_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`)) != null ? _b : [],
|
|
3913
|
+
...(_c = element.accept.mime) != null ? _c : []
|
|
3914
|
+
].join(",") || "";
|
|
3915
|
+
}
|
|
3916
|
+
const fileContainer = document.createElement("div");
|
|
3917
|
+
fileContainer.className = "file-preview-container";
|
|
3918
|
+
const initial = ctx.prefill[element.key];
|
|
3919
|
+
const allowedExts = getAllowedExtensions(element.accept);
|
|
3920
|
+
const allowedMimes = getAllowedMimes(element.accept);
|
|
3921
|
+
const maxSizeMB = (_d = element.maxSize) != null ? _d : Infinity;
|
|
3922
|
+
const handlers = {
|
|
3923
|
+
fileUploadHandler() {
|
|
3924
|
+
picker.click();
|
|
3925
|
+
},
|
|
3926
|
+
dragHandler(files) {
|
|
3098
3927
|
if (files.length > 0) {
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
pathKey,
|
|
3928
|
+
handleFileSelect({
|
|
3929
|
+
file: files[0],
|
|
3930
|
+
container: fileContainer,
|
|
3931
|
+
fieldName: pathKey,
|
|
3104
3932
|
state,
|
|
3105
|
-
deps,
|
|
3106
|
-
ctx.instance,
|
|
3107
|
-
allowedExts,
|
|
3933
|
+
deps: buildDeps(),
|
|
3934
|
+
instance: ctx.instance,
|
|
3935
|
+
allowedExtensions: allowedExts,
|
|
3936
|
+
allowedMimes,
|
|
3108
3937
|
maxSizeMB
|
|
3109
|
-
);
|
|
3938
|
+
});
|
|
3110
3939
|
}
|
|
3111
|
-
}
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
pathKey,
|
|
3117
|
-
fileWrapper,
|
|
3118
|
-
state,
|
|
3119
|
-
{
|
|
3120
|
-
picker,
|
|
3121
|
-
fileUploadHandler,
|
|
3122
|
-
dragHandler
|
|
3123
|
-
}
|
|
3124
|
-
);
|
|
3125
|
-
} else {
|
|
3940
|
+
},
|
|
3941
|
+
setupDrop(container) {
|
|
3942
|
+
setupDragAndDrop(container, handlers.dragHandler);
|
|
3943
|
+
},
|
|
3944
|
+
restoreDropzone() {
|
|
3126
3945
|
const hint = makeFieldHint(element, state);
|
|
3946
|
+
fileContainer.className = "file-preview-container w-full max-w-md bg-gray-100 rounded-lg overflow-hidden relative group cursor-pointer";
|
|
3947
|
+
fileContainer.style.height = "128px";
|
|
3127
3948
|
setEmptyFileContainer(fileContainer, state, hint);
|
|
3949
|
+
fileContainer.onclick = handlers.fileUploadHandler;
|
|
3950
|
+
setupDragAndDrop(fileContainer, handlers.dragHandler);
|
|
3951
|
+
},
|
|
3952
|
+
onRemove() {
|
|
3953
|
+
var _a2;
|
|
3954
|
+
const hiddenInput = fileWrapper.querySelector('input[type="hidden"]');
|
|
3955
|
+
const currentRid = hiddenInput == null ? void 0 : hiddenInput.value;
|
|
3956
|
+
if (currentRid) {
|
|
3957
|
+
releaseLocalFileUrl((_a2 = state.resourceIndex.get(currentRid)) == null ? void 0 : _a2.file);
|
|
3958
|
+
}
|
|
3959
|
+
if (hiddenInput) hiddenInput.value = "";
|
|
3960
|
+
handlers.restoreDropzone();
|
|
3128
3961
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
picker
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3962
|
+
};
|
|
3963
|
+
const buildDeps = () => ({
|
|
3964
|
+
picker,
|
|
3965
|
+
fileUploadHandler: handlers.fileUploadHandler,
|
|
3966
|
+
dragHandler: handlers.dragHandler,
|
|
3967
|
+
setupDrop: handlers.setupDrop,
|
|
3968
|
+
onRemove: handlers.onRemove
|
|
3969
|
+
});
|
|
3970
|
+
const renderEmptySingleState = () => {
|
|
3971
|
+
if (state.config.pickExistingFiles && !element.disableLibrary) {
|
|
3972
|
+
fileContainer.className = "file-preview-container";
|
|
3973
|
+
fileContainer.removeAttribute("style");
|
|
3974
|
+
fileContainer.onclick = null;
|
|
3975
|
+
const row = document.createElement("div");
|
|
3976
|
+
row.className = "fb-file-card-row";
|
|
3977
|
+
row.style.cssText = "display:flex;gap:8px;align-items:stretch;";
|
|
3978
|
+
const hint = makeFieldHint(element, state);
|
|
3979
|
+
const uploadCard = buildEmptyDropzone(
|
|
3980
|
+
state,
|
|
3981
|
+
t("clickDragText", state),
|
|
3982
|
+
hint,
|
|
3983
|
+
handlers.fileUploadHandler
|
|
3984
|
+
);
|
|
3985
|
+
uploadCard.style.cssText = "flex:1;min-width:0;height:128px;";
|
|
3986
|
+
setupDragAndDrop(uploadCard, handlers.dragHandler);
|
|
3987
|
+
const libraryBtn = buildLibraryButton("card", state, () => {
|
|
3988
|
+
handleLibraryPickSingle(
|
|
3989
|
+
state,
|
|
3990
|
+
element,
|
|
3136
3991
|
fileContainer,
|
|
3992
|
+
fileWrapper,
|
|
3137
3993
|
pathKey,
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
)
|
|
3144
|
-
|
|
3145
|
-
};
|
|
3146
|
-
fileWrapper.appendChild(fileContainer);
|
|
3147
|
-
fileWrapper.appendChild(picker);
|
|
3148
|
-
wrapper.appendChild(fileWrapper);
|
|
3149
|
-
}
|
|
3150
|
-
}
|
|
3151
|
-
function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
3152
|
-
var _a, _b;
|
|
3153
|
-
const state = ctx.state;
|
|
3154
|
-
if (isElementReadonly(element, state, ctx)) {
|
|
3155
|
-
const rawPrefill = ctx.prefill[element.key];
|
|
3156
|
-
const initialFiles = Array.isArray(rawPrefill) ? rawPrefill : [];
|
|
3157
|
-
const filesWrapper = document.createElement("div");
|
|
3158
|
-
filesWrapper.className = "space-y-2";
|
|
3159
|
-
filesWrapper.dataset.filesWrapper = pathKey;
|
|
3160
|
-
const filesList = document.createElement("div");
|
|
3161
|
-
filesList.className = "files-list";
|
|
3162
|
-
initialFiles.forEach((resourceId) => {
|
|
3163
|
-
const pill = document.createElement("div");
|
|
3164
|
-
pill.className = "resource-pill";
|
|
3165
|
-
pill.dataset.resourceId = resourceId;
|
|
3166
|
-
filesList.appendChild(pill);
|
|
3167
|
-
});
|
|
3168
|
-
filesWrapper.appendChild(filesList);
|
|
3169
|
-
wrapper.appendChild(filesWrapper);
|
|
3170
|
-
const resultsWrapper = document.createElement("div");
|
|
3171
|
-
resultsWrapper.className = "space-y-4";
|
|
3172
|
-
if (initialFiles.length > 0) {
|
|
3173
|
-
initialFiles.forEach((resourceId) => {
|
|
3174
|
-
renderFilePreviewReadonly(resourceId, state).then((filePreview) => {
|
|
3175
|
-
resultsWrapper.appendChild(filePreview);
|
|
3176
|
-
}).catch((err) => {
|
|
3177
|
-
console.error("Failed to render file preview:", err);
|
|
3994
|
+
pathKey,
|
|
3995
|
+
async (rid) => {
|
|
3996
|
+
await renderSingleFileEditTile(fileContainer, rid, state, buildDeps());
|
|
3997
|
+
},
|
|
3998
|
+
ctx.instance
|
|
3999
|
+
).catch((err) => {
|
|
4000
|
+
console.error("Library pick failed:", err);
|
|
3178
4001
|
});
|
|
3179
4002
|
});
|
|
4003
|
+
libraryBtn.style.cssText = "flex:1;min-width:0;";
|
|
4004
|
+
row.appendChild(uploadCard);
|
|
4005
|
+
row.appendChild(libraryBtn);
|
|
4006
|
+
fileContainer.appendChild(row);
|
|
3180
4007
|
} else {
|
|
3181
|
-
|
|
4008
|
+
handlers.restoreDropzone();
|
|
4009
|
+
}
|
|
4010
|
+
};
|
|
4011
|
+
if (initial) {
|
|
4012
|
+
handleInitialFileData(
|
|
4013
|
+
initial,
|
|
4014
|
+
fileContainer,
|
|
4015
|
+
pathKey,
|
|
4016
|
+
fileWrapper,
|
|
4017
|
+
state,
|
|
4018
|
+
buildDeps()
|
|
4019
|
+
);
|
|
4020
|
+
const prefillMeta = state.resourceIndex.get(initial);
|
|
4021
|
+
if ((_e = prefillMeta == null ? void 0 : prefillMeta.type) == null ? void 0 : _e.startsWith("video/")) {
|
|
4022
|
+
fileContainer.onclick = handlers.fileUploadHandler;
|
|
4023
|
+
setupDragAndDrop(fileContainer, handlers.dragHandler);
|
|
3182
4024
|
}
|
|
3183
|
-
wrapper.appendChild(resultsWrapper);
|
|
3184
4025
|
} else {
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
4026
|
+
renderEmptySingleState();
|
|
4027
|
+
}
|
|
4028
|
+
picker.onchange = () => {
|
|
4029
|
+
if (picker.files && picker.files.length > 0) {
|
|
4030
|
+
handleFileSelect({
|
|
4031
|
+
file: picker.files[0],
|
|
4032
|
+
container: fileContainer,
|
|
4033
|
+
fieldName: pathKey,
|
|
3189
4034
|
state,
|
|
3190
|
-
(
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
4035
|
+
deps: buildDeps(),
|
|
4036
|
+
instance: ctx.instance,
|
|
4037
|
+
allowedExtensions: allowedExts,
|
|
4038
|
+
allowedMimes,
|
|
4039
|
+
maxSizeMB
|
|
4040
|
+
});
|
|
4041
|
+
}
|
|
4042
|
+
};
|
|
4043
|
+
fileWrapper.appendChild(fileContainer);
|
|
4044
|
+
fileWrapper.appendChild(picker);
|
|
4045
|
+
wrapper.appendChild(fileWrapper);
|
|
4046
|
+
}
|
|
4047
|
+
function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
|
|
4048
|
+
var _a, _b, _c, _d;
|
|
4049
|
+
const state = ctx.state;
|
|
4050
|
+
const filesWrapper = document.createElement("div");
|
|
4051
|
+
filesWrapper.className = "space-y-2";
|
|
4052
|
+
filesWrapper.dataset.filesWrapper = pathKey;
|
|
4053
|
+
const filesPicker = document.createElement("input");
|
|
4054
|
+
filesPicker.type = "file";
|
|
4055
|
+
filesPicker.name = pathKey;
|
|
4056
|
+
filesPicker.multiple = true;
|
|
4057
|
+
filesPicker.style.display = "none";
|
|
4058
|
+
if (element.accept) {
|
|
4059
|
+
filesPicker.accept = typeof element.accept === "string" ? element.accept : [
|
|
4060
|
+
...(_b = (_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`)) != null ? _b : [],
|
|
4061
|
+
...(_c = element.accept.mime) != null ? _c : []
|
|
4062
|
+
].join(",") || "";
|
|
4063
|
+
}
|
|
4064
|
+
const filesContainer = document.createElement("div");
|
|
4065
|
+
filesContainer.className = "files-list-wrapper";
|
|
4066
|
+
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);";
|
|
4067
|
+
const list = document.createElement("div");
|
|
4068
|
+
list.className = "files-list";
|
|
4069
|
+
const initialFiles = ctx.prefill[element.key] || [];
|
|
4070
|
+
addPrefillFilesToIndex(initialFiles, state.resourceIndex);
|
|
4071
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
|
|
4072
|
+
const filesFieldHint = makeFieldHint(element, state);
|
|
4073
|
+
const filesConstraints = {
|
|
4074
|
+
maxCount: Infinity,
|
|
4075
|
+
allowedExtensions: getAllowedExtensions(element.accept),
|
|
4076
|
+
allowedMimes: getAllowedMimes(element.accept),
|
|
4077
|
+
maxSize: (_d = element.maxSize) != null ? _d : Infinity
|
|
4078
|
+
};
|
|
4079
|
+
filesContainer.appendChild(list);
|
|
4080
|
+
filesWrapper.appendChild(filesPicker);
|
|
4081
|
+
filesWrapper.appendChild(filesContainer);
|
|
4082
|
+
wrapper.appendChild(filesWrapper);
|
|
4083
|
+
const onLibraryPickFiles = state.config.pickExistingFiles && !element.disableLibrary ? () => {
|
|
4084
|
+
handleLibraryPickMulti(
|
|
3227
4085
|
state,
|
|
3228
|
-
|
|
3229
|
-
|
|
4086
|
+
element,
|
|
4087
|
+
filesWrapper,
|
|
3230
4088
|
pathKey,
|
|
3231
|
-
ctx.instance
|
|
3232
|
-
);
|
|
3233
|
-
setupFilesPickerHandler(
|
|
3234
|
-
filesPicker,
|
|
3235
4089
|
initialFiles,
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
filesConstraints,
|
|
3239
|
-
pathKey,
|
|
4090
|
+
Infinity,
|
|
4091
|
+
updateFilesList,
|
|
3240
4092
|
ctx.instance
|
|
3241
|
-
)
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
4093
|
+
).catch((err) => {
|
|
4094
|
+
console.error("Library pick failed:", err);
|
|
4095
|
+
});
|
|
4096
|
+
} : null;
|
|
4097
|
+
function updateFilesList() {
|
|
4098
|
+
const currentlyReadonly = isElementReadonly(element, state);
|
|
4099
|
+
renderResourcePills({
|
|
4100
|
+
container: list,
|
|
4101
|
+
rids: initialFiles,
|
|
4102
|
+
state,
|
|
4103
|
+
onRemove: currentlyReadonly ? null : (ridToRemove) => {
|
|
4104
|
+
var _a2;
|
|
4105
|
+
releaseLocalFileUrl((_a2 = state.resourceIndex.get(ridToRemove)) == null ? void 0 : _a2.file);
|
|
4106
|
+
const index = initialFiles.indexOf(ridToRemove);
|
|
4107
|
+
if (index > -1) initialFiles.splice(index, 1);
|
|
4108
|
+
updateFilesList();
|
|
4109
|
+
},
|
|
4110
|
+
hint: filesFieldHint,
|
|
4111
|
+
isReadonly: currentlyReadonly,
|
|
4112
|
+
onLibraryPick: currentlyReadonly ? null : onLibraryPickFiles
|
|
4113
|
+
});
|
|
3246
4114
|
}
|
|
4115
|
+
updateFilesList();
|
|
4116
|
+
setupFilesDropHandler(
|
|
4117
|
+
filesContainer,
|
|
4118
|
+
initialFiles,
|
|
4119
|
+
state,
|
|
4120
|
+
updateFilesList,
|
|
4121
|
+
filesConstraints,
|
|
4122
|
+
pathKey,
|
|
4123
|
+
ctx.instance
|
|
4124
|
+
);
|
|
4125
|
+
setupFilesPickerHandler(
|
|
4126
|
+
filesPicker,
|
|
4127
|
+
initialFiles,
|
|
4128
|
+
state,
|
|
4129
|
+
updateFilesList,
|
|
4130
|
+
filesConstraints,
|
|
4131
|
+
pathKey,
|
|
4132
|
+
ctx.instance
|
|
4133
|
+
);
|
|
3247
4134
|
}
|
|
3248
|
-
function
|
|
3249
|
-
var _a, _b, _c, _d;
|
|
4135
|
+
function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
4136
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3250
4137
|
const state = ctx.state;
|
|
3251
4138
|
const minFiles = (_a = element.minCount) != null ? _a : 0;
|
|
3252
4139
|
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
|
-
|
|
3291
|
-
|
|
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,
|
|
3327
|
-
initialFiles,
|
|
4140
|
+
const filesWrapper = document.createElement("div");
|
|
4141
|
+
filesWrapper.className = "space-y-2";
|
|
4142
|
+
filesWrapper.dataset.filesWrapper = pathKey;
|
|
4143
|
+
const filesPicker = document.createElement("input");
|
|
4144
|
+
filesPicker.type = "file";
|
|
4145
|
+
filesPicker.name = pathKey;
|
|
4146
|
+
filesPicker.multiple = true;
|
|
4147
|
+
filesPicker.style.display = "none";
|
|
4148
|
+
if (element.accept) {
|
|
4149
|
+
filesPicker.accept = typeof element.accept === "string" ? element.accept : [
|
|
4150
|
+
...(_d = (_c = element.accept.extensions) == null ? void 0 : _c.map((ext) => `.${ext}`)) != null ? _d : [],
|
|
4151
|
+
...(_e = element.accept.mime) != null ? _e : []
|
|
4152
|
+
].join(",") || "";
|
|
4153
|
+
}
|
|
4154
|
+
const filesContainer = document.createElement("div");
|
|
4155
|
+
filesContainer.className = "files-list-wrapper";
|
|
4156
|
+
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);";
|
|
4157
|
+
const list = document.createElement("div");
|
|
4158
|
+
list.className = "files-list";
|
|
4159
|
+
filesWrapper.appendChild(filesPicker);
|
|
4160
|
+
filesWrapper.appendChild(filesContainer);
|
|
4161
|
+
filesContainer.appendChild(list);
|
|
4162
|
+
const initialFiles = Array.isArray(ctx.prefill[element.key]) ? [...ctx.prefill[element.key]] : [];
|
|
4163
|
+
addPrefillFilesToIndex(initialFiles, state.resourceIndex);
|
|
4164
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
|
|
4165
|
+
const multipleFilesHint = makeFieldHint(element, state);
|
|
4166
|
+
const multipleConstraints = {
|
|
4167
|
+
maxCount: maxFiles,
|
|
4168
|
+
allowedExtensions: getAllowedExtensions(element.accept),
|
|
4169
|
+
allowedMimes: getAllowedMimes(element.accept),
|
|
4170
|
+
maxSize: (_f = element.maxSize) != null ? _f : Infinity
|
|
4171
|
+
};
|
|
4172
|
+
const buildCountInfo = () => {
|
|
4173
|
+
const countText = initialFiles.length === 1 ? t("fileCountSingle", state, { count: initialFiles.length }) : t("fileCountPlural", state, { count: initialFiles.length });
|
|
4174
|
+
const minMaxText = minFiles > 0 || maxFiles < Infinity ? ` ${t("fileCountRange", state, { min: minFiles, max: maxFiles })}` : "";
|
|
4175
|
+
return countText + minMaxText;
|
|
4176
|
+
};
|
|
4177
|
+
const onLibraryPickMultiple = state.config.pickExistingFiles && !element.disableLibrary ? () => {
|
|
4178
|
+
handleLibraryPickMulti(
|
|
3328
4179
|
state,
|
|
3329
|
-
|
|
3330
|
-
|
|
4180
|
+
element,
|
|
4181
|
+
filesWrapper,
|
|
3331
4182
|
pathKey,
|
|
3332
|
-
ctx.instance
|
|
3333
|
-
);
|
|
3334
|
-
setupFilesPickerHandler(
|
|
3335
|
-
filesPicker,
|
|
3336
4183
|
initialFiles,
|
|
3337
|
-
|
|
4184
|
+
maxFiles,
|
|
3338
4185
|
updateFilesDisplay,
|
|
3339
|
-
multipleConstraints,
|
|
3340
|
-
pathKey,
|
|
3341
4186
|
ctx.instance
|
|
4187
|
+
).catch((err) => {
|
|
4188
|
+
console.error("Library pick failed:", err);
|
|
4189
|
+
});
|
|
4190
|
+
} : null;
|
|
4191
|
+
const updateFilesDisplay = () => {
|
|
4192
|
+
const currentlyReadonly = isElementReadonly(element, state);
|
|
4193
|
+
renderResourcePills({
|
|
4194
|
+
container: list,
|
|
4195
|
+
rids: initialFiles,
|
|
4196
|
+
state,
|
|
4197
|
+
onRemove: currentlyReadonly ? null : (index) => {
|
|
4198
|
+
var _a2;
|
|
4199
|
+
releaseLocalFileUrl((_a2 = state.resourceIndex.get(index)) == null ? void 0 : _a2.file);
|
|
4200
|
+
initialFiles.splice(initialFiles.indexOf(index), 1);
|
|
4201
|
+
updateFilesDisplay();
|
|
4202
|
+
},
|
|
4203
|
+
hint: multipleFilesHint,
|
|
4204
|
+
countInfo: buildCountInfo(),
|
|
4205
|
+
maxCount: maxFiles < Infinity ? maxFiles : void 0,
|
|
4206
|
+
isReadonly: currentlyReadonly,
|
|
4207
|
+
onLibraryPick: currentlyReadonly ? null : onLibraryPickMultiple
|
|
4208
|
+
});
|
|
4209
|
+
};
|
|
4210
|
+
setupFilesDropHandler(
|
|
4211
|
+
filesContainer,
|
|
4212
|
+
initialFiles,
|
|
4213
|
+
state,
|
|
4214
|
+
updateFilesDisplay,
|
|
4215
|
+
multipleConstraints,
|
|
4216
|
+
pathKey,
|
|
4217
|
+
ctx.instance
|
|
4218
|
+
);
|
|
4219
|
+
setupFilesPickerHandler(
|
|
4220
|
+
filesPicker,
|
|
4221
|
+
initialFiles,
|
|
4222
|
+
state,
|
|
4223
|
+
updateFilesDisplay,
|
|
4224
|
+
multipleConstraints,
|
|
4225
|
+
pathKey,
|
|
4226
|
+
ctx.instance
|
|
4227
|
+
);
|
|
4228
|
+
updateFilesDisplay();
|
|
4229
|
+
wrapper.appendChild(filesWrapper);
|
|
4230
|
+
}
|
|
4231
|
+
|
|
4232
|
+
// src/components/file/validate.ts
|
|
4233
|
+
function readMultiFileResourceIds(scopeRoot, fullKey) {
|
|
4234
|
+
const wrapper = scopeRoot.querySelector(
|
|
4235
|
+
`[data-files-wrapper="${fullKey}"]`
|
|
4236
|
+
);
|
|
4237
|
+
if (!wrapper) return [];
|
|
4238
|
+
const encoded = wrapper.dataset.resourceIds;
|
|
4239
|
+
if (encoded === void 0) {
|
|
4240
|
+
throw new Error(
|
|
4241
|
+
`readMultiFileResourceIds: [data-files-wrapper="${fullKey}"] is missing data-resource-ids attribute. This is a render bug.`
|
|
3342
4242
|
);
|
|
3343
|
-
updateFilesDisplay();
|
|
3344
|
-
wrapper.appendChild(filesWrapper);
|
|
3345
4243
|
}
|
|
4244
|
+
const parsed = JSON.parse(encoded);
|
|
4245
|
+
if (!Array.isArray(parsed)) {
|
|
4246
|
+
throw new Error(
|
|
4247
|
+
`readMultiFileResourceIds: data-resource-ids on [data-files-wrapper="${fullKey}"] is not a JSON array. Got: ${encoded}`
|
|
4248
|
+
);
|
|
4249
|
+
}
|
|
4250
|
+
return parsed;
|
|
3346
4251
|
}
|
|
3347
|
-
function
|
|
3348
|
-
var _a;
|
|
3349
|
-
const
|
|
3350
|
-
const
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
4252
|
+
function validateFileCount(key, resourceIds, element, state, errors) {
|
|
4253
|
+
var _a, _b;
|
|
4254
|
+
const minFiles = "minCount" in element ? (_a = element.minCount) != null ? _a : 0 : 0;
|
|
4255
|
+
const maxFiles = "maxCount" in element ? (_b = element.maxCount) != null ? _b : Infinity : Infinity;
|
|
4256
|
+
if (element.required && resourceIds.length === 0) {
|
|
4257
|
+
errors.push(`${key}: ${t("required", state)}`);
|
|
4258
|
+
}
|
|
4259
|
+
if (resourceIds.length < minFiles) {
|
|
4260
|
+
errors.push(`${key}: ${t("minFiles", state, { min: minFiles })}`);
|
|
4261
|
+
}
|
|
4262
|
+
if (resourceIds.length > maxFiles) {
|
|
4263
|
+
errors.push(`${key}: ${t("maxFiles", state, { max: maxFiles })}`);
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
4266
|
+
function validateFileTypes(key, resourceIds, element, state, errors) {
|
|
4267
|
+
var _a, _b;
|
|
4268
|
+
const acceptField = "accept" in element ? element.accept : void 0;
|
|
4269
|
+
const allowedExtensions = getAllowedExtensions(acceptField);
|
|
4270
|
+
const allowedMimes = getAllowedMimes(acceptField);
|
|
4271
|
+
if (allowedExtensions.length === 0 && allowedMimes.length === 0) return;
|
|
4272
|
+
const formats = allowedExtensions.join(", ");
|
|
4273
|
+
const mimes = allowedMimes.join(", ");
|
|
4274
|
+
for (const rid of resourceIds) {
|
|
4275
|
+
const meta = state.resourceIndex.get(rid);
|
|
4276
|
+
const fileName = (_a = meta == null ? void 0 : meta.name) != null ? _a : rid;
|
|
4277
|
+
if (allowedExtensions.length > 0 && !isFileExtensionAllowed(fileName, allowedExtensions)) {
|
|
4278
|
+
errors.push(
|
|
4279
|
+
`${key}: ${t("invalidFileExtension", state, { name: fileName, formats })}`
|
|
4280
|
+
);
|
|
4281
|
+
continue;
|
|
3366
4282
|
}
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
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)) {
|
|
4283
|
+
if (allowedMimes.length > 0 && !(meta == null ? void 0 : meta.inferredFromExtension)) {
|
|
4284
|
+
const mimeType = (_b = meta == null ? void 0 : meta.type) != null ? _b : "";
|
|
4285
|
+
if (!isMimeAllowed(mimeType, allowedMimes)) {
|
|
3380
4286
|
errors.push(
|
|
3381
|
-
`${
|
|
4287
|
+
`${key}: ${t("invalidFileMime", state, { name: fileName, type: mimeType, mimes })}`
|
|
3382
4288
|
);
|
|
3383
4289
|
}
|
|
3384
4290
|
}
|
|
3385
|
-
}
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
);
|
|
3399
|
-
}
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
function validateFileSizes(key, resourceIds, element, state, errors) {
|
|
4294
|
+
var _a;
|
|
4295
|
+
const maxSizeMB = "maxSize" in element ? (_a = element.maxSize) != null ? _a : Infinity : Infinity;
|
|
4296
|
+
if (maxSizeMB === Infinity) return;
|
|
4297
|
+
for (const rid of resourceIds) {
|
|
4298
|
+
const meta = state.resourceIndex.get(rid);
|
|
4299
|
+
if (!meta) continue;
|
|
4300
|
+
if (meta.size > maxSizeMB * 1024 * 1024) {
|
|
4301
|
+
errors.push(
|
|
4302
|
+
`${key}: ${t("fileTooLarge", state, { name: meta.name, maxSize: maxSizeMB })}`
|
|
4303
|
+
);
|
|
3400
4304
|
}
|
|
3401
|
-
}
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
function validateMultiFile(element, key, context) {
|
|
4308
|
+
const { scopeRoot, skipValidation, path, state } = context;
|
|
4309
|
+
const errors = [];
|
|
4310
|
+
const fullKey = pathJoin(path, key);
|
|
4311
|
+
const resourceIds = readMultiFileResourceIds(scopeRoot, fullKey);
|
|
4312
|
+
if (!skipValidation) {
|
|
4313
|
+
validateFileCount(key, resourceIds, element, state, errors);
|
|
4314
|
+
validateFileTypes(key, resourceIds, element, state, errors);
|
|
4315
|
+
validateFileSizes(key, resourceIds, element, state, errors);
|
|
4316
|
+
}
|
|
4317
|
+
return { value: resourceIds, errors };
|
|
4318
|
+
}
|
|
4319
|
+
function validateSingleFile(element, key, context) {
|
|
4320
|
+
var _a;
|
|
4321
|
+
const { scopeRoot, skipValidation, state } = context;
|
|
4322
|
+
const errors = [];
|
|
4323
|
+
const input = scopeRoot.querySelector(
|
|
4324
|
+
`input[name$="${key}"][type="hidden"]`
|
|
4325
|
+
);
|
|
4326
|
+
const rid = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
4327
|
+
if (!skipValidation && element.required && rid === "") {
|
|
4328
|
+
errors.push(`${key}: ${t("required", state)}`);
|
|
4329
|
+
return { value: null, errors };
|
|
4330
|
+
}
|
|
4331
|
+
if (!skipValidation && rid !== "") {
|
|
4332
|
+
validateFileTypes(key, [rid], element, state, errors);
|
|
4333
|
+
validateFileSizes(key, [rid], element, state, errors);
|
|
4334
|
+
}
|
|
4335
|
+
return { value: rid || null, errors };
|
|
4336
|
+
}
|
|
4337
|
+
function validateFileElement(element, key, context) {
|
|
4338
|
+
const isMultipleField = element.type === "files" || "multiple" in element && Boolean(element.multiple);
|
|
3402
4339
|
if (isMultipleField) {
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
4340
|
+
return validateMultiFile(element, key, context);
|
|
4341
|
+
}
|
|
4342
|
+
return validateSingleFile(element, key, context);
|
|
4343
|
+
}
|
|
4344
|
+
|
|
4345
|
+
// src/components/file/render-readonly.ts
|
|
4346
|
+
function renderFileElementReadonly(element, ctx, wrapper, pathKey) {
|
|
4347
|
+
const state = ctx.state;
|
|
4348
|
+
const rawInitial = ctx.prefill[element.key];
|
|
4349
|
+
const initial = typeof rawInitial === "string" ? rawInitial : "";
|
|
4350
|
+
if (initial) {
|
|
4351
|
+
addPrefillFilesToIndex([initial], state.resourceIndex);
|
|
4352
|
+
const hiddenInput = document.createElement("input");
|
|
4353
|
+
hiddenInput.type = "hidden";
|
|
4354
|
+
hiddenInput.name = pathKey;
|
|
4355
|
+
hiddenInput.value = initial;
|
|
4356
|
+
wrapper.appendChild(hiddenInput);
|
|
4357
|
+
renderFilePreviewReadonly(initial, state).then((filePreview) => {
|
|
4358
|
+
wrapper.appendChild(filePreview);
|
|
4359
|
+
}).catch((err) => {
|
|
4360
|
+
console.error("Failed to render file preview:", err);
|
|
4361
|
+
wrapper.appendChild(buildEmptyReadonlyTile(state));
|
|
4362
|
+
});
|
|
3422
4363
|
} else {
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
}
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
4364
|
+
wrapper.appendChild(buildEmptyReadonlyTile(state));
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4367
|
+
function buildEmptyReadonlyTile(state) {
|
|
4368
|
+
const emptyState = document.createElement("div");
|
|
4369
|
+
emptyState.style.cssText = `
|
|
4370
|
+
width:${TILE_SIZE};
|
|
4371
|
+
height:${TILE_SIZE};
|
|
4372
|
+
display:flex;
|
|
4373
|
+
align-items:center;
|
|
4374
|
+
justify-content:center;
|
|
4375
|
+
background:var(--fb-file-upload-bg-color,#f3f4f6);
|
|
4376
|
+
border-radius:var(--fb-border-radius,0.5rem);
|
|
4377
|
+
border:1px solid var(--fb-file-upload-border-color,#d1d5db);
|
|
4378
|
+
`;
|
|
4379
|
+
emptyState.innerHTML = `<div style="font-size:11px;text-align:center;color:var(--fb-text-secondary-color,#6b7280);">${escapeHtml(t("noFileSelected", state))}</div>`;
|
|
4380
|
+
return emptyState;
|
|
4381
|
+
}
|
|
4382
|
+
function renderMultiFileReadonly(rids, state, wrapper, pathKey, marginTop) {
|
|
4383
|
+
addPrefillFilesToIndex(rids, state.resourceIndex);
|
|
4384
|
+
const filesWrapper = document.createElement("div");
|
|
4385
|
+
filesWrapper.dataset.filesWrapper = pathKey;
|
|
4386
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(rids);
|
|
4387
|
+
wrapper.appendChild(filesWrapper);
|
|
4388
|
+
if (rids.length === 0) {
|
|
4389
|
+
const emptyEl = document.createElement("div");
|
|
4390
|
+
emptyEl.className = "fb-tile-empty-text";
|
|
4391
|
+
emptyEl.textContent = t("noFilesSelected", state);
|
|
4392
|
+
filesWrapper.appendChild(emptyEl);
|
|
4393
|
+
return;
|
|
4394
|
+
}
|
|
4395
|
+
const tilesWrap = document.createElement("div");
|
|
4396
|
+
tilesWrap.style.cssText = `display:flex;flex-wrap:wrap;gap:6px;${marginTop ? `margin-top:${marginTop};` : ""}`;
|
|
4397
|
+
filesWrapper.appendChild(tilesWrap);
|
|
4398
|
+
const placeholders = rids.map(() => {
|
|
4399
|
+
const placeholder = document.createElement("div");
|
|
4400
|
+
placeholder.style.cssText = `width:${TILE_SIZE};height:${TILE_SIZE};`;
|
|
4401
|
+
tilesWrap.appendChild(placeholder);
|
|
4402
|
+
return placeholder;
|
|
4403
|
+
});
|
|
4404
|
+
for (let i = 0; i < rids.length; i++) {
|
|
4405
|
+
const resourceId = rids[i];
|
|
4406
|
+
const placeholder = placeholders[i];
|
|
4407
|
+
renderFilePreviewReadonly(resourceId, state).then((tileEl) => {
|
|
4408
|
+
placeholder.replaceWith(tileEl);
|
|
4409
|
+
}).catch((err) => {
|
|
4410
|
+
console.error("Failed to render readonly tile:", err);
|
|
4411
|
+
});
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
function renderFilesElementReadonly(element, ctx, wrapper, pathKey) {
|
|
4415
|
+
const rawPrefill = ctx.prefill[element.key];
|
|
4416
|
+
const initialFiles = Array.isArray(rawPrefill) ? rawPrefill : [];
|
|
4417
|
+
renderMultiFileReadonly(initialFiles, ctx.state, wrapper, pathKey);
|
|
4418
|
+
}
|
|
4419
|
+
function renderMultipleFileElementReadonly(element, ctx, wrapper, pathKey) {
|
|
4420
|
+
const rawPrefill = ctx.prefill[element.key];
|
|
4421
|
+
const initialFiles = Array.isArray(rawPrefill) ? rawPrefill : [];
|
|
4422
|
+
renderMultiFileReadonly(initialFiles, ctx.state, wrapper, pathKey, "4px");
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
// src/components/file.ts
|
|
4426
|
+
function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
4427
|
+
if (isElementReadonly(element, ctx.state, ctx)) {
|
|
4428
|
+
renderFileElementReadonly(element, ctx, wrapper, pathKey);
|
|
4429
|
+
} else {
|
|
4430
|
+
renderFileElementEdit(element, ctx, wrapper, pathKey);
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
4434
|
+
if (isElementReadonly(element, ctx.state, ctx)) {
|
|
4435
|
+
renderFilesElementReadonly(element, ctx, wrapper, pathKey);
|
|
4436
|
+
} else {
|
|
4437
|
+
renderFilesElementEdit(element, ctx, wrapper, pathKey);
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
|
|
4441
|
+
if (isElementReadonly(element, ctx.state, ctx)) {
|
|
4442
|
+
renderMultipleFileElementReadonly(element, ctx, wrapper, pathKey);
|
|
4443
|
+
} else {
|
|
4444
|
+
renderMultipleFileElementEdit(element, ctx, wrapper, pathKey);
|
|
3436
4445
|
}
|
|
3437
4446
|
}
|
|
3438
4447
|
function updateFileField(element, fieldPath, value, context) {
|
|
3439
|
-
var _a;
|
|
3440
4448
|
const { scopeRoot, state } = context;
|
|
3441
|
-
if ("multiple" in element && element.multiple) {
|
|
4449
|
+
if (element.type === "files" || "multiple" in element && element.multiple) {
|
|
3442
4450
|
if (!Array.isArray(value)) {
|
|
3443
4451
|
console.warn(
|
|
3444
4452
|
`updateFileField: Expected array for multiple file field "${fieldPath}", got ${typeof value}`
|
|
@@ -3446,32 +4454,20 @@ function updateFileField(element, fieldPath, value, context) {
|
|
|
3446
4454
|
return;
|
|
3447
4455
|
}
|
|
3448
4456
|
value.forEach((resourceId) => {
|
|
3449
|
-
var _a2;
|
|
3450
4457
|
if (resourceId && typeof resourceId === "string") {
|
|
3451
|
-
|
|
3452
|
-
const filename = resourceId.split("/").pop() || "file";
|
|
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
|
-
});
|
|
3469
|
-
}
|
|
4458
|
+
seedInferredResource(resourceId, state.resourceIndex);
|
|
3470
4459
|
}
|
|
3471
4460
|
});
|
|
3472
|
-
|
|
3473
|
-
`
|
|
4461
|
+
const filesWrapper = scopeRoot.querySelector(
|
|
4462
|
+
`[data-files-wrapper="${fieldPath}"]`
|
|
3474
4463
|
);
|
|
4464
|
+
if (filesWrapper) {
|
|
4465
|
+
filesWrapper.dataset.resourceIds = JSON.stringify(value);
|
|
4466
|
+
} else {
|
|
4467
|
+
console.warn(
|
|
4468
|
+
`updateFileField: [data-files-wrapper="${fieldPath}"] not found in DOM; data-resource-ids not updated`
|
|
4469
|
+
);
|
|
4470
|
+
}
|
|
3475
4471
|
} else {
|
|
3476
4472
|
const hiddenInput = scopeRoot.querySelector(
|
|
3477
4473
|
`input[name="${fieldPath}"][type="hidden"]`
|
|
@@ -3484,25 +4480,7 @@ function updateFileField(element, fieldPath, value, context) {
|
|
|
3484
4480
|
}
|
|
3485
4481
|
hiddenInput.value = value != null ? String(value) : "";
|
|
3486
4482
|
if (value && typeof value === "string") {
|
|
3487
|
-
|
|
3488
|
-
const filename = value.split("/").pop() || "file";
|
|
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
|
-
});
|
|
3505
|
-
}
|
|
4483
|
+
seedInferredResource(value, state.resourceIndex);
|
|
3506
4484
|
console.info(
|
|
3507
4485
|
`updateFileField: File field "${fieldPath}" updated. Preview update requires re-render.`
|
|
3508
4486
|
);
|
|
@@ -8245,6 +9223,7 @@ var defaultConfig = {
|
|
|
8245
9223
|
enableFilePreview: true,
|
|
8246
9224
|
maxPreviewSize: "200px",
|
|
8247
9225
|
readonly: false,
|
|
9226
|
+
pickExistingFiles: null,
|
|
8248
9227
|
parseTableFile: null,
|
|
8249
9228
|
locale: "en",
|
|
8250
9229
|
translations: {
|
|
@@ -8256,6 +9235,8 @@ var defaultConfig = {
|
|
|
8256
9235
|
noFileSelected: "No file selected",
|
|
8257
9236
|
noFilesSelected: "No files selected",
|
|
8258
9237
|
downloadButton: "Download",
|
|
9238
|
+
downloadFile: "Download",
|
|
9239
|
+
openInNewTab: "Open in new tab",
|
|
8259
9240
|
changeButton: "Change",
|
|
8260
9241
|
placeholderText: "Enter text",
|
|
8261
9242
|
previewAlt: "Preview",
|
|
@@ -8277,6 +9258,12 @@ var defaultConfig = {
|
|
|
8277
9258
|
fileCountSingle: "{count} file",
|
|
8278
9259
|
fileCountPlural: "{count} files",
|
|
8279
9260
|
fileCountRange: "({min}-{max})",
|
|
9261
|
+
uploadingFile: "Uploading\u2026",
|
|
9262
|
+
filesCounter: "{count}/{max}",
|
|
9263
|
+
fromLibrary: "From library",
|
|
9264
|
+
libraryEmpty: "Library is empty",
|
|
9265
|
+
libraryHint: "Choose from previously uploaded files",
|
|
9266
|
+
pickerError: "Failed to load files from library",
|
|
8280
9267
|
// Validation errors
|
|
8281
9268
|
required: "Required",
|
|
8282
9269
|
minItems: "Minimum {min} items required",
|
|
@@ -8292,6 +9279,7 @@ var defaultConfig = {
|
|
|
8292
9279
|
minFiles: "Minimum {min} files required",
|
|
8293
9280
|
maxFiles: "Maximum {max} files allowed",
|
|
8294
9281
|
invalidFileExtension: 'File "{name}" has unsupported format. Allowed: {formats}',
|
|
9282
|
+
invalidFileMime: 'File "{name}": file type {type} not allowed (allowed: {mimes})',
|
|
8295
9283
|
fileTooLarge: 'File "{name}" exceeds maximum size of {maxSize}MB',
|
|
8296
9284
|
filesLimitExceeded: "{skipped} file(s) skipped: maximum {max} files allowed",
|
|
8297
9285
|
unsupportedFieldType: "Unsupported field type: {type}",
|
|
@@ -8318,6 +9306,8 @@ var defaultConfig = {
|
|
|
8318
9306
|
noFileSelected: "\u0424\u0430\u0439\u043B \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D",
|
|
8319
9307
|
noFilesSelected: "\u041D\u0435\u0442 \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
8320
9308
|
downloadButton: "\u0421\u043A\u0430\u0447\u0430\u0442\u044C",
|
|
9309
|
+
downloadFile: "\u0421\u043A\u0430\u0447\u0430\u0442\u044C",
|
|
9310
|
+
openInNewTab: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 \u043D\u043E\u0432\u043E\u0439 \u0432\u043A\u043B\u0430\u0434\u043A\u0435",
|
|
8321
9311
|
changeButton: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C",
|
|
8322
9312
|
placeholderText: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442",
|
|
8323
9313
|
previewAlt: "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440",
|
|
@@ -8339,6 +9329,12 @@ var defaultConfig = {
|
|
|
8339
9329
|
fileCountSingle: "{count} \u0444\u0430\u0439\u043B",
|
|
8340
9330
|
fileCountPlural: "{count} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
8341
9331
|
fileCountRange: "({min}-{max})",
|
|
9332
|
+
uploadingFile: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430\u2026",
|
|
9333
|
+
filesCounter: "{count}/{max}",
|
|
9334
|
+
fromLibrary: "\u0418\u0437 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438",
|
|
9335
|
+
libraryEmpty: "\u0411\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0430 \u043F\u0443\u0441\u0442\u0430",
|
|
9336
|
+
libraryHint: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0438\u0437 \u0440\u0430\u043D\u0435\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u043D\u044B\u0445 \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
9337
|
+
pickerError: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0444\u0430\u0439\u043B\u044B \u0438\u0437 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438",
|
|
8342
9338
|
// Validation errors
|
|
8343
9339
|
required: "\u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u043F\u043E\u043B\u0435",
|
|
8344
9340
|
minItems: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432",
|
|
@@ -8354,6 +9350,7 @@ var defaultConfig = {
|
|
|
8354
9350
|
minFiles: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
8355
9351
|
maxFiles: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
8356
9352
|
invalidFileExtension: '\u0424\u0430\u0439\u043B "{name}" \u0438\u043C\u0435\u0435\u0442 \u043D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442. \u0414\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0435: {formats}',
|
|
9353
|
+
invalidFileMime: '\u0424\u0430\u0439\u043B "{name}": \u0442\u0438\u043F \u0444\u0430\u0439\u043B\u0430 {type} \u043D\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D (\u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u044B: {mimes})',
|
|
8357
9354
|
fileTooLarge: '\u0424\u0430\u0439\u043B "{name}" \u043F\u0440\u0435\u0432\u044B\u0448\u0430\u0435\u0442 \u043C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0440\u0430\u0437\u043C\u0435\u0440 {maxSize}\u041C\u0411',
|
|
8358
9355
|
filesLimitExceeded: "{skipped} \u0444\u0430\u0439\u043B(\u043E\u0432) \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u043E: \u043C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
8359
9356
|
unsupportedFieldType: "\u041D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0442\u0438\u043F \u043F\u043E\u043B\u044F: {type}",
|