vitepress-plugin-filelist 0.5.1 → 0.6.1

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;
@@ -329,6 +329,7 @@ function createOpenListApi(base, token) {
329
329
  return url;
330
330
  },
331
331
  directUrl,
332
+ isAttachmentUrl: (url) => !!root && (url.startsWith(`${root}/p/`) || url.startsWith(`${root}/d/`)),
332
333
  clearCache(path) {
333
334
  if (!path) urls.clear();
334
335
  else for (const key of [...urls.keys()]) if (isWithin(key, path)) urls.delete(key);
@@ -517,16 +518,99 @@ function createDemoApi(delay = 220) {
517
518
  }),
518
519
  fileUrl: async () => "",
519
520
  directUrl: () => "",
521
+ isAttachmentUrl: () => false,
520
522
  clearCache: () => {}
521
523
  };
522
524
  }
525
+ // src/client/ConfirmDialog.vue
526
+ const _hoisted_1$3 = ["aria-label"];
527
+ const _hoisted_2$3 = {
528
+ key: 0,
529
+ class: "fl-confirm-panel"
530
+ };
531
+ const _hoisted_3$2 = { class: "fl-confirm-title" };
532
+ const _hoisted_4$2 = { class: "fl-confirm-text" };
533
+ const _hoisted_5$2 = {
534
+ key: 0,
535
+ class: "fl-confirm-text"
536
+ };
537
+ const _hoisted_6$2 = { class: "fl-confirm-actions" };
538
+ const _sfc_main$3 = /*@__PURE__*/ defineComponent({
539
+ __name: "ConfirmDialog",
540
+ props: {
541
+ open: { type: Boolean },
542
+ title: {},
543
+ text: {},
544
+ tip: {},
545
+ confirmLabel: {},
546
+ cancelLabel: {}
547
+ },
548
+ emits: ["confirm", "close"],
549
+ setup(__props, { emit: __emit }) {
550
+ /** Small native <dialog> for a yes / no question or a notice (bottom sheet on phones). */
551
+ const props = __props;
552
+ const emit = __emit;
553
+ const dialog = ref();
554
+ watch(() => props.open, (open) => {
555
+ const el = dialog.value;
556
+ if (!el) return;
557
+ if (open && !el.open) {
558
+ if (typeof el.showModal === "function") el.showModal();
559
+ else el.setAttribute("open", "");
560
+ } else if (!open && el.open) {
561
+ if (typeof el.close === "function") el.close();
562
+ else el.removeAttribute("open");
563
+ }
564
+ }, { flush: "post" });
565
+ function onNativeClose() {
566
+ if (props.open) emit("close");
567
+ }
568
+ function onBackdrop(event) {
569
+ if (event.target === dialog.value) emit("close");
570
+ }
571
+ return (_ctx, _cache) => {
572
+ return openBlock(), createElementBlock("dialog", {
573
+ ref_key: "dialog",
574
+ ref: dialog,
575
+ class: "fl-confirm",
576
+ "aria-label": __props.title,
577
+ onClose: onNativeClose,
578
+ onClick: onBackdrop
579
+ }, [__props.open ? (openBlock(), createElementBlock("div", _hoisted_2$3, [
580
+ createElementVNode("p", _hoisted_3$2, toDisplayString(__props.title), 1),
581
+ createElementVNode("p", _hoisted_4$2, toDisplayString(__props.text), 1),
582
+ __props.tip ? (openBlock(), createElementBlock("p", _hoisted_5$2, toDisplayString(__props.tip), 1)) : createCommentVNode("v-if", true),
583
+ createElementVNode("div", _hoisted_6$2, [__props.confirmLabel ? (openBlock(), createElementBlock("button", {
584
+ key: 0,
585
+ class: "fl-btn",
586
+ type: "button",
587
+ onClick: _cache[0] || (_cache[0] = ($event) => emit("close"))
588
+ }, toDisplayString(__props.cancelLabel), 1)) : createCommentVNode("v-if", true), createElementVNode("button", {
589
+ class: "fl-btn fl-btn-primary",
590
+ type: "button",
591
+ autofocus: "",
592
+ onClick: _cache[1] || (_cache[1] = ($event) => __props.confirmLabel ? emit("confirm") : emit("close"))
593
+ }, toDisplayString(__props.confirmLabel || __props.cancelLabel), 1)])
594
+ ])) : createCommentVNode("v-if", true)], 40, _hoisted_1$3);
595
+ };
596
+ }
597
+ });
523
598
  // src/client/download.ts
