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,4 +1,4 @@
1
- import { computed, defineComponent, mergeProps, nextTick, onBeforeUnmount, onMounted, ref, unref, useId, useSSRContext, watch } from "vue";
1
+ import { computed, defineComponent, mergeProps, nextTick, onBeforeUnmount, onMounted, reactive, ref, unref, useId, useSSRContext, watch } from "vue";
2
2
  import { ssrIncludeBooleanAttr, ssrInterpolate, ssrLooseContain, ssrLooseEqual, ssrRenderAttr, ssrRenderAttrs, ssrRenderClass, ssrRenderComponent, ssrRenderList, ssrRenderStyle } from "vue/server-renderer";
3
3
  // src/client/utils.ts
4
4
  function joinPath(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,19 +518,177 @@ 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 _sfc_main$3 = /*@__PURE__*/ defineComponent({
527
+ __name: "ConfirmDialog",
528
+ __ssrInlineRender: true,
529
+ props: {
530
+ open: { type: Boolean },
531
+ title: {},
532
+ text: {},
533
+ tip: {},
534
+ confirmLabel: {},
535
+ cancelLabel: {}
536
+ },
537
+ emits: ["confirm", "close"],
538
+ setup(__props, { emit: __emit }) {
539
+ /** Small native <dialog> for a yes / no question or a notice (bottom sheet on phones). */
540
+ const props = __props;
541
+ const dialog = ref();
542
+ watch(() => props.open, (open) => {
543
+ const el = dialog.value;
544
+ if (!el) return;
545
+ if (open && !el.open) {
546
+ if (typeof el.showModal === "function") el.showModal();
547
+ else el.setAttribute("open", "");
548
+ } else if (!open && el.open) {
549
+ if (typeof el.close === "function") el.close();
550
+ else el.removeAttribute("open");
551
+ }
552
+ }, { flush: "post" });
553
+ return (_ctx, _push, _parent, _attrs) => {
554
+ _push(`<dialog${ssrRenderAttrs(mergeProps({
555
+ ref_key: "dialog",
556
+ ref: dialog,
557
+ class: "fl-confirm",
558
+ "aria-label": __props.title
559
+ }, _attrs))}>`);
560
+ if (__props.open) {
561
+ _push(`<div class="fl-confirm-panel"><p class="fl-confirm-title">${ssrInterpolate(__props.title)}</p><p class="fl-confirm-text">${ssrInterpolate(__props.text)}</p>`);
562
+ if (__props.tip) _push(`<p class="fl-confirm-text">${ssrInterpolate(__props.tip)}</p>`);
563
+ else _push(`<!---->`);
564
+ _push(`<div class="fl-confirm-actions">`);
565
+ if (__props.confirmLabel) _push(`<button class="fl-btn" type="button">${ssrInterpolate(__props.cancelLabel)}</button>`);
566
+ else _push(`<!---->`);
567
+ _push(`<button class="fl-btn fl-btn-primary" type="button" autofocus>${ssrInterpolate(__props.confirmLabel || __props.cancelLabel)}</button></div></div>`);
568
+ } else _push(`<!---->`);
569
+ _push(`</dialog>`);
570
+ };
571
+ }
572
+ });
573
+ const _sfc_setup$3 = _sfc_main$3.setup;
574
+ _sfc_main$3.setup = (props, ctx) => {
575
+ const ssrContext = useSSRContext();
576
+ (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/client/ConfirmDialog.vue");
577
+ return _sfc_setup$3 ? _sfc_setup$3(props, ctx) : void 0;
578
+ };
523
579
  // src/client/download.ts
524
- /** Open a file URL; OpenList serves `/p/` and `/d/` as attachments, so this downloads. */
525
- function openUrl(url) {
580
+ /**
581
+ * Hand a file URL to the browser's own download manager.
582
+ *
583
+ * `sameTab` is for links known to answer as attachments (OpenList `/p/`, `/d/`):
584
+ * the browser downloads and the page stays put, with no tab flashing open.
585
+ * Other links (a storage provider's direct URL may display inline) open in a
586
+ * new tab so the document page is never replaced. `download` also keeps a
587
+ * same-origin link out of the VitePress router and names the file.
588
+ */
589
+ function openUrl(url, options) {
526
590
  const anchor = document.createElement("a");
527
591
  anchor.href = url;
528
- anchor.target = "_blank";
592
+ anchor.download = options.name;
529
593
  anchor.rel = "noopener";
594
+ if (!options.sameTab) anchor.target = "_blank";
595
+ document.body.appendChild(anchor);
596
+ anchor.click();
597
+ anchor.remove();
598
+ }
599
+ function saveBlob(blob, name) {
600
+ const href = URL.createObjectURL(blob);
601
+ const anchor = document.createElement("a");
602
+ anchor.href = href;
603
+ anchor.download = name;
530
604
  document.body.appendChild(anchor);
531
605
  anchor.click();
532
606
  anchor.remove();
607
+ setTimeout(() => URL.revokeObjectURL(href), 3e4);
608
+ }
609
+ /** File-system-safe ZIP name from a title. */
610
+ function zipFileName(title) {
611
+ return `${title.replace(/[\\/:*?"<>|]/g, "_")}.zip`;
612
+ }
613
+ /**
614
+ * The ZIP as a stream: files are fetched a few at a time and passed through
615
+ * as they arrive, stored without compression (most shared files are already
616
+ * compressed). Clashing entry names get ` (2)`, ` (3)`...
617
+ */
618
+ /** Files fetched at once: hides per-file latency, stays under the browser's 6 per host. */
619
+ const CONCURRENCY = 4;
620
+ /** Progress updates at most this often (ms), so the bar does not re-render per chunk. */
621
+ const PROGRESS_INTERVAL = 100;
622
+ async function zipStream(files, options) {
623
+ const { makeZip } = await import("client-zip");
624
+ const progress = {
625
+ bytes: 0,
626
+ totalBytes: files.reduce((sum, file) => sum + (Number.isFinite(file.size) ? file.size : 0), 0),
627
+ files: 0,
628
+ totalFiles: files.length
629
+ };
630
+ let reportedAt = 0;
631
+ const report = (force = false) => {
632
+ const now = Date.now();
633
+ if (!force && now - reportedAt < PROGRESS_INTERVAL) return;
634
+ reportedAt = now;
635
+ options.onProgress({ ...progress });
636
+ };
637
+ report(true);
638
+ async function* entries() {
639
+ const used = /* @__PURE__ */ new Set();
640
+ const ahead = [];
641
+ let started = 0;
642
+ const startNext = () => {
643
+ const file = files[started++];
644
+ const response = options.urlOf(file).then((url) => fetch(url, { signal: options.signal }));
645
+ response.catch(() => {});
646
+ ahead.push(response);
647
+ };
648
+ while (started < files.length && ahead.length < CONCURRENCY) startNext();
649
+ for (const file of files) {
650
+ const response = await ahead.shift();
651
+ if (started < files.length) startNext();
652
+ if (!response.ok || !response.body) throw new Error(`${file.name}:HTTP ${response.status}`);
653
+ const counted = response.body.pipeThrough(new TransformStream({
654
+ transform(chunk, controller) {
655
+ progress.bytes += chunk.byteLength;
656
+ report();
657
+ controller.enqueue(chunk);
658
+ },
659
+ flush() {
660
+ progress.files += 1;
661
+ report(progress.files === progress.totalFiles);
662
+ }
663
+ }));
664
+ const modified = new Date(file.modified);
665
+ yield {
666
+ name: uniqueEntryName(zipEntryName(file.zipPath), used),
667
+ lastModified: Number.isNaN(modified.getTime()) ? void 0 : modified,
668
+ input: counted
669
+ };
670
+ }
671
+ }
672
+ return makeZip(entries());
673
+ }
674
+ /** Build the ZIP in memory (browsers without file streaming). */
675
+ async function zipToBlob(files, options) {
676
+ const reader = (await zipStream(files, options)).getReader();
677
+ const chunks = [];
678
+ const abort = () => void reader.cancel(options.signal.reason).catch(() => {});
679
+ options.signal.addEventListener("abort", abort, { once: true });
680
+ if (options.signal.aborted) abort();
681
+ try {
682
+ for (;;) {
683
+ const { done, value } = await reader.read();
684
+ if (done) break;
685
+ chunks.push(value);
686
+ }
687
+ } finally {
688
+ options.signal.removeEventListener("abort", abort);
689
+ }
690
+ options.signal.throwIfAborted();
691
+ return new Blob(chunks, { type: "application/zip" });
533
692
  }
534
693
  // src/client/icons.ts
535
694
  /** Stroke icons on a 24×24 grid, drawn by `Icon.vue`. */
@@ -692,7 +851,22 @@ const fileListLocales = {
692
851
  remembered: "已记住",
693
852
  forgetPassword: "忘记",
694
853
  unlockFolder: "输入 {name} 的密码",
695
- networkHint: "可能被跨域拦截:请检查 OpenList 的 cors.allow_origins,或 OpenList-Worker 的 ALLOW_URLS。"
854
+ networkHint: "可能被跨域拦截:请检查 OpenList 的 cors.allow_origins,或 OpenList-Worker 的 ALLOW_URLS。",
855
+ downloadStarted: "已交给浏览器下载:{name}",
856
+ batchProgress: "{done} / {total} · {files} / {count} 个文件",
857
+ batchDone: "已打包 {name}({count} 个文件,{size})",
858
+ batchCanceled: "已取消下载",
859
+ zipWarnTitle: "文件较大({size})",
860
+ zipWarnText: "当前浏览器要先把文件全部放进内存再打包,超过 {limit} 可能失败或让页面崩溃。建议分批下载。",
861
+ zipStreamTip: "在 Chrome 或 Edge 中可以边下载边保存,不受大小限制。",
862
+ zipContinue: "仍然下载",
863
+ zipBlockTitle: "无法打包 {size}",
864
+ zipBlockText: "当前浏览器一次最多打包 {limit},请分批勾选下载。",
865
+ gotIt: "知道了",
866
+ moreRemaining: "还有 {count} 项",
867
+ showMore: "显示更多",
868
+ showAll: "全部显示",
869
+ showRest: "显示剩余 {count} 项"
696
870
  },
697
871
  "en-US": {
698
872
  allFiles: "All files",
@@ -755,7 +929,22 @@ const fileListLocales = {
755
929
  remembered: "Remembered",
756
930
  forgetPassword: "Forget",
757
931
  unlockFolder: "Enter the password for {name}",
758
- networkHint: "Possibly blocked by CORS: check cors.allow_origins in OpenList, or ALLOW_URLS in OpenList-Worker."
932
+ networkHint: "Possibly blocked by CORS: check cors.allow_origins in OpenList, or ALLOW_URLS in OpenList-Worker.",
933
+ downloadStarted: "Downloading {name} in your browser",
934
+ batchProgress: "{done} / {total} · {files} / {count} files",
935
+ batchDone: "Saved {name} ({count} files, {size})",
936
+ batchCanceled: "Download cancelled",
937
+ zipWarnTitle: "Large download ({size})",
938
+ 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.",
939
+ zipStreamTip: "Chrome and Edge save while downloading, with no size limit.",
940
+ zipContinue: "Download anyway",
941
+ zipBlockTitle: "Cannot zip {size}",
942
+ zipBlockText: "This browser can zip at most {limit} at once. Select fewer files.",
943
+ gotIt: "OK",
944
+ moreRemaining: "{count} more",
945
+ showMore: "Show more",
946
+ showAll: "Show all",
947
+ showRest: "Show the remaining {count}"
759
948
  }
760
949
  };
761
950
  function formatLabel(template, values = {}) {
@@ -1242,6 +1431,9 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1242
1431
  type: Boolean,
1243
1432
  default: true
1244
1433
  },
1434
+ pageSize: { default: 200 },
1435
+ zipWarnSize: {},
1436
+ zipMaxSize: {},
1245
1437
  locale: { default: "zh-CN" },
1246
1438
  labels: {}
1247
1439
  },
@@ -1284,7 +1476,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1284
1476
  const siteTitle = ref("");
1285
1477
  const iframePreviews = ref("");
1286
1478
  const batchBusy = ref(false);
1287
- const batchProgress = ref(0);
1479
+ const zipProgress = ref(null);
1480
+ let zipAbort = null;
1288
1481
  let toastTimer;
1289
1482
  /** The folder listed in single-path mode, as the parent of the top rows. */
1290
1483
  const listedFolder = computed(() => ({
@@ -1444,6 +1637,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1444
1637
  errorMsg.value = "";
1445
1638
  try {
1446
1639
  roots.value = await buildTree();
1640
+ shown.clear();
1447
1641
  rootLock.value = null;
1448
1642
  selected.value = /* @__PURE__ */ new Set();
1449
1643
  api.value.clearCache();
@@ -1482,20 +1676,37 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1482
1676
  applySort(key, direction !== "desc");
1483
1677
  }
1484
1678
  });
1485
- const visible = computed(() => {
1679
+ /** A “show more” row closing a folder whose children are cut at `pageSize`. */
1680
+ const shown = reactive(/* @__PURE__ */ new Map());
1681
+ function limitOf(key) {
1682
+ const size = Math.max(0, Math.floor(props.pageSize ?? 200));
1683
+ return size ? shown.get(key) ?? size : Number.POSITIVE_INFINITY;
1684
+ }
1685
+ function isMore(row) {
1686
+ return "more" in row;
1687
+ }
1688
+ const rows = computed(() => {
1486
1689
  const out = [];
1487
1690
  const query = keyword.value.trim().toLowerCase();
1488
1691
  const matches = (node) => !query || node.name.toLowerCase().includes(query) || node.is_dir && node.children.some(matches);
1489
- const walk = (nodes) => {
1490
- for (const node of nodes) {
1491
- if (!matches(node)) continue;
1692
+ const walk = (nodes, key, depth) => {
1693
+ const list = nodes.filter(matches);
1694
+ const limit = query ? list.length : Math.min(list.length, limitOf(key));
1695
+ for (const node of list.slice(0, limit)) {
1492
1696
  out.push(node);
1493
- if (node.is_dir && (node.expanded || query) && node.children.length) walk(node.children);
1697
+ if (node.is_dir && (node.expanded || query) && node.children.length) walk(node.children, node.path, node.depth + 1);
1494
1698
  }
1699
+ if (limit < list.length) out.push({
1700
+ more: true,
1701
+ key,
1702
+ depth,
1703
+ remaining: list.length - limit
1704
+ });
1495
1705
  };
1496
- walk(roots.value);
1706
+ walk(roots.value, props.sources.length ? "" : props.path, 0);
1497
1707
  return out;
1498
1708
  });
1709
+ const visible = computed(() => rows.value.filter((row) => !isMore(row)));
1499
1710
  const stats = computed(() => folderStats(roots.value, selected.value));
1500
1711
  const noStats = {
1501
1712
  files: 0,
@@ -1529,11 +1740,62 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1529
1740
  return;
1530
1741
  }
1531
1742
  try {
1532
- openUrl(await fileUrlOf(node));
1743
+ const url = await fileUrlOf(node);
1744
+ openUrl(url, {
1745
+ sameTab: api.value.isAttachmentUrl(url),
1746
+ name: node.name
1747
+ });
1748
+ showToast(t("downloadStarted", { name: node.name }));
1533
1749
  } catch (error) {
1534
1750
  showToast(t("downloadFailed", { message: errorText(error) }));
1535
1751
  }
1536
1752
  }
1753
+ const zipNotice = ref(null);
1754
+ /** Only desktop browsers get “use Chrome or Edge”; on iPhone every browser is Safari. */
1755
+ const zipTip = computed(() => typeof window !== "undefined" && !window.matchMedia?.("(pointer: coarse)").matches ? t("zipStreamTip") : "");
1756
+ function zipName() {
1757
+ return zipFileName(props.title || t("defaultZip"));
1758
+ }
1759
+ async function zipInMemory(files) {
1760
+ const name = zipName();
1761
+ await runZip(files, async (options) => saveBlob(await zipToBlob(files, options), name), name);
1762
+ }
1763
+ function confirmZip() {
1764
+ const notice = zipNotice.value;
1765
+ zipNotice.value = null;
1766
+ if (notice?.kind === "warn") zipInMemory(notice.files);
1767
+ }
1768
+ async function runZip(files, run, name) {
1769
+ const controller = new AbortController();
1770
+ zipAbort = controller;
1771
+ batchBusy.value = true;
1772
+ zipProgress.value = null;
1773
+ try {
1774
+ await run({
1775
+ urlOf: fileUrlOf,
1776
+ signal: controller.signal,
1777
+ onProgress: (progress) => zipProgress.value = progress
1778
+ });
1779
+ showToast(t("batchDone", {
1780
+ name,
1781
+ count: files.length,
1782
+ size: formatSize(totalSize(files))
1783
+ }));
1784
+ selected.value = /* @__PURE__ */ new Set();
1785
+ } catch (error) {
1786
+ showToast(controller.signal.aborted ? t("batchCanceled") : t("batchFailed", { message: errorText(error) }));
1787
+ } finally {
1788
+ zipAbort = null;
1789
+ batchBusy.value = false;
1790
+ zipProgress.value = null;
1791
+ }
1792
+ }
1793
+ const zipPercent = computed(() => {
1794
+ const progress = zipProgress.value;
1795
+ if (!progress) return 0;
1796
+ const ratio = progress.totalBytes ? progress.bytes / progress.totalBytes : progress.totalFiles ? progress.files / progress.totalFiles : 0;
1797
+ return Math.min(100, Math.floor(ratio * 100));
1798
+ });
1537
1799
  /** Only types the dialog can actually render get a preview button. */
1538
1800
  function canPreview(node) {
1539
1801
  return !node.is_dir && previewKind(node.name) !== "none";
@@ -1605,6 +1867,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1605
1867
  window.addEventListener("resize", measureStickyTop, { passive: true });
1606
1868
  });
1607
1869
  onBeforeUnmount(() => {
1870
+ zipAbort?.abort();
1608
1871
  window.removeEventListener("resize", measureStickyTop);
1609
1872
  clearTimeout(toastTimer);
1610
1873
  });
@@ -1619,14 +1882,24 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1619
1882
  style: { "--fl-sticky-top-auto": `${stickyTop.value}px` },
1620
1883
  "aria-busy": refreshing.value || loadingRoot.value
1621
1884
  }, _attrs))}><div class="fl-bar">`);
1622
- if (selected.value.size) _push(`<div class="fl-selection" role="status"><span class="fl-selection-count">${ssrInterpolate(t("selected", { count: selected.value.size }))}</span><span class="fl-selection-size">${ssrInterpolate(t("totalSize", { size: unref(formatSize)(unref(totalSize)(selectedFiles.value)) }))}</span></div>`);
1885
+ if (batchBusy.value) {
1886
+ _push(`<div class="fl-selection" role="status"><span class="fl-selection-count">${ssrInterpolate(t("batching", { percent: zipPercent.value }))}</span>`);
1887
+ if (zipProgress.value) _push(`<span class="fl-selection-size">${ssrInterpolate(t("batchProgress", {
1888
+ done: unref(formatSize)(zipProgress.value.bytes),
1889
+ total: unref(formatSize)(zipProgress.value.totalBytes),
1890
+ files: zipProgress.value.files,
1891
+ count: zipProgress.value.totalFiles
1892
+ }))}</span>`);
1893
+ else _push(`<!---->`);
1894
+ _push(`<span class="fl-zip-bar" style="${ssrRenderStyle({ width: `${zipPercent.value}%` })}" aria-hidden="true"></span></div>`);
1895
+ } else if (selected.value.size) _push(`<div class="fl-selection" role="status"><span class="fl-selection-count">${ssrInterpolate(t("selected", { count: selected.value.size }))}</span><span class="fl-selection-size">${ssrInterpolate(t("totalSize", { size: unref(formatSize)(unref(totalSize)(selectedFiles.value)) }))}</span></div>`);
1623
1896
  else {
1624
1897
  _push(`<div class="fl-title"><span class="fl-title-text">${ssrInterpolate(displayTitle.value)}</span>`);
1625
1898
  if (demo.value) _push(`<span class="fl-demo-badge">${ssrInterpolate(t("demoBadge"))}</span>`);
1626
1899
  else _push(`<!---->`);
1627
1900
  _push(`</div>`);
1628
1901
  }
1629
- if (selected.value.size) {
1902
+ if (selected.value.size && !batchBusy.value) {
1630
1903
  _push(`<button class="fl-clear" type="button"${ssrRenderAttr("aria-label", t("clearSelection"))}${ssrRenderAttr("title", t("clearSelection"))}>`);
1631
1904
  _push(ssrRenderComponent(_sfc_main$2, {
1632
1905
  class: "fl-clear-icon",
@@ -1646,18 +1919,19 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1646
1919
  size: 15
1647
1920
  }, null, _parent));
1648
1921
  _push(`</button></div>`);
1649
- if (selected.value.size) {
1650
- _push(`<button class="fl-btn fl-btn-primary" type="button"${ssrIncludeBooleanAttr(batchBusy.value) ? " disabled" : ""}>`);
1922
+ if (batchBusy.value) _push(`<button class="fl-btn fl-zip-cancel" type="button">${ssrInterpolate(t("cancel"))}</button>`);
1923
+ else if (selected.value.size) {
1924
+ _push(`<button class="fl-btn fl-btn-primary" type="button">`);
1651
1925
  _push(ssrRenderComponent(_sfc_main$2, {
1652
1926
  name: "download",
1653
1927
  size: 15
1654
1928
  }, null, _parent));
1655
- _push(` ${ssrInterpolate(batchBusy.value ? t("batching", { percent: batchProgress.value }) : t("batch"))}</button>`);
1929
+ _push(` ${ssrInterpolate(t("batch"))}</button>`);
1656
1930
  } else _push(`<!---->`);
1657
1931
  _push(`</div><div class="fl-row fl-head">`);
1658
1932
  if (refreshing.value) _push(`<span class="fl-progress" aria-hidden="true"></span>`);
1659
1933
  else _push(`<!---->`);
1660
- _push(`<label class="fl-cell fl-cell-check"><input type="checkbox"${ssrRenderAttr("aria-label", t("selectAll"))}${ssrIncludeBooleanAttr(allChecked.value) ? " checked" : ""}${ssrRenderAttr("indeterminate", someChecked.value)}></label><!--[-->`);
1934
+ _push(`<label class="fl-cell fl-cell-check"><input type="checkbox"${ssrRenderAttr("aria-label", t("selectAll"))}${ssrIncludeBooleanAttr(batchBusy.value) ? " disabled" : ""}${ssrIncludeBooleanAttr(allChecked.value) ? " checked" : ""}${ssrRenderAttr("indeterminate", someChecked.value)}></label><!--[-->`);
1661
1935
  ssrRenderList([
1662
1936
  "name",
1663
1937
  "size",
@@ -1697,105 +1971,124 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1697
1971
  } else if (visible.value.length === 0) _push(`<div class="fl-status">${ssrInterpolate(t("empty"))}</div>`);
1698
1972
  else _push(`<!---->`);
1699
1973
  _push(`<!--[-->`);
1700
- ssrRenderList(visible.value, (node, index) => {
1701
- _push(`<div class="${ssrRenderClass([{
1702
- "fl-row-dir": node.is_dir,
1703
- "fl-row-root": node.isRoot,
1704
- "fl-row-selected": !node.is_dir && selected.value.has(node.path),
1705
- "fl-row-locked": !!node.lock,
1706
- "fl-row-prompting": node.lock?.prompting,
1707
- "fl-row-error": node.lock?.prompting && !!node.lock.error
1708
- }, "fl-row"])}" style="${ssrRenderStyle({ "--fl-depth": node.depth })}"><label class="fl-cell fl-cell-check"><input type="checkbox"${ssrIncludeBooleanAttr(!!node.lock) ? " disabled" : ""}${ssrRenderAttr("aria-label", t("selectItem", { name: node.name }))}${ssrIncludeBooleanAttr(isChecked(node)) ? " checked" : ""}${ssrRenderAttr("indeterminate", isIndeterminate(node))}></label><span class="fl-cell fl-cell-name"${ssrRenderAttr("title", node.isRoot ? node.path : void 0)}><span class="fl-indent" aria-hidden="true"></span>`);
1709
- if (node.lock) {
1710
- _push(`<button class="fl-arrow fl-lock" type="button"${ssrRenderAttr("aria-label", t("unlockFolder", { name: node.name }))}${ssrRenderAttr("title", t("unlockFolder", { name: node.name }))}>`);
1711
- _push(ssrRenderComponent(_sfc_main$2, {
1712
- name: "lock",
1713
- size: 14
1714
- }, null, _parent));
1715
- _push(`</button>`);
1716
- } else if (node.is_dir) {
1717
- _push(`<button type="button" class="${ssrRenderClass([{ "fl-arrow-open": node.expanded }, "fl-arrow"])}"${ssrRenderAttr("aria-label", t(node.expanded ? "collapse" : "expand", { name: node.name }))}${ssrRenderAttr("aria-expanded", node.expanded)}>`);
1718
- _push(ssrRenderComponent(_sfc_main$2, {
1719
- name: "next",
1720
- size: 14
1721
- }, null, _parent));
1722
- _push(`</button>`);
1723
- } else _push(`<span class="fl-arrow-placeholder" aria-hidden="true"></span>`);
1724
- if (node.is_dir) _push(`<svg class="fl-folder-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><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"></path></svg>`);
1725
- else _push(`<span class="fl-type"${ssrRenderAttr("data-kind", unref(fileKind)(node.name))} aria-hidden="true">${ssrInterpolate(unref(extOf)(node.name) || "FILE")}</span>`);
1726
- _push(`<span class="fl-label"><span class="${ssrRenderClass([{ "fl-name-dir": node.is_dir }, "fl-name"])}">${ssrInterpolate(node.name)}</span><span class="fl-meta">`);
1727
- if (!node.is_dir) _push(`<!--[-->${ssrInterpolate(unref(formatSize)(node.size))} · ${ssrInterpolate(unref(formatDate)(node.modified))}<!--]-->`);
1728
- else if (node.loaded) _push(`<!--[-->${ssrInterpolate(t("folderItems", { count: statsOf(node).files }))} · ${ssrInterpolate(unref(formatSize)(statsOf(node).bytes))}<!--]-->`);
1729
- else _push(`<!---->`);
1730
- _push(`</span></span>`);
1731
- if (node.is_dir && node.loaded) _push(`<span class="fl-count">${ssrInterpolate(t("folderItems", { count: statsOf(node).files }))}</span>`);
1732
- else _push(`<!---->`);
1733
- if (node.lock?.prompting && node.lock.error) _push(`<span${ssrRenderAttr("id", `${unref(uid)}-error-${index}`)} class="fl-unlock-message" role="alert">${ssrInterpolate(unlockErrorText(node.lock.error))}</span>`);
1734
- else _push(`<!---->`);
1735
- if (!node.lock && unref(remembered).has(node.path)) {
1736
- _push(`<button class="fl-remembered" type="button"${ssrRenderAttr("aria-label", `${t("forgetPassword")} ${node.name}`)}${ssrRenderAttr("title", t("forgetPassword"))}>`);
1737
- _push(ssrRenderComponent(_sfc_main$2, {
1738
- name: "key",
1739
- size: 11
1740
- }, null, _parent));
1741
- _push(` ${ssrInterpolate(t("remembered"))} · ${ssrInterpolate(t("forgetPassword"))}</button>`);
1742
- } else _push(`<!---->`);
1743
- if (node.loading) _push(`<span class="fl-loading" aria-hidden="true">…</span>`);
1744
- else _push(`<!---->`);
1745
- _push(`</span>`);
1746
- if (node.lock?.prompting) {
1747
- _push(`<form class="fl-unlock fl-unlock-row">`);
1748
- _push(ssrRenderComponent(_sfc_main$2, {
1749
- class: "fl-unlock-lock",
1750
- name: "lock",
1751
- size: 18
1752
- }, null, _parent));
1753
- _push(`<input${ssrRenderAttr("value", node.lock.input)} type="password" autocomplete="off"${ssrRenderAttr("data-unlock", node.path)}${ssrRenderAttr("placeholder", node.lock.error ? unlockErrorText(node.lock.error) : t("passwordPlaceholder"))}${ssrRenderAttr("aria-label", t("passwordFor", { name: node.name }))}${ssrRenderAttr("aria-invalid", !!node.lock.error)}${ssrRenderAttr("aria-describedby", node.lock.error ? `${unref(uid)}-error-${index}` : void 0)}>`);
1754
- if (canRemember.value) {
1755
- _push(`<button class="fl-unlock-remember" type="button"${ssrRenderAttr("aria-pressed", node.lock.remember)}${ssrRenderAttr("aria-label", t("rememberPassword"))}${ssrRenderAttr("title", t("rememberPassword"))}>`);
1756
- _push(ssrRenderComponent(_sfc_main$2, { name: "key" }, null, _parent));
1757
- _push(`</button>`);
1758
- } else _push(`<!---->`);
1759
- _push(`<button class="fl-unlock-submit" type="submit"${ssrIncludeBooleanAttr(node.lock.busy || !node.lock.input) ? " disabled" : ""}>${ssrInterpolate(t("open"))}</button><button class="fl-unlock-cancel" type="button"${ssrRenderAttr("aria-label", t("cancel"))}${ssrRenderAttr("title", t("cancel"))}>`);
1760
- _push(ssrRenderComponent(_sfc_main$2, {
1761
- name: "close",
1762
- size: 14
1763
- }, null, _parent));
1764
- _push(`</button></form>`);
1974
+ ssrRenderList(rows.value, (node, index) => {
1975
+ _push(`<!--[-->`);
1976
+ if (isMore(node)) {
1977
+ _push(`<div class="fl-row fl-more" style="${ssrRenderStyle({ "--fl-depth": node.depth })}"><span class="fl-cell fl-cell-check"></span><span class="fl-cell fl-more-cell"><span class="fl-indent" aria-hidden="true"></span><span class="fl-more-count">${ssrInterpolate(t("moreRemaining", { count: node.remaining }))}</span>`);
1978
+ if (node.remaining > (__props.pageSize || 0)) _push(`<!--[--><button class="fl-more-btn" type="button">${ssrInterpolate(t("showMore"))}<span class="fl-more-page">${ssrInterpolate(` ${t("folderItems", { count: __props.pageSize })}`)}</span></button><button class="fl-more-btn fl-more-all" type="button">${ssrInterpolate(t("showAll"))}</button><!--]-->`);
1979
+ else _push(`<button class="fl-more-btn" type="button">${ssrInterpolate(t("showRest", { count: node.remaining }))}</button>`);
1980
+ _push(`</span></div>`);
1765
1981
  } else {
1766
- _push(`<!--[--><span class="fl-cell fl-cell-size">${ssrInterpolate(node.is_dir ? "—" : unref(formatSize)(node.size))}</span><span class="fl-cell fl-cell-date">${ssrInterpolate(unref(formatDate)(node.modified))}</span><span class="fl-cell fl-cell-op">`);
1982
+ _push(`<div class="${ssrRenderClass([{
1983
+ "fl-row-dir": node.is_dir,
1984
+ "fl-row-root": node.isRoot,
1985
+ "fl-row-selected": !node.is_dir && selected.value.has(node.path),
1986
+ "fl-row-locked": !!node.lock,
1987
+ "fl-row-prompting": node.lock?.prompting,
1988
+ "fl-row-error": node.lock?.prompting && !!node.lock.error
1989
+ }, "fl-row"])}" style="${ssrRenderStyle({ "--fl-depth": node.depth })}"><label class="fl-cell fl-cell-check"><input type="checkbox"${ssrIncludeBooleanAttr(!!node.lock || batchBusy.value) ? " disabled" : ""}${ssrRenderAttr("aria-label", t("selectItem", { name: node.name }))}${ssrIncludeBooleanAttr(isChecked(node)) ? " checked" : ""}${ssrRenderAttr("indeterminate", isIndeterminate(node))}></label><span class="fl-cell fl-cell-name"${ssrRenderAttr("title", node.isRoot ? node.path : void 0)}><span class="fl-indent" aria-hidden="true"></span>`);
1767
1990
  if (node.lock) {
1768
- _push(`<button class="fl-action fl-arrow-end fl-lock" type="button"${ssrRenderAttr("aria-label", t("unlockFolder", { name: node.name }))}>`);
1991
+ _push(`<button class="fl-arrow fl-lock" type="button"${ssrRenderAttr("aria-label", t("unlockFolder", { name: node.name }))}${ssrRenderAttr("title", t("unlockFolder", { name: node.name }))}>`);
1769
1992
  _push(ssrRenderComponent(_sfc_main$2, {
1770
1993
  name: "lock",
1771
- size: 18
1994
+ size: 14
1772
1995
  }, null, _parent));
1773
1996
  _push(`</button>`);
1774
1997
  } else if (node.is_dir) {
1775
- _push(`<button type="button" class="${ssrRenderClass([{ "fl-arrow-open": node.expanded }, "fl-action fl-arrow-end"])}"${ssrRenderAttr("aria-label", t(node.expanded ? "collapse" : "expand", { name: node.name }))}${ssrRenderAttr("aria-expanded", node.expanded)}>`);
1998
+ _push(`<button type="button" class="${ssrRenderClass([{ "fl-arrow-open": node.expanded }, "fl-arrow"])}"${ssrRenderAttr("aria-label", t(node.expanded ? "collapse" : "expand", { name: node.name }))}${ssrRenderAttr("aria-expanded", node.expanded)}>`);
1776
1999
  _push(ssrRenderComponent(_sfc_main$2, {
1777
2000
  name: "next",
1778
- size: 18
2001
+ size: 14
1779
2002
  }, null, _parent));
1780
2003
  _push(`</button>`);
1781
- } else {
1782
- _push(`<!--[-->`);
1783
- if (canPreview(node)) {
1784
- _push(`<button class="fl-action fl-preview-btn" type="button"${ssrRenderAttr("aria-label", `${t("preview")} ${node.name}`)}${ssrRenderAttr("title", t("preview"))}>`);
1785
- _push(ssrRenderComponent(_sfc_main$2, { name: "eye" }, null, _parent));
2004
+ } else _push(`<span class="fl-arrow-placeholder" aria-hidden="true"></span>`);
2005
+ if (node.is_dir) _push(`<svg class="fl-folder-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><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"></path></svg>`);
2006
+ else _push(`<span class="fl-type"${ssrRenderAttr("data-kind", unref(fileKind)(node.name))} aria-hidden="true">${ssrInterpolate(unref(extOf)(node.name) || "FILE")}</span>`);
2007
+ _push(`<span class="fl-label"><span class="${ssrRenderClass([{ "fl-name-dir": node.is_dir }, "fl-name"])}">${ssrInterpolate(node.name)}</span><span class="fl-meta">`);
2008
+ if (!node.is_dir) _push(`<!--[-->${ssrInterpolate(unref(formatSize)(node.size))} · ${ssrInterpolate(unref(formatDate)(node.modified))}<!--]-->`);
2009
+ else if (node.loaded) _push(`<!--[-->${ssrInterpolate(t("folderItems", { count: statsOf(node).files }))} · ${ssrInterpolate(unref(formatSize)(statsOf(node).bytes))}<!--]-->`);
2010
+ else _push(`<!---->`);
2011
+ _push(`</span></span>`);
2012
+ if (node.is_dir && node.loaded) _push(`<span class="fl-count">${ssrInterpolate(t("folderItems", { count: statsOf(node).files }))}</span>`);
2013
+ else _push(`<!---->`);
2014
+ if (node.lock?.prompting && node.lock.error) _push(`<span${ssrRenderAttr("id", `${unref(uid)}-error-${index}`)} class="fl-unlock-message" role="alert">${ssrInterpolate(unlockErrorText(node.lock.error))}</span>`);
2015
+ else _push(`<!---->`);
2016
+ if (!node.lock && unref(remembered).has(node.path)) {
2017
+ _push(`<button class="fl-remembered" type="button"${ssrRenderAttr("aria-label", `${t("forgetPassword")} ${node.name}`)}${ssrRenderAttr("title", t("forgetPassword"))}>`);
2018
+ _push(ssrRenderComponent(_sfc_main$2, {
2019
+ name: "key",
2020
+ size: 11
2021
+ }, null, _parent));
2022
+ _push(` ${ssrInterpolate(t("remembered"))} · ${ssrInterpolate(t("forgetPassword"))}</button>`);
2023
+ } else _push(`<!---->`);
2024
+ if (node.loading) _push(`<span class="fl-loading" aria-hidden="true">…</span>`);
2025
+ else _push(`<!---->`);
2026
+ _push(`</span>`);
2027
+ if (node.lock?.prompting) {
2028
+ _push(`<form class="fl-unlock fl-unlock-row">`);
2029
+ _push(ssrRenderComponent(_sfc_main$2, {
2030
+ class: "fl-unlock-lock",
2031
+ name: "lock",
2032
+ size: 18
2033
+ }, null, _parent));
2034
+ _push(`<input${ssrRenderAttr("value", node.lock.input)} type="password" autocomplete="off"${ssrRenderAttr("data-unlock", node.path)}${ssrRenderAttr("placeholder", node.lock.error ? unlockErrorText(node.lock.error) : t("passwordPlaceholder"))}${ssrRenderAttr("aria-label", t("passwordFor", { name: node.name }))}${ssrRenderAttr("aria-invalid", !!node.lock.error)}${ssrRenderAttr("aria-describedby", node.lock.error ? `${unref(uid)}-error-${index}` : void 0)}>`);
2035
+ if (canRemember.value) {
2036
+ _push(`<button class="fl-unlock-remember" type="button"${ssrRenderAttr("aria-pressed", node.lock.remember)}${ssrRenderAttr("aria-label", t("rememberPassword"))}${ssrRenderAttr("title", t("rememberPassword"))}>`);
2037
+ _push(ssrRenderComponent(_sfc_main$2, { name: "key" }, null, _parent));
1786
2038
  _push(`</button>`);
1787
2039
  } else _push(`<!---->`);
1788
- _push(`<button class="fl-action fl-download" type="button"${ssrRenderAttr("aria-label", `${t("download")} ${node.name}`)}${ssrRenderAttr("title", t("download"))}>`);
1789
- _push(ssrRenderComponent(_sfc_main$2, { name: "download" }, null, _parent));
1790
- _push(`</button><!--]-->`);
2040
+ _push(`<button class="fl-unlock-submit" type="submit"${ssrIncludeBooleanAttr(node.lock.busy || !node.lock.input) ? " disabled" : ""}>${ssrInterpolate(t("open"))}</button><button class="fl-unlock-cancel" type="button"${ssrRenderAttr("aria-label", t("cancel"))}${ssrRenderAttr("title", t("cancel"))}>`);
2041
+ _push(ssrRenderComponent(_sfc_main$2, {
2042
+ name: "close",
2043
+ size: 14
2044
+ }, null, _parent));
2045
+ _push(`</button></form>`);
2046
+ } else {
2047
+ _push(`<!--[--><span class="fl-cell fl-cell-size">${ssrInterpolate(node.is_dir ? "—" : unref(formatSize)(node.size))}</span><span class="fl-cell fl-cell-date">${ssrInterpolate(unref(formatDate)(node.modified))}</span><span class="fl-cell fl-cell-op">`);
2048
+ if (node.lock) {
2049
+ _push(`<button class="fl-action fl-arrow-end fl-lock" type="button"${ssrRenderAttr("aria-label", t("unlockFolder", { name: node.name }))}>`);
2050
+ _push(ssrRenderComponent(_sfc_main$2, {
2051
+ name: "lock",
2052
+ size: 18
2053
+ }, null, _parent));
2054
+ _push(`</button>`);
2055
+ } else if (node.is_dir) {
2056
+ _push(`<button type="button" class="${ssrRenderClass([{ "fl-arrow-open": node.expanded }, "fl-action fl-arrow-end"])}"${ssrRenderAttr("aria-label", t(node.expanded ? "collapse" : "expand", { name: node.name }))}${ssrRenderAttr("aria-expanded", node.expanded)}>`);
2057
+ _push(ssrRenderComponent(_sfc_main$2, {
2058
+ name: "next",
2059
+ size: 18
2060
+ }, null, _parent));
2061
+ _push(`</button>`);
2062
+ } else {
2063
+ _push(`<!--[-->`);
2064
+ if (canPreview(node)) {
2065
+ _push(`<button class="fl-action fl-preview-btn" type="button"${ssrRenderAttr("aria-label", `${t("preview")} ${node.name}`)}${ssrRenderAttr("title", t("preview"))}>`);
2066
+ _push(ssrRenderComponent(_sfc_main$2, { name: "eye" }, null, _parent));
2067
+ _push(`</button>`);
2068
+ } else _push(`<!---->`);
2069
+ _push(`<button class="fl-action fl-download" type="button"${ssrRenderAttr("aria-label", `${t("download")} ${node.name}`)}${ssrRenderAttr("title", t("download"))}>`);
2070
+ _push(ssrRenderComponent(_sfc_main$2, { name: "download" }, null, _parent));
2071
+ _push(`</button><!--]-->`);
2072
+ }
2073
+ _push(`</span><!--]-->`);
1791
2074
  }
1792
- _push(`</span><!--]-->`);
2075
+ _push(`</div>`);
1793
2076
  }
1794
- _push(`</div>`);
2077
+ _push(`<!--]-->`);
1795
2078
  });
1796
2079
  _push(`<!--]--><div class="fl-foot"><span>${ssrInterpolate(t("loaded", { count: allFiles.value.length }))} · ${ssrInterpolate(t("totalSize", { size: unref(formatSize)(unref(totalSize)(allFiles.value)) }))}</span></div>`);
1797
2080
  if (toast.value) _push(`<div class="fl-toast" role="status">${ssrInterpolate(toast.value)}</div>`);
1798
2081
  else _push(`<!---->`);
2082
+ _push(ssrRenderComponent(_sfc_main$3, {
2083
+ open: !!zipNotice.value,
2084
+ title: zipNotice.value ? t(zipNotice.value.kind === "warn" ? "zipWarnTitle" : "zipBlockTitle", { size: unref(formatSize)(zipNotice.value.size) }) : "",
2085
+ text: zipNotice.value ? t(zipNotice.value.kind === "warn" ? "zipWarnText" : "zipBlockText", { limit: unref(formatSize)(zipNotice.value.limit) }) : "",
2086
+ tip: zipTip.value,
2087
+ "confirm-label": zipNotice.value?.kind === "warn" ? t("zipContinue") : void 0,
2088
+ "cancel-label": zipNotice.value?.kind === "warn" ? t("cancel") : t("gotIt"),
2089
+ onConfirm: confirmZip,
2090
+ onClose: ($event) => zipNotice.value = null
2091
+ }, null, _parent));
1799
2092
  _push(ssrRenderComponent(_sfc_main$1, {
1800
2093
  open: !!previewNode.value,
1801
2094
  file: previewNode.value,