vitepress-plugin-filelist 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import "../style.css";
2
- import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, toDisplayString, unref, useId, vModelSelect, vModelText, watch, withDirectives, withKeys, withModifiers } from "vue";
2
+ import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, reactive, ref, renderList, toDisplayString, unref, useId, vModelSelect, vModelText, watch, withDirectives, withKeys, withModifiers } from "vue";
3
3
  // src/client/utils.ts
4
4
  function joinPath(parent, name) {
5
5
  return (parent.endsWith("/") ? parent : `${parent}/`) + name;
@@ -520,6 +520,79 @@ function createDemoApi(delay = 220) {
520
520
  clearCache: () => {}
521
521
  };
522
522
  }
523
+ // src/client/ConfirmDialog.vue
524
+ const _hoisted_1$3 = ["aria-label"];
525
+ const _hoisted_2$3 = {
526
+ key: 0,
527
+ class: "fl-confirm-panel"
528
+ };
529
+ const _hoisted_3$2 = { class: "fl-confirm-title" };
530
+ const _hoisted_4$2 = { class: "fl-confirm-text" };
531
+ const _hoisted_5$2 = {
532
+ key: 0,
533
+ class: "fl-confirm-text"
534
+ };
535
+ const _hoisted_6$2 = { class: "fl-confirm-actions" };
536
+ const _sfc_main$3 = /*@__PURE__*/ defineComponent({
537
+ __name: "ConfirmDialog",
538
+ props: {
539
+ open: { type: Boolean },
540
+ title: {},
541
+ text: {},
542
+ tip: {},
543
+ confirmLabel: {},
544
+ cancelLabel: {}
545
+ },
546
+ emits: ["confirm", "close"],
547
+ setup(__props, { emit: __emit }) {
548
+ /** Small native <dialog> for a yes / no question or a notice (bottom sheet on phones). */
549
+ const props = __props;
550
+ const emit = __emit;
551
+ const dialog = ref();
552
+ watch(() => props.open, (open) => {
553
+ const el = dialog.value;
554
+ if (!el) return;
555
+ if (open && !el.open) {
556
+ if (typeof el.showModal === "function") el.showModal();
557
+ else el.setAttribute("open", "");
558
+ } else if (!open && el.open) {
559
+ if (typeof el.close === "function") el.close();
560
+ else el.removeAttribute("open");
561
+ }
562
+ }, { flush: "post" });
563
+ function onNativeClose() {
564
+ if (props.open) emit("close");
565
+ }
566
+ function onBackdrop(event) {
567
+ if (event.target === dialog.value) emit("close");
568
+ }
569
+ return (_ctx, _cache) => {
570
+ return openBlock(), createElementBlock("dialog", {
571
+ ref_key: "dialog",
572
+ ref: dialog,
573
+ class: "fl-confirm",
574
+ "aria-label": __props.title,
575
+ onClose: onNativeClose,
576
+ onClick: onBackdrop
577
+ }, [__props.open ? (openBlock(), createElementBlock("div", _hoisted_2$3, [
578
+ createElementVNode("p", _hoisted_3$2, toDisplayString(__props.title), 1),
579
+ createElementVNode("p", _hoisted_4$2, toDisplayString(__props.text), 1),
580
+ __props.tip ? (openBlock(), createElementBlock("p", _hoisted_5$2, toDisplayString(__props.tip), 1)) : createCommentVNode("v-if", true),
581
+ createElementVNode("div", _hoisted_6$2, [__props.confirmLabel ? (openBlock(), createElementBlock("button", {
582
+ key: 0,
583
+ class: "fl-btn",
584
+ type: "button",
585
+ onClick: _cache[0] || (_cache[0] = ($event) => emit("close"))
586
+ }, toDisplayString(__props.cancelLabel), 1)) : createCommentVNode("v-if", true), createElementVNode("button", {
587
+ class: "fl-btn fl-btn-primary",
588
+ type: "button",
589
+ autofocus: "",
590
+ onClick: _cache[1] || (_cache[1] = ($event) => __props.confirmLabel ? emit("confirm") : emit("close"))
591
+ }, toDisplayString(__props.confirmLabel || __props.cancelLabel), 1)])
592
+ ])) : createCommentVNode("v-if", true)], 40, _hoisted_1$3);
593
+ };
594
+ }
595
+ });
523
596
  // src/client/download.ts
524
597
  /** Open a file URL; OpenList serves `/p/` and `/d/` as attachments, so this downloads. */