524
- /** Open a file URL; OpenList serves `/p/` and `/d/` as attachments, so this downloads. */
525
- function openUrl(url) {
599
+ /**
600
+ * Hand a file URL to the browser's own download manager.
601
+ *
602
+ * `sameTab` is for links known to answer as attachments (OpenList `/p/`, `/d/`):
603
+ * the browser downloads and the page stays put, with no tab flashing open.
604
+ * Other links (a storage provider's direct URL may display inline) open in a
605
+ * new tab so the document page is never replaced. `download` also keeps a
606
+ * same-origin link out of the VitePress router and names the file.
607
+ */
608
+ function openUrl(url, options) {
526
609
  const anchor = document.createElement("a");
527
610
  anchor.href = url;
528
- anchor.target = "_blank";
611
+ anchor.download = options.name;
529
612
  anchor.rel = "noopener";
613
+ if (!options.sameTab) anchor.target = "_blank";
530
614
  document.body.appendChild(anchor);
531
615
  anchor.click();
532
616
  anchor.remove();
@@ -545,32 +629,113 @@ function saveBlob(blob, name) {
545
629
  function zipFileName(title) {
546
630
  return `${title.replace(/[\\/:*?"<>|]/g, "_")}.zip`;
547
631
  }
632
+ /** Whether the browser can write a ZIP straight into a file the user picks (Chromium). */
633
+ function canStreamToDisk() {
634
+ return typeof window !== "undefined" && window.isSecureContext && "showSaveFilePicker" in window;
635
+ }
548
636
  /**
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.
637
+ * Ask where to save the ZIP. Call it straight from the click, before any other
638
+ * `await`, or the browser refuses. Resolves `null` when the user cancels.
552
639
  */
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
- }
640
+ async function pickZipFile(name) {
641
+ try {
642
+ return await window.showSaveFilePicker({
643
+ suggestedName: name,
644
+ types: [{
645
+ description: "ZIP",
646
+ accept: { "application/zip": [".zip"] }
647
+ }]
648
+ });
649
+ } catch (error) {
650
+ if (error instanceof DOMException && error.name === "AbortError") return null;
651
+ throw error;
652
+ }
653
+ }
654
+ /**
655
+ * The ZIP as a stream: files are fetched a few at a time and passed through
656
+ * as they arrive, stored without compression (most shared files are already
657
+ * compressed). Clashing entry names get ` (2)`, ` (3)`...
658
+ */
659
+ /** Files fetched at once: hides per-file latency, stays under the browser's 6 per host. */
660
+ const CONCURRENCY = 4;
661
+ /** Progress updates at most this often (ms), so the bar does not re-render per chunk. */
662
+ const PROGRESS_INTERVAL = 100;
663
+ async function zipStream(files, options) {
664
+ const { makeZip } = await import("client-zip");
665
+ const progress = {
666
+ bytes: 0,
667
+ totalBytes: files.reduce((sum, file) => sum + (Number.isFinite(file.size) ? file.size : 0), 0),
668
+ files: 0,
669
+ totalFiles: files.length
566
670
  };
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" });
671
+ let reportedAt = 0;
672
+ const report = (force = false) => {
673
+ const now = Date.now();
674
+ if (!force && now - reportedAt < PROGRESS_INTERVAL) return;
675
+ reportedAt = now;
676
+ options.onProgress({ ...progress });
677
+ };
678
+ report(true);
679
+ async function* entries() {
680
+ const used = /* @__PURE__ */ new Set();
681
+ const ahead = [];
682
+ let started = 0;
683
+ const startNext = () => {
684
+ const file = files[started++];
685
+ const response = options.urlOf(file).then((url) => fetch(url, { signal: options.signal }));
686
+ response.catch(() => {});
687
+ ahead.push(response);
688
+ };
689
+ while (started < files.length && ahead.length < CONCURRENCY) startNext();
690
+ for (const file of files) {
691
+ const response = await ahead.shift();
692
+ if (started < files.length) startNext();
693
+ if (!response.ok || !response.body) throw new Error(`${file.name}:HTTP ${response.status}`);
694
+ const counted = response.body.pipeThrough(new TransformStream({
695
+ transform(chunk, controller) {
696
+ progress.bytes += chunk.byteLength;
697
+ report();
698
+ controller.enqueue(chunk);
699
+ },
700
+ flush() {
701
+ progress.files += 1;
702
+ report(progress.files === progress.totalFiles);
703
+ }
704
+ }));
705
+ const modified = new Date(file.modified);
706
+ yield {
707
+ name: uniqueEntryName(zipEntryName(file.zipPath), used),
708
+ lastModified: Number.isNaN(modified.getTime()) ? void 0 : modified,
709
+ input: counted
710
+ };
711
+ }
712
+ }
713
+ return makeZip(entries());
714
+ }
715
+ /** Stream the ZIP into a picked file; memory use stays small whatever the size. */
716
+ async function zipToFile(handle, files, options) {
717
+ const stream = await zipStream(files, options);
718
+ const writable = await handle.createWritable();
719
+ await stream.pipeTo(writable, { signal: options.signal });
720
+ }
721
+ /** Build the ZIP in memory (browsers without file streaming). */
722
+ async function zipToBlob(files, options) {
723
+ const reader = (await zipStream(files, options)).getReader();
724
+ const chunks = [];
725
+ const abort = () => void reader.cancel(options.signal.reason).catch(() => {});
726
+ options.signal.addEventListener("abort", abort, { once: true });
727
+ if (options.signal.aborted) abort();
728
+ try {
729
+ for (;;) {
730
+ const { done, value } = await reader.read();
731
+ if (done) break;
732
+ chunks.push(value);
733
+ }
734
+ } finally {
735
+ options.signal.removeEventListener("abort", abort);
736
+ }
737
+ options.signal.throwIfAborted();
738
+ return new Blob(chunks, { type: "application/zip" });
574
739
  }
575
740
  // src/client/icons.ts
576
741
  /** Stroke icons on a 24×24 grid, drawn by `Icon.vue`. */
@@ -733,7 +898,22 @@ const fileListLocales = {
733
898
  remembered: "已记住",
734
899
  forgetPassword: "忘记",
735
900
  unlockFolder: "输入 {name} 的密码",
736
- networkHint: "可能被跨域拦截:请检查 OpenList 的 cors.allow_origins,或 OpenList-Worker 的 ALLOW_URLS。"
901
+ networkHint: "可能被跨域拦截:请检查 OpenList 的 cors.allow_origins,或 OpenList-Worker 的 ALLOW_URLS。",
902
+ downloadStarted: "已交给浏览器下载:{name}",
903
+ batchProgress: "{done} / {total} · {files} / {count} 个文件",
904
+ batchDone: "已打包 {name}({count} 个文件,{size})",
905
+ batchCanceled: "已取消下载",
906
+ zipWarnTitle: "文件较大({size})",
907
+ zipWarnText: "当前浏览器要先把文件全部放进内存再打包,超过 {limit} 可能失败或让页面崩溃。建议分批下载。",
908
+ zipStreamTip: "在 Chrome 或 Edge 中可以边下载边保存,不受大小限制。",
909
+ zipContinue: "仍然下载",
910
+ zipBlockTitle: "无法打包 {size}",
911
+ zipBlockText: "当前浏览器一次最多打包 {limit},请分批勾选下载。",
912
+ gotIt: "知道了",
913
+ moreRemaining: "还有 {count} 项",
914
+ showMore: "显示更多",
915
+ showAll: "全部显示",
916
+ showRest: "显示剩余 {count} 项"
737
917
  },
738
918
  "en-US": {
739
919
  allFiles: "All files",
@@ -796,7 +976,22 @@ const fileListLocales = {
796
976
  remembered: "Remembered",
797
977
  forgetPassword: "Forget",
798
978
  unlockFolder: "Enter the password for {name}",
799
- networkHint: "Possibly blocked by CORS: check cors.allow_origins in OpenList, or ALLOW_URLS in OpenList-Worker."
979
+ networkHint: "Possibly blocked by CORS: check cors.allow_origins in OpenList, or ALLOW_URLS in OpenList-Worker.",
980
+ downloadStarted: "Downloading {name} in your browser",
981
+ batchProgress: "{done} / {total} · {files} / {count} files",
982
+ batchDone: "Saved {name} ({count} files, {size})",
983
+ batchCanceled: "Download cancelled",
984
+ zipWarnTitle: "Large download ({size})",
985
+ 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.",
986
+ zipStreamTip: "Chrome and Edge save while downloading, with no size limit.",
987
+ zipContinue: "Download anyway",
988
+ zipBlockTitle: "Cannot zip {size}",
989
+ zipBlockText: "This browser can zip at most {limit} at once. Select fewer files.",
990
+ gotIt: "OK",
991
+ moreRemaining: "{count} more",
992
+ showMore: "Show more",
993
+ showAll: "Show all",
994
+ showRest: "Show the remaining {count}"
800
995
  }
801
996
  };
802
997
  function formatLabel(template, values = {}) {
@@ -1392,113 +1587,129 @@ const _hoisted_3 = {
1392
1587
  role: "status"
1393
1588
  };
1394
1589
  const _hoisted_4 = { class: "fl-selection-count" };
1395
- const _hoisted_5 = { class: "fl-selection-size" };
1590
+ const _hoisted_5 = {
1591
+ key: 0,
1592
+ class: "fl-selection-size"
1593
+ };
1396
1594
  const _hoisted_6 = {
1397
1595
  key: 1,
1596
+ class: "fl-selection",
1597
+ role: "status"
1598
+ };
1599
+ const _hoisted_7 = { class: "fl-selection-count" };
1600
+ const _hoisted_8 = { class: "fl-selection-size" };
1601
+ const _hoisted_9 = {
1602
+ key: 2,
1398
1603
  class: "fl-title"
1399
1604
  };
1400
- const _hoisted_7 = { class: "fl-title-text" };
1401
- const _hoisted_8 = {
1605
+ const _hoisted_10 = { class: "fl-title-text" };
1606
+ const _hoisted_11 = {
1402
1607
  key: 0,
1403
1608
  class: "fl-demo-badge"
1404
1609
  };
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 = [
1610
+ const _hoisted_12 = ["aria-label", "title"];
1611
+ const _hoisted_13 = { class: "fl-clear-text" };
1612
+ const _hoisted_14 = { class: "fl-tools" };
1613
+ const _hoisted_15 = { class: "fl-search" };
1614
+ const _hoisted_16 = ["placeholder", "aria-label"];
1615
+ const _hoisted_17 = [
1411
1616
  "disabled",
1412
1617
  "aria-label",
1413
1618
  "title"
1414
1619
  ];
1415
- const _hoisted_15 = ["disabled"];
1416
- const _hoisted_16 = { class: "fl-row fl-head" };
1417
- const _hoisted_17 = {
1620
+ const _hoisted_18 = { class: "fl-row fl-head" };
1621
+ const _hoisted_19 = {
1418
1622
  key: 0,
1419
1623
  class: "fl-progress",
1420
1624
  "aria-hidden": "true"
1421
1625
  };
1422
- const _hoisted_18 = { class: "fl-cell fl-cell-check" };
1423
- const _hoisted_19 = [
1626
+ const _hoisted_20 = { class: "fl-cell fl-cell-check" };
1627
+ const _hoisted_21 = [
1424
1628
  "aria-label",
1629
+ "disabled",
1425
1630
  "checked",
1426
1631
  ".indeterminate"
1427
1632
  ];
1428
- const _hoisted_20 = ["aria-sort", "onClick"];
1429
- const _hoisted_21 = {
1633
+ const _hoisted_22 = ["aria-sort", "onClick"];
1634
+ const _hoisted_23 = {
1430
1635
  class: "fl-sort",
1431
1636
  "aria-hidden": "true"
1432
1637
  };
1433
- const _hoisted_22 = { class: "fl-cell fl-cell-op" };
1434
- const _hoisted_23 = { class: "fl-sort-select" };
1435
- const _hoisted_24 = {
1638
+ const _hoisted_24 = { class: "fl-cell fl-cell-op" };
1639
+ const _hoisted_25 = { class: "fl-sort-select" };
1640
+ const _hoisted_26 = {
1436
1641
  class: "fl-sort-current",
1437
1642
  "aria-hidden": "true"
1438
1643
  };
1439
- const _hoisted_25 = ["aria-label"];
1440
- const _hoisted_26 = ["value"];
1441
- const _hoisted_27 = {
1644
+ const _hoisted_27 = ["aria-label"];
1645
+ const _hoisted_28 = ["value"];
1646
+ const _hoisted_29 = {
1442
1647
  key: 0,
1443
1648
  class: "fl-status"
1444
1649
  };
1445
- const _hoisted_28 = ["for"];
1446
- const _hoisted_29 = ["id"];
1447
- const _hoisted_30 = { class: "fl-unlock" };
1448
- const _hoisted_31 = [
1650
+ const _hoisted_30 = ["for"];
1651
+ const _hoisted_31 = ["id"];
1652
+ const _hoisted_32 = { class: "fl-unlock" };
1653
+ const _hoisted_33 = [
1449
1654
  "id",
1450
1655
  "data-unlock",
1451
1656
  "placeholder",
1452
1657
  "aria-invalid",
1453
1658
  "aria-describedby"
1454
1659
  ];
1455
- const _hoisted_32 = [
1660
+ const _hoisted_34 = [
1456
1661
  "aria-pressed",
1457
1662
  "aria-label",
1458
1663
  "title"
1459
1664
  ];
1460
- const _hoisted_33 = ["disabled"];
1461
- const _hoisted_34 = {
1665
+ const _hoisted_35 = ["disabled"];
1666
+ const _hoisted_36 = {
1462
1667
  key: 2,
1463
1668
  class: "fl-status fl-error"
1464
1669
  };
1465
- const _hoisted_35 = {
1670
+ const _hoisted_37 = {
1466
1671
  key: 0,
1467
1672
  class: "fl-hint"
1468
1673
  };
1469
- const _hoisted_36 = {
1674
+ const _hoisted_38 = {
1470
1675
  key: 1,
1471
1676
  class: "fl-hint"
1472
1677
  };
1473
- const _hoisted_37 = {
1678
+ const _hoisted_39 = {
1474
1679
  key: 3,
1475
1680
  class: "fl-status"
1476
1681
  };
1477
- const _hoisted_38 = { class: "fl-cell fl-cell-check" };
1478
- const _hoisted_39 = [
1682
+ const _hoisted_40 = { class: "fl-cell fl-more-cell" };
1683
+ const _hoisted_41 = { class: "fl-more-count" };
1684
+ const _hoisted_42 = ["onClick"];
1685
+ const _hoisted_43 = { class: "fl-more-page" };
1686
+ const _hoisted_44 = ["onClick"];
1687
+ const _hoisted_45 = ["onClick"];
1688
+ const _hoisted_46 = { class: "fl-cell fl-cell-check" };
1689
+ const _hoisted_47 = [
1479
1690
  "disabled",
1480
1691
  "aria-label",
1481
1692
  "checked",
1482
1693
  ".indeterminate",
1483
1694
  "onChange"
1484
1695
  ];
1485
- const _hoisted_40 = ["title"];
1486
- const _hoisted_41 = [
1696
+ const _hoisted_48 = ["title"];
1697
+ const _hoisted_49 = [
1487
1698
  "aria-label",
1488
1699
  "title",
1489
1700
  "onClick"
1490
1701
  ];
1491
- const _hoisted_42 = [
1702
+ const _hoisted_50 = [
1492
1703
  "aria-label",
1493
1704
  "aria-expanded",
1494
1705
  "onClick"
1495
1706
  ];
1496
- const _hoisted_43 = {
1707
+ const _hoisted_51 = {
1497
1708
  key: 2,
1498
1709
  class: "fl-arrow-placeholder",
1499
1710
  "aria-hidden": "true"
1500
1711
  };
1501
- const _hoisted_44 = {
1712
+ const _hoisted_52 = {
1502
1713
  key: 3,
1503
1714
  class: "fl-folder-icon",
1504
1715
  viewBox: "0 0 24 24",
@@ -1506,27 +1717,27 @@ const _hoisted_44 = {
1506
1717
  height: "18",
1507
1718
  "aria-hidden": "true"
1508
1719
  };
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 = {
1720
+ const _hoisted_53 = ["data-kind"];
1721
+ const _hoisted_54 = { class: "fl-label" };
1722
+ const _hoisted_55 = ["onClick"];
1723
+ const _hoisted_56 = { class: "fl-meta" };
1724
+ const _hoisted_57 = {
1514
1725
  key: 5,
1515
1726
  class: "fl-count"
1516
1727
  };
1517
- const _hoisted_50 = ["id"];
1518
- const _hoisted_51 = [
1728
+ const _hoisted_58 = ["id"];
1729
+ const _hoisted_59 = [
1519
1730
  "aria-label",
1520
1731
  "title",
1521
1732
  "onClick"
1522
1733
  ];
1523
- const _hoisted_52 = {
1734
+ const _hoisted_60 = {
1524
1735
  key: 8,
1525
1736
  class: "fl-loading",
1526
1737
  "aria-hidden": "true"
1527
1738
  };
1528
- const _hoisted_53 = ["onSubmit", "onKeydown"];
1529
- const _hoisted_54 = [
1739
+ const _hoisted_61 = ["onSubmit", "onKeydown"];
1740
+ const _hoisted_62 = [
1530
1741
  "onUpdate:modelValue",
1531
1742
  "data-unlock",
1532
1743
  "placeholder",
@@ -1534,43 +1745,45 @@ const _hoisted_54 = [
1534
1745
  "aria-invalid",
1535
1746
  "aria-describedby"
1536
1747
  ];
1537
- const _hoisted_55 = [
1748
+ const _hoisted_63 = [
1538
1749
  "aria-pressed",
1539
1750
  "aria-label",
1540
1751
  "title",
1541
1752
  "onClick"
1542
1753
  ];
1543
- const _hoisted_56 = ["disabled"];
1544
- const _hoisted_57 = [
1754
+ const _hoisted_64 = ["disabled"];
1755
+ const _hoisted_65 = [
1545
1756
  "aria-label",
1546
1757
  "title",
1547
1758
  "onClick"
1548
1759
  ];
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 = [
1760
+ const _hoisted_66 = { class: "fl-cell fl-cell-size" };
1761
+ const _hoisted_67 = { class: "fl-cell fl-cell-date" };
1762
+ const _hoisted_68 = { class: "fl-cell fl-cell-op" };
1763
+ const _hoisted_69 = ["aria-label", "onClick"];
1764
+ const _hoisted_70 = [
1554
1765
  "aria-label",
1555
1766
  "aria-expanded",
1556
1767
  "onClick"
1557
1768
  ];
1558
- const _hoisted_63 = [
1769
+ const _hoisted_71 = [
1559
1770
  "aria-label",
1560
1771
  "title",
1561
1772
  "onClick"
1562
1773
  ];
1563
- const _hoisted_64 = [
1774
+ const _hoisted_72 = [
1564
1775
  "aria-label",
1565
1776
  "title",
1566
1777
  "onClick"
1567
1778
  ];
1568
- const _hoisted_65 = { class: "fl-foot" };
1569
- const _hoisted_66 = {
1779
+ const _hoisted_73 = { class: "fl-foot" };
1780
+ const _hoisted_74 = {
1570
1781
  key: 4,
1571
1782
  class: "fl-toast",
1572
1783
  role: "status"
1573
1784
  };
1785
+ /** Rows shown per folder, beyond the first page; kept across refreshes. */
1786
+ const MB = 1048576;
1574
1787
  const _sfc_main = /*@__PURE__*/ defineComponent({
1575
1788
  __name: "FileList",
1576
1789
  props: {
@@ -1588,6 +1801,9 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1588
1801
  type: Boolean,
1589
1802
  default: true
1590
1803
  },
1804
+ pageSize: { default: 200 },
1805
+ zipWarnSize: {},
1806
+ zipMaxSize: {},
1591
1807
  locale: { default: "zh-CN" },
1592
1808
  labels: {}
1593
1809
  },
@@ -1630,7 +1846,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1630
1846
  const siteTitle = ref("");
1631
1847
  const iframePreviews = ref("");
1632
1848
  const batchBusy = ref(false);
1633
- const batchProgress = ref(0);
1849
+ const zipProgress = ref(null);
1850
+ let zipAbort = null;
1634
1851
  let toastTimer;
1635
1852
  /** The folder listed in single-path mode, as the parent of the top rows. */
1636
1853
  const listedFolder = computed(() => ({
@@ -1863,6 +2080,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1863
2080
  errorMsg.value = "";
1864
2081
  try {
1865
2082
  roots.value = await buildTree();
2083
+ shown.clear();
1866
2084
  rootLock.value = null;
1867
2085
  selected.value = /* @__PURE__ */ new Set();
1868
2086
  api.value.clearCache();
@@ -1943,20 +2161,41 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1943
2161
  applySort(key, direction !== "desc");
1944
2162
  }
1945
2163
  });
1946
- const visible = computed(() => {
2164
+ /** A “show more” row closing a folder whose children are cut at `pageSize`. */
2165
+ const shown = reactive(/* @__PURE__ */ new Map());
2166
+ function limitOf(key) {
2167
+ const size = Math.max(0, Math.floor(props.pageSize ?? 200));
2168
+ return size ? shown.get(key) ?? size : Number.POSITIVE_INFINITY;
2169
+ }
2170
+ function showMore(row, all) {
2171
+ const size = Math.max(1, Math.floor(props.pageSize ?? 200));
2172
+ shown.set(row.key, all ? Number.POSITIVE_INFINITY : limitOf(row.key) + size);
2173
+ }
2174
+ function isMore(row) {
2175
+ return "more" in row;
2176
+ }
2177
+ const rows = computed(() => {
1947
2178
  const out = [];
1948
2179
  const query = keyword.value.trim().toLowerCase();
1949
2180
  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;
2181
+ const walk = (nodes, key, depth) => {
2182
+ const list = nodes.filter(matches);
2183
+ const limit = query ? list.length : Math.min(list.length, limitOf(key));
2184
+ for (const node of list.slice(0, limit)) {
1953
2185
  out.push(node);
1954
- if (node.is_dir && (node.expanded || query) && node.children.length) walk(node.children);
2186
+ if (node.is_dir && (node.expanded || query) && node.children.length) walk(node.children, node.path, node.depth + 1);
1955
2187
  }
2188
+ if (limit < list.length) out.push({
2189
+ more: true,
2190
+ key,
2191
+ depth,
2192
+ remaining: list.length - limit
2193
+ });
1956
2194
  };
1957
- walk(roots.value);
2195
+ walk(roots.value, props.sources.length ? "" : props.path, 0);
1958
2196
  return out;
1959
2197
  });
2198
+ const visible = computed(() => rows.value.filter((row) => !isMore(row)));
1960
2199
  const stats = computed(() => folderStats(roots.value, selected.value));
1961
2200
  const noStats = {
1962
2201
  files: 0,
@@ -2038,29 +2277,114 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2038
2277
  return;
2039
2278
  }
2040
2279
  try {
2041
- openUrl(await fileUrlOf(node));
2280
+ const url = await fileUrlOf(node);
2281
+ openUrl(url, {
2282
+ sameTab: api.value.isAttachmentUrl(url),
2283
+ name: node.name
2284
+ });
2285
+ showToast(t("downloadStarted", { name: node.name }));
2042
2286
  } catch (error) {
2043
2287
  showToast(t("downloadFailed", { message: errorText(error) }));
2044
2288
  }
2045
2289
  }
2290
+ function zipLimits() {
2291
+ const touch = typeof window !== "undefined" && !!window.matchMedia?.("(pointer: coarse)").matches;
2292
+ const bytes = (value, fallback) => {
2293
+ const mb = value ?? fallback;
2294
+ return mb > 0 ? mb * MB : Number.POSITIVE_INFINITY;
2295
+ };
2296
+ return {
2297
+ warn: bytes(props.zipWarnSize, touch ? 200 : 500),
2298
+ max: bytes(props.zipMaxSize, touch ? 1024 : 2048)
2299
+ };
2300
+ }
2301
+ const zipNotice = ref(null);
2302
+ /** Only desktop browsers get “use Chrome or Edge”; on iPhone every browser is Safari. */
2303
+ const zipTip = computed(() => typeof window !== "undefined" && !window.matchMedia?.("(pointer: coarse)").matches ? t("zipStreamTip") : "");
2304
+ function zipName() {
2305
+ return zipFileName(props.title || t("defaultZip"));
2306
+ }
2307
+ /**
2308
+ * Chrome / Edge stream into a file the user picks (no size limit). Other
2309
+ * browsers build the ZIP in memory, so large selections ask first or are refused.
2310
+ */
2046
2311
  async function batchDownload() {
2047
2312
  const files = selectedFiles.value;
2048
- if (!files.length) return;
2313
+ if (!files.length || batchBusy.value) return;
2049
2314
  if (demo.value) {
2050
2315
  showToast(t("demoBatch", { count: files.length }));
2051
2316
  return;
2052
2317
  }
2318
+ if (canStreamToDisk()) {
2319
+ let handle;
2320
+ try {
2321
+ handle = await pickZipFile(zipName());
2322
+ } catch (error) {
2323
+ showToast(t("batchFailed", { message: errorText(error) }));
2324
+ return;
2325
+ }
2326
+ if (handle) await runZip(files, (options) => zipToFile(handle, files, options), handle.name);
2327
+ return;
2328
+ }
2329
+ const size = totalSize(files);
2330
+ const { warn, max } = zipLimits();
2331
+ if (size > max) zipNotice.value = {
2332
+ kind: "block",
2333
+ size,
2334
+ limit: max,
2335
+ files
2336
+ };
2337
+ else if (size > warn) zipNotice.value = {
2338
+ kind: "warn",
2339
+ size,
2340
+ limit: warn,
2341
+ files
2342
+ };
2343
+ else await zipInMemory(files);
2344
+ }
2345
+ async function zipInMemory(files) {
2346
+ const name = zipName();
2347
+ await runZip(files, async (options) => saveBlob(await zipToBlob(files, options), name), name);
2348
+ }
2349
+ function confirmZip() {
2350
+ const notice = zipNotice.value;
2351
+ zipNotice.value = null;
2352
+ if (notice?.kind === "warn") zipInMemory(notice.files);
2353
+ }
2354
+ async function runZip(files, run, name) {
2355
+ const controller = new AbortController();
2356
+ zipAbort = controller;
2053
2357
  batchBusy.value = true;
2054
- batchProgress.value = 0;
2358
+ zipProgress.value = null;
2055
2359
  try {
2056
- saveBlob(await zipFiles(files, fileUrlOf, (percent) => batchProgress.value = percent), zipFileName(props.title || t("defaultZip")));
2360
+ await run({
2361
+ urlOf: fileUrlOf,
2362
+ signal: controller.signal,
2363
+ onProgress: (progress) => zipProgress.value = progress
2364
+ });
2365
+ showToast(t("batchDone", {
2366
+ name,
2367
+ count: files.length,
2368
+ size: formatSize(totalSize(files))
2369
+ }));
2057
2370
  selected.value = /* @__PURE__ */ new Set();
2058
2371
  } catch (error) {
2059
- showToast(t("batchFailed", { message: errorText(error) }));
2372
+ showToast(controller.signal.aborted ? t("batchCanceled") : t("batchFailed", { message: errorText(error) }));
2060
2373
  } finally {
2374
+ zipAbort = null;
2061
2375
  batchBusy.value = false;
2376
+ zipProgress.value = null;
2062
2377
  }
2063
2378
  }
2379
+ function cancelZip() {
2380
+ zipAbort?.abort();
2381
+ }
2382
+ const zipPercent = computed(() => {
2383
+ const progress = zipProgress.value;
2384
+ if (!progress) return 0;
2385
+ const ratio = progress.totalBytes ? progress.bytes / progress.totalBytes : progress.totalFiles ? progress.files / progress.totalFiles : 0;
2386
+ return Math.min(100, Math.floor(ratio * 100));
2387
+ });
2064
2388
  /** Only types the dialog can actually render get a preview button. */
2065
2389
  function canPreview(node) {
2066
2390
  return !node.is_dir && previewKind(node.name) !== "none";
@@ -2132,6 +2456,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2132
2456
  window.addEventListener("resize", measureStickyTop, { passive: true });
2133
2457
  });
2134
2458
  onBeforeUnmount(() => {
2459
+ zipAbort?.abort();
2135
2460
  window.removeEventListener("resize", measureStickyTop);
2136
2461
  clearTimeout(toastTimer);
2137
2462
  });
@@ -2147,9 +2472,22 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2147
2472
  "aria-busy": refreshing.value || loadingRoot.value
2148
2473
  }, [
2149
2474
  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,
2475
+ batchBusy.value ? (openBlock(), createElementBlock("div", _hoisted_3, [
2476
+ createElementVNode("span", _hoisted_4, toDisplayString(t("batching", { percent: zipPercent.value })), 1),
2477
+ zipProgress.value ? (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString(t("batchProgress", {
2478
+ done: unref(formatSize)(zipProgress.value.bytes),
2479
+ total: unref(formatSize)(zipProgress.value.totalBytes),
2480
+ files: zipProgress.value.files,
2481
+ count: zipProgress.value.totalFiles
2482
+ })), 1)) : createCommentVNode("v-if", true),
2483
+ createElementVNode("span", {
2484
+ class: "fl-zip-bar",
2485
+ style: normalizeStyle({ width: `${zipPercent.value}%` }),
2486
+ "aria-hidden": "true"
2487
+ }, null, 4)
2488
+ ])) : 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)])),
2489
+ selected.value.size && !batchBusy.value ? (openBlock(), createElementBlock("button", {
2490
+ key: 3,
2153
2491
  class: "fl-clear",
2154
2492
  type: "button",
2155
2493
  "aria-label": t("clearSelection"),
@@ -2159,8 +2497,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2159
2497
  class: "fl-clear-icon",
2160
2498
  name: "close",
2161
2499
  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, {
2500
+ }), createElementVNode("span", _hoisted_13, toDisplayString(t("clear")), 1)], 8, _hoisted_12)) : createCommentVNode("v-if", true),
2501
+ createElementVNode("div", _hoisted_14, [createElementVNode("label", _hoisted_15, [createVNode(_sfc_main$2, {
2164
2502
  name: "search",
2165
2503
  size: 15
2166
2504
  }), withDirectives(createElementVNode("input", {
@@ -2168,7 +2506,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2168
2506
  type: "text",
2169
2507
  placeholder: t("filter"),
2170
2508
  "aria-label": t("filter")
2171
- }, null, 8, _hoisted_13), [[vModelText, keyword.value]])]), createElementVNode("button", {
2509
+ }, null, 8, _hoisted_16), [[vModelText, keyword.value]])]), createElementVNode("button", {
2172
2510
  class: normalizeClass(["fl-icon-btn fl-refresh", { "fl-spinning": refreshing.value }]),
2173
2511
  type: "button",
2174
2512
  disabled: refreshing.value || loadingRoot.value,
@@ -2178,27 +2516,32 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2178
2516
  }, [createVNode(_sfc_main$2, {
2179
2517
  name: "refresh",
2180
2518
  size: 15
2181
- })], 10, _hoisted_14)]),
2182
- selected.value.size ? (openBlock(), createElementBlock("button", {
2183
- key: 3,
2519
+ })], 10, _hoisted_17)]),
2520
+ batchBusy.value ? (openBlock(), createElementBlock("button", {
2521
+ key: 4,
2522
+ class: "fl-btn fl-zip-cancel",
2523
+ type: "button",
2524
+ onClick: cancelZip
2525
+ }, toDisplayString(t("cancel")), 1)) : selected.value.size ? (openBlock(), createElementBlock("button", {
2526
+ key: 5,
2184
2527
  class: "fl-btn fl-btn-primary",
2185
2528
  type: "button",
2186
- disabled: batchBusy.value,
2187
2529
  onClick: batchDownload
2188
2530
  }, [createVNode(_sfc_main$2, {
2189
2531
  name: "download",
2190
2532
  size: 15
2191
- }), createTextVNode(" " + toDisplayString(batchBusy.value ? t("batching", { percent: batchProgress.value }) : t("batch")), 1)], 8, _hoisted_15)) : createCommentVNode("v-if", true)
2533
+ }), createTextVNode(" " + toDisplayString(t("batch")), 1)])) : createCommentVNode("v-if", true)
2192
2534
  ]),
2193
- createElementVNode("div", _hoisted_16, [
2194
- refreshing.value ? (openBlock(), createElementBlock("span", _hoisted_17)) : createCommentVNode("v-if", true),
2195
- createElementVNode("label", _hoisted_18, [createElementVNode("input", {
2535
+ createElementVNode("div", _hoisted_18, [
2536
+ refreshing.value ? (openBlock(), createElementBlock("span", _hoisted_19)) : createCommentVNode("v-if", true),
2537
+ createElementVNode("label", _hoisted_20, [createElementVNode("input", {
2196
2538
  type: "checkbox",
2197
2539
  "aria-label": t("selectAll"),
2540
+ disabled: batchBusy.value,
2198
2541
  checked: allChecked.value,
2199
2542
  ".indeterminate": someChecked.value,
2200
2543
  onChange: toggleCheckAll
2201
- }, null, 40, _hoisted_19)]),
2544
+ }, null, 40, _hoisted_21)]),
2202
2545
  (openBlock(), createElementBlock(Fragment, null, renderList([
2203
2546
  "name",
2204
2547
  "size",
@@ -2212,11 +2555,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2212
2555
  type: "button",
2213
2556
  "aria-sort": sortKey.value === column ? sortAsc.value ? "ascending" : "descending" : void 0,
2214
2557
  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);
2558
+ }, [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
2559
  }), 64)),
2217
- createElementVNode("span", _hoisted_22, [createElementVNode("label", _hoisted_23, [
2560
+ createElementVNode("span", _hoisted_24, [createElementVNode("label", _hoisted_25, [
2218
2561
  createElementVNode("span", null, toDisplayString(t("sortBy")), 1),
2219
- createElementVNode("span", _hoisted_24, toDisplayString(sortOptions.value.find((option) => option.value === sortValue.value)?.label), 1),
2562
+ createElementVNode("span", _hoisted_26, toDisplayString(sortOptions.value.find((option) => option.value === sortValue.value)?.label), 1),
2220
2563
  withDirectives(createElementVNode("select", {
2221
2564
  "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => sortValue.value = $event),
2222
2565
  "aria-label": t("sortBy")
@@ -2224,11 +2567,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2224
2567
  return openBlock(), createElementBlock("option", {
2225
2568
  key: option.value,
2226
2569
  value: option.value
2227
- }, toDisplayString(option.label), 9, _hoisted_26);
2228
- }), 128))], 8, _hoisted_25), [[vModelSelect, sortValue.value]])
2570
+ }, toDisplayString(option.label), 9, _hoisted_28);
2571
+ }), 128))], 8, _hoisted_27), [[vModelSelect, sortValue.value]])
2229
2572
  ])])
2230
2573
  ]),
2231
- loadingRoot.value ? (openBlock(), createElementBlock("div", _hoisted_27, toDisplayString(t("loading")), 1)) : rootLock.value ? (openBlock(), createElementBlock("form", {
2574
+ loadingRoot.value ? (openBlock(), createElementBlock("div", _hoisted_29, toDisplayString(t("loading")), 1)) : rootLock.value ? (openBlock(), createElementBlock("form", {
2232
2575
  key: 1,
2233
2576
  class: normalizeClass(["fl-unlock-root", { "fl-unlock-error": rootLock.value.error }]),
2234
2577
  onSubmit: withModifiers(submitRootUnlock, ["prevent"])
@@ -2240,14 +2583,14 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2240
2583
  createElementVNode("label", {
2241
2584
  class: "fl-unlock-label",
2242
2585
  for: `${unref(uid)}-root`
2243
- }, toDisplayString(t("passwordPrompt")), 9, _hoisted_28),
2586
+ }, toDisplayString(t("passwordPrompt")), 9, _hoisted_30),
2244
2587
  rootLock.value.error ? (openBlock(), createElementBlock("span", {
2245
2588
  key: 0,
2246
2589
  id: `${unref(uid)}-root-error`,
2247
2590
  class: "fl-unlock-message",
2248
2591
  role: "alert"
2249
- }, toDisplayString(unlockErrorText(rootLock.value.error)), 9, _hoisted_29)) : createCommentVNode("v-if", true),
2250
- createElementVNode("span", _hoisted_30, [
2592
+ }, toDisplayString(unlockErrorText(rootLock.value.error)), 9, _hoisted_31)) : createCommentVNode("v-if", true),
2593
+ createElementVNode("span", _hoisted_32, [
2251
2594
  withDirectives(createElementVNode("input", {
2252
2595
  id: `${unref(uid)}-root`,
2253
2596
  "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => rootLock.value.input = $event),
@@ -2257,7 +2600,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2257
2600
  placeholder: t("passwordPlaceholder"),
2258
2601
  "aria-invalid": !!rootLock.value.error,
2259
2602
  "aria-describedby": rootLock.value.error ? `${unref(uid)}-root-error` : void 0
2260
- }, null, 8, _hoisted_31), [[vModelText, rootLock.value.input]]),
2603
+ }, null, 8, _hoisted_33), [[vModelText, rootLock.value.input]]),
2261
2604
  canRemember.value ? (openBlock(), createElementBlock("button", {
2262
2605
  key: 0,
2263
2606
  class: "fl-unlock-remember",
@@ -2266,26 +2609,50 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2266
2609
  "aria-label": t("rememberPassword"),
2267
2610
  title: t("rememberPassword"),
2268
2611
  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),
2612
+ }, [createVNode(_sfc_main$2, { name: "key" })], 8, _hoisted_34)) : createCommentVNode("v-if", true),
2270
2613
  createElementVNode("button", {
2271
2614
  class: "fl-unlock-submit",
2272
2615
  type: "submit",
2273
2616
  disabled: rootLock.value.busy || !rootLock.value.input
2274
- }, toDisplayString(t("open")), 9, _hoisted_33)
2617
+ }, toDisplayString(t("open")), 9, _hoisted_35)
2275
2618
  ])
2276
- ], 34)) : errorMsg.value ? (openBlock(), createElementBlock("div", _hoisted_34, [
2619
+ ], 34)) : errorMsg.value ? (openBlock(), createElementBlock("div", _hoisted_36, [
2277
2620
  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),
2621
+ /guest/i.test(errorMsg.value) ? (openBlock(), createElementBlock("p", _hoisted_37, toDisplayString(t("guestHint")), 1)) : createCommentVNode("v-if", true),
2622
+ unref(isNetworkError)(errorMsg.value) ? (openBlock(), createElementBlock("p", _hoisted_38, toDisplayString(t("corsHint")), 1)) : createCommentVNode("v-if", true),
2280
2623
  createElementVNode("button", {
2281
2624
  class: "fl-btn",
2282
2625
  type: "button",
2283
2626
  onClick: loadRoot
2284
2627
  }, 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,
2628
+ ])) : visible.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_39, toDisplayString(t("empty")), 1)) : createCommentVNode("v-if", true),
2629
+ (openBlock(true), createElementBlock(Fragment, null, renderList(rows.value, (node, index) => {
2630
+ return openBlock(), createElementBlock(Fragment, { key: isMore(node) ? `more:${node.key}` : node.path }, [isMore(node) ? (openBlock(), createElementBlock("div", {
2631
+ key: 0,
2632
+ class: "fl-row fl-more",
2633
+ style: normalizeStyle({ "--fl-depth": node.depth })
2634
+ }, [_cache[9] || (_cache[9] = createElementVNode("span", { class: "fl-cell fl-cell-check" }, null, -1)), createElementVNode("span", _hoisted_40, [
2635
+ _cache[8] || (_cache[8] = createElementVNode("span", {
2636
+ class: "fl-indent",
2637
+ "aria-hidden": "true"
2638
+ }, null, -1)),
2639
+ createElementVNode("span", _hoisted_41, toDisplayString(t("moreRemaining", { count: node.remaining })), 1),
2640
+ node.remaining > (__props.pageSize || 0) ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("button", {
2641
+ class: "fl-more-btn",
2642
+ type: "button",
2643
+ onClick: ($event) => showMore(node, false)
2644
+ }, [createTextVNode(toDisplayString(t("showMore")), 1), createElementVNode("span", _hoisted_43, toDisplayString(` ${t("folderItems", { count: __props.pageSize })}`), 1)], 8, _hoisted_42), createElementVNode("button", {
2645
+ class: "fl-more-btn fl-more-all",
2646
+ type: "button",
2647
+ onClick: ($event) => showMore(node, true)
2648
+ }, toDisplayString(t("showAll")), 9, _hoisted_44)], 64)) : (openBlock(), createElementBlock("button", {
2649
+ key: 1,
2650
+ class: "fl-more-btn",
2651
+ type: "button",
2652
+ onClick: ($event) => showMore(node, true)
2653
+ }, toDisplayString(t("showRest", { count: node.remaining })), 9, _hoisted_45))
2654
+ ])], 4)) : (openBlock(), createElementBlock("div", {
2655
+ key: 1,
2289
2656
  class: normalizeClass(["fl-row", {
2290
2657
  "fl-row-dir": node.is_dir,
2291
2658
  "fl-row-root": node.isRoot,
@@ -2296,19 +2663,19 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2296
2663
  }]),
2297
2664
  style: normalizeStyle({ "--fl-depth": node.depth })
2298
2665
  }, [
2299
- createElementVNode("label", _hoisted_38, [createElementVNode("input", {
2666
+ createElementVNode("label", _hoisted_46, [createElementVNode("input", {
2300
2667
  type: "checkbox",
2301
- disabled: !!node.lock,
2668
+ disabled: !!node.lock || batchBusy.value,
2302
2669
  "aria-label": t("selectItem", { name: node.name }),
2303
2670
  checked: isChecked(node),
2304
2671
  ".indeterminate": isIndeterminate(node),
2305
2672
  onChange: ($event) => toggleCheck(node, $event)
2306
- }, null, 40, _hoisted_39)]),
2673
+ }, null, 40, _hoisted_47)]),
2307
2674
  createElementVNode("span", {
2308
2675
  class: "fl-cell fl-cell-name",
2309
2676
  title: node.isRoot ? node.path : void 0
2310
2677
  }, [
2311
- _cache[8] || (_cache[8] = createElementVNode("span", {
2678
+ _cache[11] || (_cache[11] = createElementVNode("span", {
2312
2679
  class: "fl-indent",
2313
2680
  "aria-hidden": "true"
2314
2681
  }, null, -1)),
@@ -2322,7 +2689,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2322
2689
  }, [createVNode(_sfc_main$2, {
2323
2690
  name: "lock",
2324
2691
  size: 14
2325
- })], 8, _hoisted_41)) : node.is_dir ? (openBlock(), createElementBlock("button", {
2692
+ })], 8, _hoisted_49)) : node.is_dir ? (openBlock(), createElementBlock("button", {
2326
2693
  key: 1,
2327
2694
  class: normalizeClass(["fl-arrow", { "fl-arrow-open": node.expanded }]),
2328
2695
  type: "button",
@@ -2332,24 +2699,24 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2332
2699
  }, [createVNode(_sfc_main$2, {
2333
2700
  name: "next",
2334
2701
  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", {
2702
+ })], 10, _hoisted_50)) : (openBlock(), createElementBlock("span", _hoisted_51)),
2703
+ 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
2704
  key: 4,
2338
2705
  class: "fl-type",
2339
2706
  "data-kind": unref(fileKind)(node.name),
2340
2707
  "aria-hidden": "true"
2341
- }, toDisplayString(unref(extOf)(node.name) || "FILE"), 9, _hoisted_45)),
2342
- createElementVNode("span", _hoisted_46, [createElementVNode("span", {
2708
+ }, toDisplayString(unref(extOf)(node.name) || "FILE"), 9, _hoisted_53)),
2709
+ createElementVNode("span", _hoisted_54, [createElementVNode("span", {
2343
2710
  class: normalizeClass(["fl-name", { "fl-name-dir": node.is_dir }]),
2344
2711
  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),
2712
+ }, 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)])]),
2713
+ node.is_dir && node.loaded ? (openBlock(), createElementBlock("span", _hoisted_57, toDisplayString(t("folderItems", { count: statsOf(node).files })), 1)) : createCommentVNode("v-if", true),
2347
2714
  node.lock?.prompting && node.lock.error ? (openBlock(), createElementBlock("span", {
2348
2715
  key: 6,
2349
2716
  id: `${unref(uid)}-error-${index}`,
2350
2717
  class: "fl-unlock-message",
2351
2718
  role: "alert"
2352
- }, toDisplayString(unlockErrorText(node.lock.error)), 9, _hoisted_50)) : createCommentVNode("v-if", true),
2719
+ }, toDisplayString(unlockErrorText(node.lock.error)), 9, _hoisted_58)) : createCommentVNode("v-if", true),
2353
2720
  !node.lock && unref(remembered).has(node.path) ? (openBlock(), createElementBlock("button", {
2354
2721
  key: 7,
2355
2722
  class: "fl-remembered",
@@ -2360,9 +2727,9 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2360
2727
  }, [createVNode(_sfc_main$2, {
2361
2728
  name: "key",
2362
2729
  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),
2730
+ }), createTextVNode(" " + toDisplayString(t("remembered")) + " · " + toDisplayString(t("forgetPassword")), 1)], 8, _hoisted_59)) : createCommentVNode("v-if", true),
2731
+ node.loading ? (openBlock(), createElementBlock("span", _hoisted_60, "…")) : createCommentVNode("v-if", true)
2732
+ ], 8, _hoisted_48),
2366
2733
  node.lock?.prompting ? (openBlock(), createElementBlock("form", {
2367
2734
  key: 0,
2368
2735
  class: "fl-unlock fl-unlock-row",
@@ -2383,7 +2750,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2383
2750
  "aria-label": t("passwordFor", { name: node.name }),
2384
2751
  "aria-invalid": !!node.lock.error,
2385
2752
  "aria-describedby": node.lock.error ? `${unref(uid)}-error-${index}` : void 0
2386
- }, null, 8, _hoisted_54), [[vModelText, node.lock.input]]),
2753
+ }, null, 8, _hoisted_62), [[vModelText, node.lock.input]]),
2387
2754
  canRemember.value ? (openBlock(), createElementBlock("button", {
2388
2755
  key: 0,
2389
2756
  class: "fl-unlock-remember",
@@ -2392,12 +2759,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2392
2759
  "aria-label": t("rememberPassword"),
2393
2760
  title: t("rememberPassword"),
2394
2761
  onClick: ($event) => node.lock.remember = !node.lock.remember
2395
- }, [createVNode(_sfc_main$2, { name: "key" })], 8, _hoisted_55)) : createCommentVNode("v-if", true),
2762
+ }, [createVNode(_sfc_main$2, { name: "key" })], 8, _hoisted_63)) : createCommentVNode("v-if", true),
2396
2763
  createElementVNode("button", {
2397
2764
  class: "fl-unlock-submit",
2398
2765
  type: "submit",
2399
2766
  disabled: node.lock.busy || !node.lock.input
2400
- }, toDisplayString(t("open")), 9, _hoisted_56),
2767
+ }, toDisplayString(t("open")), 9, _hoisted_64),
2401
2768
  createElementVNode("button", {
2402
2769
  class: "fl-unlock-cancel",
2403
2770
  type: "button",
@@ -2407,11 +2774,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2407
2774
  }, [createVNode(_sfc_main$2, {
2408
2775
  name: "close",
2409
2776
  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", {
2777
+ })], 8, _hoisted_65)
2778
+ ], 40, _hoisted_61)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
2779
+ createElementVNode("span", _hoisted_66, toDisplayString(node.is_dir ? "—" : unref(formatSize)(node.size)), 1),
2780
+ createElementVNode("span", _hoisted_67, toDisplayString(unref(formatDate)(node.modified)), 1),
2781
+ createElementVNode("span", _hoisted_68, [node.lock ? (openBlock(), createElementBlock("button", {
2415
2782
  key: 0,
2416
2783
  class: "fl-action fl-arrow-end fl-lock",
2417
2784
  type: "button",
@@ -2420,7 +2787,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2420
2787
  }, [createVNode(_sfc_main$2, {
2421
2788
  name: "lock",
2422
2789
  size: 18
2423
- })], 8, _hoisted_61)) : node.is_dir ? (openBlock(), createElementBlock("button", {
2790
+ })], 8, _hoisted_69)) : node.is_dir ? (openBlock(), createElementBlock("button", {
2424
2791
  key: 1,
2425
2792
  class: normalizeClass(["fl-action fl-arrow-end", { "fl-arrow-open": node.expanded }]),
2426
2793
  type: "button",
@@ -2430,25 +2797,42 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2430
2797
  }, [createVNode(_sfc_main$2, {
2431
2798
  name: "next",
2432
2799
  size: 18
2433
- })], 10, _hoisted_62)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [canPreview(node) ? (openBlock(), createElementBlock("button", {
2800
+ })], 10, _hoisted_70)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [canPreview(node) ? (openBlock(), createElementBlock("button", {
2434
2801
  key: 0,
2435
2802
  class: "fl-action fl-preview-btn",
2436
2803
  type: "button",
2437
2804
  "aria-label": `${t("preview")} ${node.name}`,
2438
2805
  title: t("preview"),
2439
2806
  onClick: ($event) => preview(node)
2440
- }, [createVNode(_sfc_main$2, { name: "eye" })], 8, _hoisted_63)) : createCommentVNode("v-if", true), createElementVNode("button", {
2807
+ }, [createVNode(_sfc_main$2, { name: "eye" })], 8, _hoisted_71)) : createCommentVNode("v-if", true), createElementVNode("button", {
2441
2808
  class: "fl-action fl-download",
2442
2809
  type: "button",
2443
2810
  "aria-label": `${t("download")} ${node.name}`,
2444
2811
  title: t("download"),
2445
2812
  onClick: ($event) => download(node)
2446
- }, [createVNode(_sfc_main$2, { name: "download" })], 8, _hoisted_64)], 64))])
2813
+ }, [createVNode(_sfc_main$2, { name: "download" })], 8, _hoisted_72)], 64))])
2447
2814
  ], 64))