525
598
  function openUrl(url) {
@@ -545,32 +618,93 @@ function saveBlob(blob, name) {
545
618
  function zipFileName(title) {
546
619
  return `${title.replace(/[\\/:*?"<>|]/g, "_")}.zip`;
547
620
  }
621
+ /** Whether the browser can write a ZIP straight into a file the user picks (Chromium). */
622
+ function canStreamToDisk() {
623
+ return typeof window !== "undefined" && window.isSecureContext && "showSaveFilePicker" in window;
624
+ }
548
625
  /**
549
- * Fetch files (three at a time) and pack them into one ZIP. `zipPath` is the
550
- * entry path; clashing names get ` (2)`, ` (3)`... `onProgress` goes 0–90 while
551
- * fetching; packing is the rest.
626
+ * Ask where to save the ZIP. Call it straight from the click, before any other
627
+ * `await`, or the browser refuses. Resolves `null` when the user cancels.
552
628
  */
553
- async function zipFiles(files, urlOf, onProgress) {
554
- const entries = {};
555
- const used = /* @__PURE__ */ new Set();
556
- const queue = [...files];
557
- let done = 0;
558
- const worker = async () => {
559
- for (let file = queue.shift(); file; file = queue.shift()) {
560
- const response = await fetch(await urlOf(file));
561
- if (!response.ok) throw new Error(`${file.name}:HTTP ${response.status}`);
562
- entries[uniqueEntryName(zipEntryName(file.zipPath), used)] = new Uint8Array(await response.arrayBuffer());
563
- done += 1;
564
- onProgress(Math.round(done / files.length * 90));
565
- }
629
+ async function pickZipFile(name) {
630
+ try {
631
+ return await window.showSaveFilePicker({
632
+ suggestedName: name,
633
+ types: [{
634
+ description: "ZIP",
635
+ accept: { "application/zip": [".zip"] }
636
+ }]
637
+ });
638
+ } catch (error) {
639
+ if (error instanceof DOMException && error.name === "AbortError") return null;
640
+ throw error;
641
+ }
642
+ }
643
+ /**
644
+ * The ZIP as a stream: files are fetched one after another and passed through
645
+ * as they arrive, stored without compression (most shared files are already
646
+ * compressed). Clashing entry names get ` (2)`, ` (3)`...
647
+ */
648
+ async function zipStream(files, options) {
649
+ const { makeZip } = await import("client-zip");
650
+ const progress = {
651
+ bytes: 0,
652
+ totalBytes: files.reduce((sum, file) => sum + (Number.isFinite(file.size) ? file.size : 0), 0),
653
+ files: 0,
654
+ totalFiles: files.length
566
655
  };
567
- await Promise.all(Array.from({ length: Math.min(3, files.length) }, worker));
568
- const { zip } = await import("fflate");
569
- const data = await new Promise((resolve, reject) => {
570
- zip(entries, (error, output) => error ? reject(error) : resolve(output));
571
- });
572
- onProgress(100);
573
- return new Blob([data], { type: "application/zip" });
656
+ const report = () => options.onProgress({ ...progress });
657
+ report();
658
+ async function* entries() {
659
+ const used = /* @__PURE__ */ new Set();
660
+ for (const file of files) {
661
+ const response = await fetch(await options.urlOf(file), { signal: options.signal });
662
+ if (!response.ok || !response.body) throw new Error(`${file.name}:HTTP ${response.status}`);
663
+ const counted = response.body.pipeThrough(new TransformStream({
664
+ transform(chunk, controller) {
665
+ progress.bytes += chunk.byteLength;
666
+ report();
667
+ controller.enqueue(chunk);
668
+ },
669
+ flush() {
670
+ progress.files += 1;
671
+ report();
672
+ }
673
+ }));
674
+ const modified = new Date(file.modified);
675
+ yield {
676
+ name: uniqueEntryName(zipEntryName(file.zipPath), used),
677
+ lastModified: Number.isNaN(modified.getTime()) ? void 0 : modified,
678
+ input: counted
679
+ };
680
+ }
681
+ }
682
+ return makeZip(entries());
683
+ }
684
+ /** Stream the ZIP into a picked file; memory use stays small whatever the size. */
685
+ async function zipToFile(handle, files, options) {
686
+ const stream = await zipStream(files, options);
687
+ const writable = await handle.createWritable();
688
+ await stream.pipeTo(writable, { signal: options.signal });
689
+ }
690
+ /** Build the ZIP in memory (browsers without file streaming). */
691
+ async function zipToBlob(files, options) {
692
+ const reader = (await zipStream(files, options)).getReader();
693
+ const chunks = [];
694
+ const abort = () => void reader.cancel(options.signal.reason).catch(() => {});
695
+ options.signal.addEventListener("abort", abort, { once: true });
696
+ if (options.signal.aborted) abort();
697
+ try {
698
+ for (;;) {
699
+ const { done, value } = await reader.read();
700
+ if (done) break;
701
+ chunks.push(value);
702
+ }
703
+ } finally {
704
+ options.signal.removeEventListener("abort", abort);
705
+ }
706
+ options.signal.throwIfAborted();
707
+ return new Blob(chunks, { type: "application/zip" });
574
708
  }
575
709
  // src/client/icons.ts
576
710
  /** Stroke icons on a 24×24 grid, drawn by `Icon.vue`. */
@@ -733,7 +867,21 @@ const fileListLocales = {
733
867
  remembered: "已记住",
734
868
  forgetPassword: "忘记",
735
869
  unlockFolder: "输入 {name} 的密码",
736
- networkHint: "可能被跨域拦截:请检查 OpenList 的 cors.allow_origins,或 OpenList-Worker 的 ALLOW_URLS。"
870
+ networkHint: "可能被跨域拦截:请检查 OpenList 的 cors.allow_origins,或 OpenList-Worker 的 ALLOW_URLS。",
871
+ batchProgress: "{done} / {total} · {files} / {count} 个文件",
872
+ batchDone: "已打包 {name}({count} 个文件,{size})",
873
+ batchCanceled: "已取消下载",
874
+ zipWarnTitle: "文件较大({size})",
875
+ zipWarnText: "当前浏览器要先把文件全部放进内存再打包,超过 {limit} 可能失败或让页面崩溃。建议分批下载。",
876
+ zipStreamTip: "在 Chrome 或 Edge 中可以边下载边保存,不受大小限制。",
877
+ zipContinue: "仍然下载",
878
+ zipBlockTitle: "无法打包 {size}",
879
+ zipBlockText: "当前浏览器一次最多打包 {limit},请分批勾选下载。",
880
+ gotIt: "知道了",
881
+ moreRemaining: "还有 {count} 项",
882
+ showMore: "显示更多",
883
+ showAll: "全部显示",
884
+ showRest: "显示剩余 {count} 项"
737
885
  },
738
886
  "en-US": {
739
887
  allFiles: "All files",
@@ -796,7 +944,21 @@ const fileListLocales = {
796
944
  remembered: "Remembered",
797
945
  forgetPassword: "Forget",
798
946
  unlockFolder: "Enter the password for {name}",
799
- networkHint: "Possibly blocked by CORS: check cors.allow_origins in OpenList, or ALLOW_URLS in OpenList-Worker."
947
+ networkHint: "Possibly blocked by CORS: check cors.allow_origins in OpenList, or ALLOW_URLS in OpenList-Worker.",
948
+ batchProgress: "{done} / {total} · {files} / {count} files",
949
+ batchDone: "Saved {name} ({count} files, {size})",
950
+ batchCanceled: "Download cancelled",
951
+ zipWarnTitle: "Large download ({size})",
952
+ zipWarnText: "This browser has to hold every file in memory to build the ZIP. Above {limit} it may fail or crash the page. Consider downloading in smaller batches.",
953
+ zipStreamTip: "Chrome and Edge save while downloading, with no size limit.",
954
+ zipContinue: "Download anyway",
955
+ zipBlockTitle: "Cannot zip {size}",
956
+ zipBlockText: "This browser can zip at most {limit} at once. Select fewer files.",
957
+ gotIt: "OK",
958
+ moreRemaining: "{count} more",
959
+ showMore: "Show more",
960
+ showAll: "Show all",
961
+ showRest: "Show the remaining {count}"
800
962
  }
801
963
  };
802
964
  function formatLabel(template, values = {}) {
@@ -1392,113 +1554,129 @@ const _hoisted_3 = {
1392
1554
  role: "status"
1393
1555
  };
1394
1556
  const _hoisted_4 = { class: "fl-selection-count" };
1395
- const _hoisted_5 = { class: "fl-selection-size" };
1557
+ const _hoisted_5 = {
1558
+ key: 0,
1559
+ class: "fl-selection-size"
1560
+ };
1396
1561
  const _hoisted_6 = {
1397
1562
  key: 1,
1563
+ class: "fl-selection",
1564
+ role: "status"
1565
+ };
1566
+ const _hoisted_7 = { class: "fl-selection-count" };
1567
+ const _hoisted_8 = { class: "fl-selection-size" };
1568
+ const _hoisted_9 = {
1569
+ key: 2,
1398
1570
  class: "fl-title"
1399
1571
  };
1400
- const _hoisted_7 = { class: "fl-title-text" };
1401
- const _hoisted_8 = {
1572
+ const _hoisted_10 = { class: "fl-title-text" };
1573
+ const _hoisted_11 = {
1402
1574
  key: 0,
1403
1575
  class: "fl-demo-badge"
1404
1576
  };
1405
- const _hoisted_9 = ["aria-label", "title"];
1406
- const _hoisted_10 = { class: "fl-clear-text" };
1407
- const _hoisted_11 = { class: "fl-tools" };
1408
- const _hoisted_12 = { class: "fl-search" };
1409
- const _hoisted_13 = ["placeholder", "aria-label"];
1410
- const _hoisted_14 = [
1577
+ const _hoisted_12 = ["aria-label", "title"];
1578
+ const _hoisted_13 = { class: "fl-clear-text" };
1579
+ const _hoisted_14 = { class: "fl-tools" };
1580
+ const _hoisted_15 = { class: "fl-search" };
1581
+ const _hoisted_16 = ["placeholder", "aria-label"];
1582
+ const _hoisted_17 = [
1411
1583
  "disabled",
1412
1584
  "aria-label",
1413
1585
  "title"
1414
1586
  ];
1415
- const _hoisted_15 = ["disabled"];
1416
- const _hoisted_16 = { class: "fl-row fl-head" };
1417
- const _hoisted_17 = {
1587
+ const _hoisted_18 = { class: "fl-row fl-head" };
1588
+ const _hoisted_19 = {
1418
1589
  key: 0,
1419
1590
  class: "fl-progress",
1420
1591
  "aria-hidden": "true"
1421
1592
  };
1422
- const _hoisted_18 = { class: "fl-cell fl-cell-check" };
1423
- const _hoisted_19 = [
1593
+ const _hoisted_20 = { class: "fl-cell fl-cell-check" };
1594
+ const _hoisted_21 = [
1424
1595
  "aria-label",
1596
+ "disabled",
1425
1597
  "checked",
1426
1598
  ".indeterminate"
1427
1599
  ];
1428
- const _hoisted_20 = ["aria-sort", "onClick"];
1429
- const _hoisted_21 = {
1600
+ const _hoisted_22 = ["aria-sort", "onClick"];
1601
+ const _hoisted_23 = {
1430
1602
  class: "fl-sort",
1431
1603
  "aria-hidden": "true"
1432
1604
  };
1433
- const _hoisted_22 = { class: "fl-cell fl-cell-op" };
1434
- const _hoisted_23 = { class: "fl-sort-select" };
1435
- const _hoisted_24 = {
1605
+ const _hoisted_24 = { class: "fl-cell fl-cell-op" };
1606
+ const _hoisted_25 = { class: "fl-sort-select" };
1607
+ const _hoisted_26 = {
1436
1608
  class: "fl-sort-current",
1437
1609
  "aria-hidden": "true"
1438
1610
  };
1439
- const _hoisted_25 = ["aria-label"];
1440
- const _hoisted_26 = ["value"];
1441
- const _hoisted_27 = {
1611
+ const _hoisted_27 = ["aria-label"];
1612
+ const _hoisted_28 = ["value"];
1613
+ const _hoisted_29 = {
1442
1614
  key: 0,
1443
1615
  class: "fl-status"
1444
1616
  };
1445
- const _hoisted_28 = ["for"];
1446
- const _hoisted_29 = ["id"];
1447
- const _hoisted_30 = { class: "fl-unlock" };
1448
- const _hoisted_31 = [
1617
+ const _hoisted_30 = ["for"];
1618
+ const _hoisted_31 = ["id"];
1619
+ const _hoisted_32 = { class: "fl-unlock" };
1620
+ const _hoisted_33 = [
1449
1621
  "id",
1450
1622
  "data-unlock",
1451
1623
  "placeholder",
1452
1624
  "aria-invalid",
1453
1625
  "aria-describedby"
1454
1626
  ];
1455
- const _hoisted_32 = [
1627
+ const _hoisted_34 = [
1456
1628
  "aria-pressed",
1457
1629
  "aria-label",
1458
1630
  "title"
1459
1631
  ];
1460
- const _hoisted_33 = ["disabled"];
1461
- const _hoisted_34 = {
1632
+ const _hoisted_35 = ["disabled"];
1633
+ const _hoisted_36 = {
1462
1634
  key: 2,
1463
1635
  class: "fl-status fl-error"
1464
1636
  };
1465
- const _hoisted_35 = {
1637
+ const _hoisted_37 = {
1466
1638
  key: 0,
1467
1639
  class: "fl-hint"
1468
1640
  };
1469
- const _hoisted_36 = {
1641
+ const _hoisted_38 = {
1470
1642
  key: 1,
1471
1643
  class: "fl-hint"
1472
1644
  };
1473
- const _hoisted_37 = {
1645
+ const _hoisted_39 = {
1474
1646
  key: 3,
1475
1647
  class: "fl-status"
1476
1648
  };
1477
- const _hoisted_38 = { class: "fl-cell fl-cell-check" };
1478
- const _hoisted_39 = [
1649
+ const _hoisted_40 = { class: "fl-cell fl-more-cell" };
1650
+ const _hoisted_41 = { class: "fl-more-count" };
1651
+ const _hoisted_42 = ["onClick"];
1652
+ const _hoisted_43 = { class: "fl-more-page" };
1653
+ const _hoisted_44 = ["onClick"];
1654
+ const _hoisted_45 = ["onClick"];
1655
+ const _hoisted_46 = { class: "fl-cell fl-cell-check" };
1656
+ const _hoisted_47 = [
1479
1657
  "disabled",
1480
1658
  "aria-label",
1481
1659
  "checked",
1482
1660
  ".indeterminate",
1483
1661
  "onChange"
1484
1662
  ];
1485
- const _hoisted_40 = ["title"];
1486
- const _hoisted_41 = [
1663
+ const _hoisted_48 = ["title"];
1664
+ const _hoisted_49 = [
1487
1665
  "aria-label",
1488
1666
  "title",
1489
1667
  "onClick"
1490
1668
  ];
1491
- const _hoisted_42 = [
1669
+ const _hoisted_50 = [
1492
1670
  "aria-label",
1493
1671
  "aria-expanded",
1494
1672
  "onClick"
1495
1673
  ];
1496
- const _hoisted_43 = {
1674
+ const _hoisted_51 = {
1497
1675
  key: 2,
1498
1676
  class: "fl-arrow-placeholder",
1499
1677
  "aria-hidden": "true"
1500
1678
  };
1501
- const _hoisted_44 = {
1679
+ const _hoisted_52 = {
1502
1680
  key: 3,
1503
1681
  class: "fl-folder-icon",
1504
1682
  viewBox: "0 0 24 24",
@@ -1506,27 +1684,27 @@ const _hoisted_44 = {
1506
1684
  height: "18",
1507
1685
  "aria-hidden": "true"
1508
1686
  };
1509
- const _hoisted_45 = ["data-kind"];
1510
- const _hoisted_46 = { class: "fl-label" };
1511
- const _hoisted_47 = ["onClick"];
1512
- const _hoisted_48 = { class: "fl-meta" };
1513
- const _hoisted_49 = {
1687
+ const _hoisted_53 = ["data-kind"];
1688
+ const _hoisted_54 = { class: "fl-label" };
1689
+ const _hoisted_55 = ["onClick"];
1690
+ const _hoisted_56 = { class: "fl-meta" };
1691
+ const _hoisted_57 = {
1514
1692
  key: 5,
1515
1693
  class: "fl-count"
1516
1694
  };
1517
- const _hoisted_50 = ["id"];
1518
- const _hoisted_51 = [
1695
+ const _hoisted_58 = ["id"];
1696
+ const _hoisted_59 = [
1519
1697
  "aria-label",
1520
1698
  "title",
1521
1699
  "onClick"
1522
1700
  ];
1523
- const _hoisted_52 = {
1701
+ const _hoisted_60 = {
1524
1702
  key: 8,
1525
1703
  class: "fl-loading",
1526
1704
  "aria-hidden": "true"
1527
1705
  };
1528
- const _hoisted_53 = ["onSubmit", "onKeydown"];
1529
- const _hoisted_54 = [
1706
+ const _hoisted_61 = ["onSubmit", "onKeydown"];
1707
+ const _hoisted_62 = [
1530
1708
  "onUpdate:modelValue",
1531
1709
  "data-unlock",
1532
1710
  "placeholder",
@@ -1534,43 +1712,45 @@ const _hoisted_54 = [
1534
1712
  "aria-invalid",
1535
1713
  "aria-describedby"
1536
1714
  ];
1537
- const _hoisted_55 = [
1715
+ const _hoisted_63 = [
1538
1716
  "aria-pressed",
1539
1717
  "aria-label",
1540
1718
  "title",
1541
1719
  "onClick"
1542
1720
  ];
1543
- const _hoisted_56 = ["disabled"];
1544
- const _hoisted_57 = [
1721
+ const _hoisted_64 = ["disabled"];
1722
+ const _hoisted_65 = [
1545
1723
  "aria-label",
1546
1724
  "title",
1547
1725
  "onClick"
1548
1726
  ];
1549
- const _hoisted_58 = { class: "fl-cell fl-cell-size" };
1550
- const _hoisted_59 = { class: "fl-cell fl-cell-date" };
1551
- const _hoisted_60 = { class: "fl-cell fl-cell-op" };
1552
- const _hoisted_61 = ["aria-label", "onClick"];
1553
- const _hoisted_62 = [
1727
+ const _hoisted_66 = { class: "fl-cell fl-cell-size" };
1728
+ const _hoisted_67 = { class: "fl-cell fl-cell-date" };
1729
+ const _hoisted_68 = { class: "fl-cell fl-cell-op" };
1730
+ const _hoisted_69 = ["aria-label", "onClick"];
1731
+ const _hoisted_70 = [
1554
1732
  "aria-label",
1555
1733
  "aria-expanded",
1556
1734
  "onClick"
1557
1735
  ];
1558
- const _hoisted_63 = [
1736
+ const _hoisted_71 = [
1559
1737
  "aria-label",
1560
1738
  "title",
1561
1739
  "onClick"
1562
1740
  ];
1563
- const _hoisted_64 = [
1741
+ const _hoisted_72 = [
1564
1742
  "aria-label",
1565
1743
  "title",
1566
1744
  "onClick"
1567
1745
  ];
1568
- const _hoisted_65 = { class: "fl-foot" };
1569
- const _hoisted_66 = {
1746
+ const _hoisted_73 = { class: "fl-foot" };
1747
+ const _hoisted_74 = {
1570
1748
  key: 4,
1571
1749
  class: "fl-toast",
1572
1750
  role: "status"
1573
1751
  };
1752
+ /** Rows shown per folder, beyond the first page; kept across refreshes. */
1753
+ const MB = 1048576;
1574
1754
  const _sfc_main = /*@__PURE__*/ defineComponent({
1575
1755
  __name: "FileList",
1576
1756
  props: {
@@ -1588,6 +1768,9 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1588
1768
  type: Boolean,
1589
1769
  default: true
1590
1770
  },
1771
+ pageSize: { default: 200 },
1772
+ zipWarnSize: {},
1773
+ zipMaxSize: {},
1591
1774
  locale: { default: "zh-CN" },
1592
1775
  labels: {}
1593
1776
  },
@@ -1630,7 +1813,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1630
1813
  const siteTitle = ref("");
1631
1814
  const iframePreviews = ref("");
1632
1815
  const batchBusy = ref(false);
1633
- const batchProgress = ref(0);
1816
+ const zipProgress = ref(null);
1817
+ let zipAbort = null;
1634
1818
  let toastTimer;
1635
1819
  /** The folder listed in single-path mode, as the parent of the top rows. */
1636
1820
  const listedFolder = computed(() => ({
@@ -1863,6 +2047,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1863
2047
  errorMsg.value = "";
1864
2048
  try {
1865
2049
  roots.value = await buildTree();
2050
+ shown.clear();
1866
2051
  rootLock.value = null;
1867
2052
  selected.value = /* @__PURE__ */ new Set();
1868
2053
  api.value.clearCache();
@@ -1943,20 +2128,41 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1943
2128
  applySort(key, direction !== "desc");
1944
2129
  }
1945
2130
  });
1946
- const visible = computed(() => {
2131
+ /** A “show more” row closing a folder whose children are cut at `pageSize`. */
2132
+ const shown = reactive(/* @__PURE__ */ new Map());
2133
+ function limitOf(key) {
2134
+ const size = Math.max(0, Math.floor(props.pageSize ?? 200));
2135
+ return size ? shown.get(key) ?? size : Number.POSITIVE_INFINITY;
2136
+ }
2137
+ function showMore(row, all) {
2138
+ const size = Math.max(1, Math.floor(props.pageSize ?? 200));
2139
+ shown.set(row.key, all ? Number.POSITIVE_INFINITY : limitOf(row.key) + size);
2140
+ }
2141
+ function isMore(row) {
2142
+ return "more" in row;
2143
+ }
2144
+ const rows = computed(() => {
1947
2145
  const out = [];
1948
2146
  const query = keyword.value.trim().toLowerCase();
1949
2147
  const matches = (node) => !query || node.name.toLowerCase().includes(query) || node.is_dir && node.children.some(matches);
1950
- const walk = (nodes) => {
1951
- for (const node of nodes) {
1952
- if (!matches(node)) continue;
2148
+ const walk = (nodes, key, depth) => {
2149
+ const list = nodes.filter(matches);
2150
+ const limit = query ? list.length : Math.min(list.length, limitOf(key));
2151
+ for (const node of list.slice(0, limit)) {
1953
2152
  out.push(node);
1954
- if (node.is_dir && (node.expanded || query) && node.children.length) walk(node.children);
2153
+ if (node.is_dir && (node.expanded || query) && node.children.length) walk(node.children, node.path, node.depth + 1);
1955
2154
  }
2155
+ if (limit < list.length) out.push({
2156
+ more: true,
2157
+ key,
2158
+ depth,
2159
+ remaining: list.length - limit
2160
+ });
1956
2161
  };
1957
- walk(roots.value);
2162
+ walk(roots.value, props.sources.length ? "" : props.path, 0);
1958
2163
  return out;
1959
2164
  });
2165
+ const visible = computed(() => rows.value.filter((row) => !isMore(row)));
1960
2166
  const stats = computed(() => folderStats(roots.value, selected.value));
1961
2167
  const noStats = {
1962
2168
  files: 0,
@@ -2043,24 +2249,104 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2043
2249
  showToast(t("downloadFailed", { message: errorText(error) }));
2044
2250
  }
2045
2251
  }
2252
+ function zipLimits() {
2253
+ const touch = typeof window !== "undefined" && !!window.matchMedia?.("(pointer: coarse)").matches;
2254
+ const bytes = (value, fallback) => {
2255
+ const mb = value ?? fallback;
2256
+ return mb > 0 ? mb * MB : Number.POSITIVE_INFINITY;
2257
+ };
2258
+ return {
2259
+ warn: bytes(props.zipWarnSize, touch ? 200 : 500),
2260
+ max: bytes(props.zipMaxSize, touch ? 1024 : 2048)
2261
+ };
2262
+ }
2263
+ const zipNotice = ref(null);
2264
+ /** Only desktop browsers get “use Chrome or Edge”; on iPhone every browser is Safari. */
2265
+ const zipTip = computed(() => typeof window !== "undefined" && !window.matchMedia?.("(pointer: coarse)").matches ? t("zipStreamTip") : "");
2266
+ function zipName() {
2267
+ return zipFileName(props.title || t("defaultZip"));
2268
+ }
2269
+ /**
2270
+ * Chrome / Edge stream into a file the user picks (no size limit). Other
2271
+ * browsers build the ZIP in memory, so large selections ask first or are refused.
2272
+ */
2046
2273
  async function batchDownload() {
2047
2274
  const files = selectedFiles.value;
2048
- if (!files.length) return;
2275
+ if (!files.length || batchBusy.value) return;
2049
2276
  if (demo.value) {
2050
2277
  showToast(t("demoBatch", { count: files.length }));
2051
2278
  return;
2052
2279
  }
2280
+ if (canStreamToDisk()) {
2281
+ let handle;
2282
+ try {
2283
+ handle = await pickZipFile(zipName());
2284
+ } catch (error) {
2285
+ showToast(t("batchFailed", { message: errorText(error) }));
2286
+ return;
2287
+ }
2288
+ if (handle) await runZip(files, (options) => zipToFile(handle, files, options), handle.name);
2289
+ return;
2290
+ }
2291
+ const size = totalSize(files);
2292
+ const { warn, max } = zipLimits();
2293
+ if (size > max) zipNotice.value = {
2294
+ kind: "block",
2295
+ size,
2296
+ limit: max,
2297
+ files
2298
+ };
2299
+ else if (size > warn) zipNotice.value = {
2300
+ kind: "warn",
2301
+ size,
2302
+ limit: warn,
2303
+ files
2304
+ };
2305
+ else await zipInMemory(files);
2306
+ }
2307
+ async function zipInMemory(files) {
2308
+ const name = zipName();
2309
+ await runZip(files, async (options) => saveBlob(await zipToBlob(files, options), name), name);
2310
+ }
2311
+ function confirmZip() {
2312
+ const notice = zipNotice.value;
2313
+ zipNotice.value = null;
2314
+ if (notice?.kind === "warn") zipInMemory(notice.files);
2315
+ }
2316
+ async function runZip(files, run, name) {
2317
+ const controller = new AbortController();
2318
+ zipAbort = controller;
2053
2319
  batchBusy.value = true;
2054
- batchProgress.value = 0;
2320
+ zipProgress.value = null;
2055
2321
  try {
2056
- saveBlob(await zipFiles(files, fileUrlOf, (percent) => batchProgress.value = percent), zipFileName(props.title || t("defaultZip")));
2322
+ await run({
2323
+ urlOf: fileUrlOf,
2324
+ signal: controller.signal,
2325
+ onProgress: (progress) => zipProgress.value = progress
2326
+ });
2327
+ showToast(t("batchDone", {
2328
+ name,
2329
+ count: files.length,
2330
+ size: formatSize(totalSize(files))
2331
+ }));
2057
2332
  selected.value = /* @__PURE__ */ new Set();
2058
2333
  } catch (error) {
2059
- showToast(t("batchFailed", { message: errorText(error) }));
2334
+ showToast(controller.signal.aborted ? t("batchCanceled") : t("batchFailed", { message: errorText(error) }));
2060
2335
  } finally {
2336
+ zipAbort = null;
2061
2337
  batchBusy.value = false;
2338
+ zipProgress.value = null;
2062
2339
  }
2063
2340
  }
2341
+ function cancelZip() {
2342
+ zipAbort?.abort();
2343
+ }
2344
+ const zipPercent = computed(() => {
2345
+ const progress = zipProgress.value;
2346
+ if (!progress) return 0;
2347
+ const ratio = progress.totalBytes ? progress.bytes / progress.totalBytes : progress.totalFiles ? progress.files / progress.totalFiles : 0;
2348
+ return Math.min(100, Math.floor(ratio * 100));
2349
+ });
2064
2350
  /** Only types the dialog can actually render get a preview button. */
2065
2351
  function canPreview(node) {
2066
2352
  return !node.is_dir && previewKind(node.name) !== "none";
@@ -2132,6 +2418,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2132
2418
  window.addEventListener("resize", measureStickyTop, { passive: true });
2133
2419
  });
2134
2420
  onBeforeUnmount(() => {
2421
+ zipAbort?.abort();
2135
2422
  window.removeEventListener("resize", measureStickyTop);
2136
2423
  clearTimeout(toastTimer);
2137
2424
  });
@@ -2147,9 +2434,22 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2147
2434
  "aria-busy": refreshing.value || loadingRoot.value
2148
2435
  }, [
2149
2436
  createElementVNode("div", _hoisted_2, [
2150
- selected.value.size ? (openBlock(), createElementBlock("div", _hoisted_3, [createElementVNode("span", _hoisted_4, toDisplayString(t("selected", { count: selected.value.size })), 1), createElementVNode("span", _hoisted_5, toDisplayString(t("totalSize", { size: unref(formatSize)(unref(totalSize)(selectedFiles.value)) })), 1)])) : (openBlock(), createElementBlock("div", _hoisted_6, [createElementVNode("span", _hoisted_7, toDisplayString(displayTitle.value), 1), demo.value ? (openBlock(), createElementBlock("span", _hoisted_8, toDisplayString(t("demoBadge")), 1)) : createCommentVNode("v-if", true)])),
2151
- selected.value.size ? (openBlock(), createElementBlock("button", {
2152
- key: 2,
2437
+ batchBusy.value ? (openBlock(), createElementBlock("div", _hoisted_3, [
2438
+ createElementVNode("span", _hoisted_4, toDisplayString(t("batching", { percent: zipPercent.value })), 1),
2439
+ zipProgress.value ? (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString(t("batchProgress", {
2440
+ done: unref(formatSize)(zipProgress.value.bytes),
2441
+ total: unref(formatSize)(zipProgress.value.totalBytes),
2442
+ files: zipProgress.value.files,
2443
+ count: zipProgress.value.totalFiles
2444
+ })), 1)) : createCommentVNode("v-if", true),
2445
+ createElementVNode("span", {
2446
+ class: "fl-zip-bar",
2447
+ style: normalizeStyle({ width: `${zipPercent.value}%` }),
2448
+ "aria-hidden": "true"
2449
+ }, null, 4)
2450
+ ])) : selected.value.size ? (openBlock(), createElementBlock("div", _hoisted_6, [createElementVNode("span", _hoisted_7, toDisplayString(t("selected", { count: selected.value.size })), 1), createElementVNode("span", _hoisted_8, toDisplayString(t("totalSize", { size: unref(formatSize)(unref(totalSize)(selectedFiles.value)) })), 1)])) : (openBlock(), createElementBlock("div", _hoisted_9, [createElementVNode("span", _hoisted_10, toDisplayString(displayTitle.value), 1), demo.value ? (openBlock(), createElementBlock("span", _hoisted_11, toDisplayString(t("demoBadge")), 1)) : createCommentVNode("v-if", true)])),
2451
+ selected.value.size && !batchBusy.value ? (openBlock(), createElementBlock("button", {
2452
+ key: 3,
2153
2453
  class: "fl-clear",
2154
2454
  type: "button",
2155
2455
  "aria-label": t("clearSelection"),
@@ -2159,8 +2459,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2159
2459
  class: "fl-clear-icon",
2160
2460
  name: "close",
2161
2461
  size: 18
2162
- }), createElementVNode("span", _hoisted_10, toDisplayString(t("clear")), 1)], 8, _hoisted_9)) : createCommentVNode("v-if", true),
2163
- createElementVNode("div", _hoisted_11, [createElementVNode("label", _hoisted_12, [createVNode(_sfc_main$2, {
2462
+ }), createElementVNode("span", _hoisted_13, toDisplayString(t("clear")), 1)], 8, _hoisted_12)) : createCommentVNode("v-if", true),
2463
+ createElementVNode("div", _hoisted_14, [createElementVNode("label", _hoisted_15, [createVNode(_sfc_main$2, {
2164
2464
  name: "search",
2165
2465
  size: 15
2166
2466
  }), withDirectives(createElementVNode("input", {
@@ -2168,7 +2468,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2168
2468
  type: "text",
2169
2469
  placeholder: t("filter"),
2170
2470
  "aria-label": t("filter")
2171
- }, null, 8, _hoisted_13), [[vModelText, keyword.value]])]), createElementVNode("button", {
2471
+ }, null, 8, _hoisted_16), [[vModelText, keyword.value]])]), createElementVNode("button", {
2172
2472
  class: normalizeClass(["fl-icon-btn fl-refresh", { "fl-spinning": refreshing.value }]),
2173
2473
  type: "button",
2174
2474
  disabled: refreshing.value || loadingRoot.value,
@@ -2178,27 +2478,32 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2178
2478
  }, [createVNode(_sfc_main$2, {
2179
2479
  name: "refresh",
2180
2480
  size: 15
2181
- })], 10, _hoisted_14)]),
2182
- selected.value.size ? (openBlock(), createElementBlock("button", {
2183
- key: 3,
2481
+ })], 10, _hoisted_17)]),
2482
+ batchBusy.value ? (openBlock(), createElementBlock("button", {
2483
+ key: 4,
2484
+ class: "fl-btn fl-zip-cancel",
2485
+ type: "button",
2486
+ onClick: cancelZip
2487
+ }, toDisplayString(t("cancel")), 1)) : selected.value.size ? (openBlock(), createElementBlock("button", {
2488
+ key: 5,
2184
2489
  class: "fl-btn fl-btn-primary",
2185
2490
  type: "button",
2186
- disabled: batchBusy.value,
2187
2491
  onClick: batchDownload
2188
2492
  }, [createVNode(_sfc_main$2, {
2189
2493
  name: "download",
2190
2494
  size: 15
2191
- }), createTextVNode(" " + toDisplayString(batchBusy.value ? t("batching", { percent: batchProgress.value }) : t("batch")), 1)], 8, _hoisted_15)) : createCommentVNode("v-if", true)
2495
+ }), createTextVNode(" " + toDisplayString(t("batch")), 1)])) : createCommentVNode("v-if", true)
2192
2496
  ]),
2193
- createElementVNode("div", _hoisted_16, [
2194
- refreshing.value ? (openBlock(), createElementBlock("span", _hoisted_17)) : createCommentVNode("v-if", true),
2195
- createElementVNode("label", _hoisted_18, [createElementVNode("input", {
2497
+ createElementVNode("div", _hoisted_18, [
2498
+ refreshing.value ? (openBlock(), createElementBlock("span", _hoisted_19)) : createCommentVNode("v-if", true),
2499
+ createElementVNode("label", _hoisted_20, [createElementVNode("input", {
2196
2500
  type: "checkbox",
2197
2501
  "aria-label": t("selectAll"),
2502
+ disabled: batchBusy.value,
2198
2503
  checked: allChecked.value,
2199
2504
  ".indeterminate": someChecked.value,
2200
2505
  onChange: toggleCheckAll
2201
- }, null, 40, _hoisted_19)]),
2506
+ }, null, 40, _hoisted_21)]),
2202
2507
  (openBlock(), createElementBlock(Fragment, null, renderList([
2203
2508
  "name",
2204
2509
  "size",
@@ -2212,11 +2517,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2212
2517
  type: "button",
2213
2518
  "aria-sort": sortKey.value === column ? sortAsc.value ? "ascending" : "descending" : void 0,
2214
2519
  onClick: ($event) => setSort(column)
2215
- }, [createTextVNode(toDisplayString(t(column === "name" ? "columnName" : column === "size" ? "columnSize" : "columnDate")), 1), createElementVNode("span", _hoisted_21, toDisplayString(sortKey.value === column ? sortAsc.value ? "▲" : "▼" : ""), 1)], 8, _hoisted_20)], 2);
2520
+ }, [createTextVNode(toDisplayString(t(column === "name" ? "columnName" : column === "size" ? "columnSize" : "columnDate")), 1), createElementVNode("span", _hoisted_23, toDisplayString(sortKey.value === column ? sortAsc.value ? "▲" : "▼" : ""), 1)], 8, _hoisted_22)], 2);
2216
2521
  }), 64)),
2217
- createElementVNode("span", _hoisted_22, [createElementVNode("label", _hoisted_23, [
2522
+ createElementVNode("span", _hoisted_24, [createElementVNode("label", _hoisted_25, [
2218
2523
  createElementVNode("span", null, toDisplayString(t("sortBy")), 1),
2219
- createElementVNode("span", _hoisted_24, toDisplayString(sortOptions.value.find((option) => option.value === sortValue.value)?.label), 1),
2524
+ createElementVNode("span", _hoisted_26, toDisplayString(sortOptions.value.find((option) => option.value === sortValue.value)?.label), 1),
2220
2525
  withDirectives(createElementVNode("select", {
2221
2526
  "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => sortValue.value = $event),
2222
2527
  "aria-label": t("sortBy")
@@ -2224,11 +2529,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2224
2529
  return openBlock(), createElementBlock("option", {
2225
2530
  key: option.value,
2226
2531
  value: option.value
2227
- }, toDisplayString(option.label), 9, _hoisted_26);
2228
- }), 128))], 8, _hoisted_25), [[vModelSelect, sortValue.value]])
2532
+ }, toDisplayString(option.label), 9, _hoisted_28);
2533
+ }), 128))], 8, _hoisted_27), [[vModelSelect, sortValue.value]])
2229
2534
  ])])
2230
2535
  ]),
2231
- loadingRoot.value ? (openBlock(), createElementBlock("div", _hoisted_27, toDisplayString(t("loading")), 1)) : rootLock.value ? (openBlock(), createElementBlock("form", {
2536
+ loadingRoot.value ? (openBlock(), createElementBlock("div", _hoisted_29, toDisplayString(t("loading")), 1)) : rootLock.value ? (openBlock(), createElementBlock("form", {
2232
2537
  key: 1,
2233
2538
  class: normalizeClass(["fl-unlock-root", { "fl-unlock-error": rootLock.value.error }]),
2234
2539
  onSubmit: withModifiers(submitRootUnlock, ["prevent"])
@@ -2240,14 +2545,14 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2240
2545
  createElementVNode("label", {
2241
2546
  class: "fl-unlock-label",
2242
2547
  for: `${unref(uid)}-root`
2243
- }, toDisplayString(t("passwordPrompt")), 9, _hoisted_28),
2548
+ }, toDisplayString(t("passwordPrompt")), 9, _hoisted_30),
2244
2549
  rootLock.value.error ? (openBlock(), createElementBlock("span", {
2245
2550
  key: 0,
2246
2551
  id: `${unref(uid)}-root-error`,
2247
2552
  class: "fl-unlock-message",
2248
2553
  role: "alert"
2249
- }, toDisplayString(unlockErrorText(rootLock.value.error)), 9, _hoisted_29)) : createCommentVNode("v-if", true),
2250
- createElementVNode("span", _hoisted_30, [
2554
+ }, toDisplayString(unlockErrorText(rootLock.value.error)), 9, _hoisted_31)) : createCommentVNode("v-if", true),
2555
+ createElementVNode("span", _hoisted_32, [
2251
2556
  withDirectives(createElementVNode("input", {
2252
2557
  id: `${unref(uid)}-root`,
2253
2558
  "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => rootLock.value.input = $event),
@@ -2257,7 +2562,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2257
2562
  placeholder: t("passwordPlaceholder"),
2258
2563
  "aria-invalid": !!rootLock.value.error,
2259
2564
  "aria-describedby": rootLock.value.error ? `${unref(uid)}-root-error` : void 0
2260
- }, null, 8, _hoisted_31), [[vModelText, rootLock.value.input]]),
2565
+ }, null, 8, _hoisted_33), [[vModelText, rootLock.value.input]]),
2261
2566
  canRemember.value ? (openBlock(), createElementBlock("button", {
2262
2567
  key: 0,
2263
2568
  class: "fl-unlock-remember",
@@ -2266,26 +2571,50 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2266
2571
  "aria-label": t("rememberPassword"),
2267
2572
  title: t("rememberPassword"),
2268
2573
  onClick: _cache[3] || (_cache[3] = ($event) => rootLock.value.remember = !rootLock.value.remember)
2269
- }, [createVNode(_sfc_main$2, { name: "key" })], 8, _hoisted_32)) : createCommentVNode("v-if", true),
2574
+ }, [createVNode(_sfc_main$2, { name: "key" })], 8, _hoisted_34)) : createCommentVNode("v-if", true),
2270
2575
  createElementVNode("button", {
2271
2576
  class: "fl-unlock-submit",
2272
2577
  type: "submit",
2273
2578
  disabled: rootLock.value.busy || !rootLock.value.input
2274
- }, toDisplayString(t("open")), 9, _hoisted_33)
2579
+ }, toDisplayString(t("open")), 9, _hoisted_35)
2275
2580
  ])
2276
- ], 34)) : errorMsg.value ? (openBlock(), createElementBlock("div", _hoisted_34, [
2581
+ ], 34)) : errorMsg.value ? (openBlock(), createElementBlock("div", _hoisted_36, [
2277
2582
  createTextVNode(toDisplayString(t("loadFailed", { message: errorMsg.value })) + " ", 1),
2278
- /guest/i.test(errorMsg.value) ? (openBlock(), createElementBlock("p", _hoisted_35, toDisplayString(t("guestHint")), 1)) : createCommentVNode("v-if", true),
2279
- unref(isNetworkError)(errorMsg.value) ? (openBlock(), createElementBlock("p", _hoisted_36, toDisplayString(t("corsHint")), 1)) : createCommentVNode("v-if", true),
2583
+ /guest/i.test(errorMsg.value) ? (openBlock(), createElementBlock("p", _hoisted_37, toDisplayString(t("guestHint")), 1)) : createCommentVNode("v-if", true),
2584
+ unref(isNetworkError)(errorMsg.value) ? (openBlock(), createElementBlock("p", _hoisted_38, toDisplayString(t("corsHint")), 1)) : createCommentVNode("v-if", true),
2280
2585
  createElementVNode("button", {
2281
2586
  class: "fl-btn",
2282
2587
  type: "button",
2283
2588
  onClick: loadRoot
2284
2589
  }, toDisplayString(t("retry")), 1)
2285
- ])) : visible.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_37, toDisplayString(t("empty")), 1)) : createCommentVNode("v-if", true),
2286
- (openBlock(true), createElementBlock(Fragment, null, renderList(visible.value, (node, index) => {
2287
- return openBlock(), createElementBlock("div", {
2288
- key: node.path,
2590
+ ])) : visible.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_39, toDisplayString(t("empty")), 1)) : createCommentVNode("v-if", true),
2591
+ (openBlock(true), createElementBlock(Fragment, null, renderList(rows.value, (node, index) => {
2592
+ return openBlock(), createElementBlock(Fragment, { key: isMore(node) ? `more:${node.key}` : node.path }, [isMore(node) ? (openBlock(), createElementBlock("div", {
2593
+ key: 0,
2594
+ class: "fl-row fl-more",
2595
+ style: normalizeStyle({ "--fl-depth": node.depth })
2596
+ }, [_cache[9] || (_cache[9] = createElementVNode("span", { class: "fl-cell fl-cell-check" }, null, -1)), createElementVNode("span", _hoisted_40, [
2597
+ _cache[8] || (_cache[8] = createElementVNode("span", {
2598
+ class: "fl-indent",
2599
+ "aria-hidden": "true"
2600
+ }, null, -1)),
2601
+ createElementVNode("span", _hoisted_41, toDisplayString(t("moreRemaining", { count: node.remaining })), 1),
2602
+ node.remaining > (__props.pageSize || 0) ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("button", {
2603
+ class: "fl-more-btn",
2604
+ type: "button",
2605
+ onClick: ($event) => showMore(node, false)
2606
+ }, [createTextVNode(toDisplayString(t("showMore")), 1), createElementVNode("span", _hoisted_43, toDisplayString(` ${t("folderItems", { count: __props.pageSize })}`), 1)], 8, _hoisted_42), createElementVNode("button", {
2607
+ class: "fl-more-btn fl-more-all",
2608
+ type: "button",
2609
+ onClick: ($event) => showMore(node, true)
2610
+ }, toDisplayString(t("showAll")), 9, _hoisted_44)], 64)) : (openBlock(), createElementBlock("button", {
2611
+ key: 1,
2612
+ class: "fl-more-btn",
2613
+ type: "button",
2614
+ onClick: ($event) => showMore(node, true)
2615
+ }, toDisplayString(t("showRest", { count: node.remaining })), 9, _hoisted_45))
2616
+ ])], 4)) : (openBlock(), createElementBlock("div", {
2617
+ key: 1,
2289
2618
  class: normalizeClass(["fl-row", {
2290
2619
  "fl-row-dir": node.is_dir,
2291
2620
  "fl-row-root": node.isRoot,
@@ -2296,19 +2625,19 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2296
2625
  }]),
2297
2626
  style: normalizeStyle({ "--fl-depth": node.depth })
2298
2627
  }, [
2299
- createElementVNode("label", _hoisted_38, [createElementVNode("input", {
2628
+ createElementVNode("label", _hoisted_46, [createElementVNode("input", {
2300
2629
  type: "checkbox",
2301
- disabled: !!node.lock,
2630
+ disabled: !!node.lock || batchBusy.value,
2302
2631
  "aria-label": t("selectItem", { name: node.name }),
2303
2632
  checked: isChecked(node),
2304
2633
  ".indeterminate": isIndeterminate(node),
2305
2634
  onChange: ($event) => toggleCheck(node, $event)
2306
- }, null, 40, _hoisted_39)]),
2635
+ }, null, 40, _hoisted_47)]),
2307
2636
  createElementVNode("span", {
2308
2637
  class: "fl-cell fl-cell-name",
2309
2638
  title: node.isRoot ? node.path : void 0
2310
2639
  }, [
2311
- _cache[8] || (_cache[8] = createElementVNode("span", {
2640
+ _cache[11] || (_cache[11] = createElementVNode("span", {
2312
2641
  class: "fl-indent",
2313
2642
  "aria-hidden": "true"
2314
2643
  }, null, -1)),
@@ -2322,7 +2651,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2322
2651
  }, [createVNode(_sfc_main$2, {
2323
2652
  name: "lock",
2324
2653
  size: 14
2325
- })], 8, _hoisted_41)) : node.is_dir ? (openBlock(), createElementBlock("button", {
2654
+ })], 8, _hoisted_49)) : node.is_dir ? (openBlock(), createElementBlock("button", {
2326
2655
  key: 1,
2327
2656
  class: normalizeClass(["fl-arrow", { "fl-arrow-open": node.expanded }]),
2328
2657
  type: "button",
@@ -2332,24 +2661,24 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2332
2661
  }, [createVNode(_sfc_main$2, {
2333
2662
  name: "next",
2334
2663
  size: 14
2335
- })], 10, _hoisted_42)) : (openBlock(), createElementBlock("span", _hoisted_43)),
2336
- node.is_dir ? (openBlock(), createElementBlock("svg", _hoisted_44, [..._cache[7] || (_cache[7] = [createElementVNode("path", { d: "M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }, null, -1)])])) : (openBlock(), createElementBlock("span", {
2664
+ })], 10, _hoisted_50)) : (openBlock(), createElementBlock("span", _hoisted_51)),
2665
+ node.is_dir ? (openBlock(), createElementBlock("svg", _hoisted_52, [..._cache[10] || (_cache[10] = [createElementVNode("path", { d: "M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }, null, -1)])])) : (openBlock(), createElementBlock("span", {
2337
2666
  key: 4,
2338
2667
  class: "fl-type",
2339
2668
  "data-kind": unref(fileKind)(node.name),
2340
2669
  "aria-hidden": "true"
2341
- }, toDisplayString(unref(extOf)(node.name) || "FILE"), 9, _hoisted_45)),
2342
- createElementVNode("span", _hoisted_46, [createElementVNode("span", {
2670
+ }, toDisplayString(unref(extOf)(node.name) || "FILE"), 9, _hoisted_53)),
2671
+ createElementVNode("span", _hoisted_54, [createElementVNode("span", {
2343
2672
  class: normalizeClass(["fl-name", { "fl-name-dir": node.is_dir }]),
2344
2673
  onClick: ($event) => node.lock || node.is_dir ? toggleExpand(node) : download(node)
2345
- }, toDisplayString(node.name), 11, _hoisted_47), createElementVNode("span", _hoisted_48, [!node.is_dir ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(toDisplayString(unref(formatSize)(node.size)) + " · " + toDisplayString(unref(formatDate)(node.modified)), 1)], 64)) : node.loaded ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(t("folderItems", { count: statsOf(node).files })) + " · " + toDisplayString(unref(formatSize)(statsOf(node).bytes)), 1)], 64)) : createCommentVNode("v-if", true)])]),
2346
- node.is_dir && node.loaded ? (openBlock(), createElementBlock("span", _hoisted_49, toDisplayString(t("folderItems", { count: statsOf(node).files })), 1)) : createCommentVNode("v-if", true),
2674
+ }, toDisplayString(node.name), 11, _hoisted_55), createElementVNode("span", _hoisted_56, [!node.is_dir ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(toDisplayString(unref(formatSize)(node.size)) + " · " + toDisplayString(unref(formatDate)(node.modified)), 1)], 64)) : node.loaded ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(t("folderItems", { count: statsOf(node).files })) + " · " + toDisplayString(unref(formatSize)(statsOf(node).bytes)), 1)], 64)) : createCommentVNode("v-if", true)])]),
2675
+ node.is_dir && node.loaded ? (openBlock(), createElementBlock("span", _hoisted_57, toDisplayString(t("folderItems", { count: statsOf(node).files })), 1)) : createCommentVNode("v-if", true),
2347
2676
  node.lock?.prompting && node.lock.error ? (openBlock(), createElementBlock("span", {
2348
2677
  key: 6,
2349
2678
  id: `${unref(uid)}-error-${index}`,
2350
2679
  class: "fl-unlock-message",
2351
2680
  role: "alert"
2352
- }, toDisplayString(unlockErrorText(node.lock.error)), 9, _hoisted_50)) : createCommentVNode("v-if", true),
2681
+ }, toDisplayString(unlockErrorText(node.lock.error)), 9, _hoisted_58)) : createCommentVNode("v-if", true),
2353
2682
  !node.lock && unref(remembered).has(node.path) ? (openBlock(), createElementBlock("button", {
2354
2683
  key: 7,
2355
2684
  class: "fl-remembered",
@@ -2360,9 +2689,9 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2360
2689
  }, [createVNode(_sfc_main$2, {
2361
2690
  name: "key",
2362
2691
  size: 11
2363
- }), createTextVNode(" " + toDisplayString(t("remembered")) + " · " + toDisplayString(t("forgetPassword")), 1)], 8, _hoisted_51)) : createCommentVNode("v-if", true),
2364
- node.loading ? (openBlock(), createElementBlock("span", _hoisted_52, "…")) : createCommentVNode("v-if", true)
2365
- ], 8, _hoisted_40),
2692
+ }), createTextVNode(" " + toDisplayString(t("remembered")) + " · " + toDisplayString(t("forgetPassword")), 1)], 8, _hoisted_59)) : createCommentVNode("v-if", true),
2693
+ node.loading ? (openBlock(), createElementBlock("span", _hoisted_60, "…")) : createCommentVNode("v-if", true)
2694
+ ], 8, _hoisted_48),
2366
2695
  node.lock?.prompting ? (openBlock(), createElementBlock("form", {
2367
2696
  key: 0,
2368
2697
  class: "fl-unlock fl-unlock-row",
@@ -2383,7 +2712,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2383
2712
  "aria-label": t("passwordFor", { name: node.name }),
2384
2713
  "aria-invalid": !!node.lock.error,
2385
2714
  "aria-describedby": node.lock.error ? `${unref(uid)}-error-${index}` : void 0
2386
- }, null, 8, _hoisted_54), [[vModelText, node.lock.input]]),
2715
+ }, null, 8, _hoisted_62), [[vModelText, node.lock.input]]),
2387
2716
  canRemember.value ? (openBlock(), createElementBlock("button", {
2388
2717
  key: 0,
2389
2718
  class: "fl-unlock-remember",
@@ -2392,12 +2721,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2392
2721
  "aria-label": t("rememberPassword"),
2393
2722
  title: t("rememberPassword"),
2394
2723
  onClick: ($event) => node.lock.remember = !node.lock.remember
2395
- }, [createVNode(_sfc_main$2, { name: "key" })], 8, _hoisted_55)) : createCommentVNode("v-if", true),
2724
+ }, [createVNode(_sfc_main$2, { name: "key" })], 8, _hoisted_63)) : createCommentVNode("v-if", true),
2396
2725
  createElementVNode("button", {
2397
2726
  class: "fl-unlock-submit",
2398
2727
  type: "submit",
2399
2728
  disabled: node.lock.busy || !node.lock.input
2400
- }, toDisplayString(t("open")), 9, _hoisted_56),
2729
+ }, toDisplayString(t("open")), 9, _hoisted_64),
2401
2730
  createElementVNode("button", {
2402
2731
  class: "fl-unlock-cancel",
2403
2732
  type: "button",
@@ -2407,11 +2736,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2407
2736
  }, [createVNode(_sfc_main$2, {
2408
2737
  name: "close",
2409
2738
  size: 14
2410
- })], 8, _hoisted_57)
2411
- ], 40, _hoisted_53)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
2412
- createElementVNode("span", _hoisted_58, toDisplayString(node.is_dir ? "—" : unref(formatSize)(node.size)), 1),
2413
- createElementVNode("span", _hoisted_59, toDisplayString(unref(formatDate)(node.modified)), 1),
2414
- createElementVNode("span", _hoisted_60, [node.lock ? (openBlock(), createElementBlock("button", {
2739
+ })], 8, _hoisted_65)
2740
+ ], 40, _hoisted_61)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
2741
+ createElementVNode("span", _hoisted_66, toDisplayString(node.is_dir ? "—" : unref(formatSize)(node.size)), 1),
2742
+ createElementVNode("span", _hoisted_67, toDisplayString(unref(formatDate)(node.modified)), 1),
2743
+ createElementVNode("span", _hoisted_68, [node.lock ? (openBlock(), createElementBlock("button", {
2415
2744
  key: 0,
2416
2745
  class: "fl-action fl-arrow-end fl-lock",
2417
2746
  type: "button",
@@ -2420,7 +2749,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2420
2749
  }, [createVNode(_sfc_main$2, {
2421
2750
  name: "lock",
2422
2751
  size: 18
2423
- })], 8, _hoisted_61)) : node.is_dir ? (openBlock(), createElementBlock("button", {
2752
+ })], 8, _hoisted_69)) : node.is_dir ? (openBlock(), createElementBlock("button", {
2424
2753
  key: 1,
2425
2754
  class: normalizeClass(["fl-action fl-arrow-end", { "fl-arrow-open": node.expanded }]),
2426
2755
  type: "button",
@@ -2430,25 +2759,42 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2430
2759
  }, [createVNode(_sfc_main$2, {
2431
2760
  name: "next",
2432
2761
  size: 18
2433
- })], 10, _hoisted_62)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [canPreview(node) ? (openBlock(), createElementBlock("button", {
2762
+ })], 10, _hoisted_70)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [canPreview(node) ? (openBlock(), createElementBlock("button", {
2434
2763
  key: 0,
2435
2764
  class: "fl-action fl-preview-btn",
2436
2765
  type: "button",
2437
2766
  "aria-label": `${t("preview")} ${node.name}`,
2438
2767
  title: t("preview"),
2439
2768
  onClick: ($event) => preview(node)
2440
- }, [createVNode(_sfc_main$2, { name: "eye" })], 8, _hoisted_63)) : createCommentVNode("v-if", true), createElementVNode("button", {
2769
+ }, [createVNode(_sfc_main$2, { name: "eye" })], 8, _hoisted_71)) : createCommentVNode("v-if", true), createElementVNode("button", {
2441
2770
  class: "fl-action fl-download",
2442
2771
  type: "button",
2443
2772
  "aria-label": `${t("download")} ${node.name}`,
2444
2773
  title: t("download"),
2445
2774
  onClick: ($event) => download(node)
2446
- }, [createVNode(_sfc_main$2, { name: "download" })], 8, _hoisted_64)], 64))])
2775
+ }, [createVNode(_sfc_main$2, { name: "download" })], 8, _hoisted_72)], 64))])
2447
2776
  ], 64))
2448
- ], 6);
2777
+ ], 6))], 64);
2449
2778
  }), 128)),
2450
- createElementVNode("div", _hoisted_65, [createElementVNode("span", null, toDisplayString(t("loaded", { count: allFiles.value.length })) + " · " + toDisplayString(t("totalSize", { size: unref(formatSize)(unref(totalSize)(allFiles.value)) })), 1)]),
2451
- toast.value ? (openBlock(), createElementBlock("div", _hoisted_66, toDisplayString(toast.value), 1)) : createCommentVNode("v-if", true),
2779
+ createElementVNode("div", _hoisted_73, [createElementVNode("span", null, toDisplayString(t("loaded", { count: allFiles.value.length })) + " · " + toDisplayString(t("totalSize", { size: unref(formatSize)(unref(totalSize)(allFiles.value)) })), 1)]),
2780
+ toast.value ? (openBlock(), createElementBlock("div", _hoisted_74, toDisplayString(toast.value), 1)) : createCommentVNode("v-if", true),
2781
+ createVNode(_sfc_main$3, {
2782
+ open: !!zipNotice.value,
2783
+ title: zipNotice.value ? t(zipNotice.value.kind === "warn" ? "zipWarnTitle" : "zipBlockTitle", { size: unref(formatSize)(zipNotice.value.size) }) : "",
2784
+ text: zipNotice.value ? t(zipNotice.value.kind === "warn" ? "zipWarnText" : "zipBlockText", { limit: unref(formatSize)(zipNotice.value.limit) }) : "",
2785
+ tip: zipTip.value,
2786
+ "confirm-label": zipNotice.value?.kind === "warn" ? t("zipContinue") : void 0,
2787
+ "cancel-label": zipNotice.value?.kind === "warn" ? t("cancel") : t("gotIt"),
2788
+ onConfirm: confirmZip,
2789
+ onClose: _cache[4] || (_cache[4] = ($event) => zipNotice.value = null)
2790
+ }, null, 8, [
2791
+ "open",
2792
+ "title",
2793
+ "text",
2794
+ "tip",
2795
+ "confirm-label",
2796
+ "cancel-label"
2797
+ ]),
2452
2798
  createVNode(_sfc_main$1, {
2453
2799
  open: !!previewNode.value,
2454
2800
  file: previewNode.value,
@@ -2461,9 +2807,9 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2461
2807
  position: previewPosition.value,
2462
2808
  total: previewPosition.value ? previewList.value.length : 0,
2463
2809
  onClose: closePreview,
2464
- onPrev: _cache[4] || (_cache[4] = ($event) => stepPreview(-1)),
2465
- onNext: _cache[5] || (_cache[5] = ($event) => stepPreview(1)),
2466
- onDownload: _cache[6] || (_cache[6] = ($event) => previewNode.value && download(previewNode.value))
2810
+ onPrev: _cache[5] || (_cache[5] = ($event) => stepPreview(-1)),
2811
+ onNext: _cache[6] || (_cache[6] = ($event) => stepPreview(1)),
2812
+ onDownload: _cache[7] || (_cache[7] = ($event) => previewNode.value && download(previewNode.value))
2467
2813
  }, null, 8, [
2468
2814
  "open",
2469
2815
  "file",