2448
- ], 6);
2815
+ ], 6))], 64);
2449
2816
  }), 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),
2817
+ 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)]),
2818
+ toast.value ? (openBlock(), createElementBlock("div", _hoisted_74, toDisplayString(toast.value), 1)) : createCommentVNode("v-if", true),
2819
+ createVNode(_sfc_main$3, {
2820
+ open: !!zipNotice.value,
2821
+ title: zipNotice.value ? t(zipNotice.value.kind === "warn" ? "zipWarnTitle" : "zipBlockTitle", { size: unref(formatSize)(zipNotice.value.size) }) : "",
2822
+ text: zipNotice.value ? t(zipNotice.value.kind === "warn" ? "zipWarnText" : "zipBlockText", { limit: unref(formatSize)(zipNotice.value.limit) }) : "",
2823
+ tip: zipTip.value,
2824
+ "confirm-label": zipNotice.value?.kind === "warn" ? t("zipContinue") : void 0,
2825
+ "cancel-label": zipNotice.value?.kind === "warn" ? t("cancel") : t("gotIt"),
2826
+ onConfirm: confirmZip,
2827
+ onClose: _cache[4] || (_cache[4] = ($event) => zipNotice.value = null)
2828
+ }, null, 8, [
2829
+ "open",
2830
+ "title",
2831
+ "text",
2832
+ "tip",
2833
+ "confirm-label",
2834
+ "cancel-label"
2835
+ ]),
2452
2836
  createVNode(_sfc_main$1, {
2453
2837
  open: !!previewNode.value,
2454
2838
  file: previewNode.value,
@@ -2461,9 +2845,9 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2461
2845
  position: previewPosition.value,
2462
2846
  total: previewPosition.value ? previewList.value.length : 0,
2463
2847
  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))
2848
+ onPrev: _cache[5] || (_cache[5] = ($event) => stepPreview(-1)),
2849
+ onNext: _cache[6] || (_cache[6] = ($event) => stepPreview(1)),
2850
+ onDownload: _cache[7] || (_cache[7] = ($event) => previewNode.value && download(previewNode.value))
2467
2851
  }, null, 8, [
2468
2852
  "open",
2469
2853
  "file",