vitepress-plugin-filelist 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -179,12 +179,29 @@ function previewKind(name) {
179
179
  function totalSize(items) {
180
180
  return items.reduce((sum, item) => sum + (Number.isFinite(item.size) && (item.size ?? 0) > 0 ? item.size : 0), 0);
181
181
  }
182
- function compareNodes(a, b, key, ascending = true) {
182
+ const collators = /* @__PURE__ */ new Map();
183
+ /**
184
+ * Name order for a UI locale: pinyin for Chinese, alphabetical for English,
185
+ * with numbers in natural order (“2” before “10”) either way.
186
+ */
187
+ function collatorFor(locale) {
188
+ const tag = locale.toLowerCase().startsWith("zh") ? "zh-Hans-CN" : "en";
189
+ let collator = collators.get(tag);
190
+ if (!collator) {
191
+ collator = new Intl.Collator(tag, {
192
+ numeric: true,
193
+ sensitivity: "base"
194
+ });
195
+ collators.set(tag, collator);
196
+ }
197
+ return collator;
198
+ }
199
+ function compareNodes(a, b, key, ascending = true, locale = "zh-CN") {
183
200
  if (a.is_dir !== b.is_dir) return a.is_dir ? -1 : 1;
184
201
  let result = 0;
185
202
  if (key === "size") result = a.size - b.size;
186
203
  else if (key === "modified") result = new Date(a.modified).getTime() - new Date(b.modified).getTime();
187
- else result = a.name.localeCompare(b.name, "zh-Hans-CN", { numeric: true });
204
+ else result = collatorFor(locale).compare(a.name, b.name) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
188
205
  return ascending ? result : -result;
189
206
  }
190
207
  /**
@@ -899,6 +916,22 @@ const fileListLocales = {
899
916
  forgetPassword: "忘记",
900
917
  unlockFolder: "输入 {name} 的密码",
901
918
  networkHint: "可能被跨域拦截:请检查 OpenList 的 cors.allow_origins,或 OpenList-Worker 的 ALLOW_URLS。",
919
+ deepHint: "只筛选了已加载的内容",
920
+ deepSearch: "深度搜索",
921
+ deepRunning: "正在深度搜索 · 已查找 {count} 个文件夹",
922
+ deepStop: "停止",
923
+ deepDone: "已搜索全部 {count} 个文件夹",
924
+ deepCapped: "已查找 {count} 个文件夹,还有未搜索的子目录",
925
+ deepContinue: "继续",
926
+ deepSkipped: "跳过 {count} 个加密文件夹",
927
+ deepFailed: "{count} 个文件夹读取失败",
928
+ viewMode: "显示方式",
929
+ viewSource: "源码",
930
+ viewRendered: "渲染",
931
+ htmlSandboxNote: "已禁用脚本;网页引用的相对路径资源不会加载",
932
+ imageOmitted: "[图片:{alt}]",
933
+ batchFailedTitle: "打包失败",
934
+ batchFailedHint: "可以减少勾选的文件后重试。",
902
935
  downloadStarted: "已交给浏览器下载:{name}",
903
936
  batchProgress: "{done} / {total} · {files} / {count} 个文件",
904
937
  batchDone: "已打包 {name}({count} 个文件,{size})",
@@ -977,6 +1010,22 @@ const fileListLocales = {
977
1010
  forgetPassword: "Forget",
978
1011
  unlockFolder: "Enter the password for {name}",
979
1012
  networkHint: "Possibly blocked by CORS: check cors.allow_origins in OpenList, or ALLOW_URLS in OpenList-Worker.",
1013
+ deepHint: "Only loaded folders are filtered",
1014
+ deepSearch: "Deep search",
1015
+ deepRunning: "Searching · {count} folders checked",
1016
+ deepStop: "Stop",
1017
+ deepDone: "Searched all {count} folders",
1018
+ deepCapped: "{count} folders checked; some sub-folders remain",
1019
+ deepContinue: "Continue",
1020
+ deepSkipped: "{count} locked folders skipped",
1021
+ deepFailed: "{count} folders could not be read",
1022
+ viewMode: "View",
1023
+ viewSource: "Source",
1024
+ viewRendered: "Rendered",
1025
+ htmlSandboxNote: "Scripts are disabled; resources at relative paths do not load",
1026
+ imageOmitted: "[Image: {alt}]",
1027
+ batchFailedTitle: "Could not create the ZIP",
1028
+ batchFailedHint: "Try again with fewer files selected.",
980
1029
  downloadStarted: "Downloading {name} in your browser",
981
1030
  batchProgress: "{done} / {total} · {files} / {count} files",
982
1031
  batchDone: "Saved {name} ({count} files, {size})",
@@ -1003,6 +1052,45 @@ function resolveLabels(locale = "zh-CN", overrides) {
1003
1052
  ...overrides
1004
1053
  };
1005
1054
  }
1055
+ // src/client/markdown.ts
1056
+ /**
1057
+ * Markdown → safe HTML for the preview dialog. Loaded on demand, so pages that
1058
+ * never render Markdown do not download markdown-it or DOMPurify.
1059
+ *
1060
+ * - Raw HTML in the file is allowed, then sanitised (no scripts, handlers, iframes...)
1061
+ * - Only absolute http(s) / mailto links stay clickable, opening in a new tab;
1062
+ * relative links (to other files in the share) become plain text
1063
+ * - Images with a relative source are replaced by their alt text, because the
1064
+ * share's relative paths do not resolve on the documentation site
1065
+ */
1066
+ async function renderMarkdown(source, imageLabel) {
1067
+ const [{ default: MarkdownIt }, { default: DOMPurify }] = await Promise.all([import("markdown-it"), import("dompurify")]);
1068
+ const probe = DOMPurify.isSupported ? DOMPurify.sanitize("<img src=\"x\" onerror=\"1\"><script>1<\/script>") : "<script";
1069
+ if (/<script|onerror/i.test(probe)) throw new Error("HTML sanitiser unavailable");
1070
+ const html = new MarkdownIt({
1071
+ html: true,
1072
+ linkify: true
1073
+ }).render(source);
1074
+ const template = document.createElement("template");
1075
+ template.innerHTML = DOMPurify.sanitize(html);
1076
+ const fragment = template.content;
1077
+ for (const link of fragment.querySelectorAll("a")) {
1078
+ const href = link.getAttribute("href") ?? "";
1079
+ if (/^(https?:|mailto:)/i.test(href)) {
1080
+ link.setAttribute("target", "_blank");
1081
+ link.setAttribute("rel", "noopener noreferrer");
1082
+ } else link.replaceWith(...link.childNodes);
1083
+ }
1084
+ for (const image of fragment.querySelectorAll("img")) {
1085
+ const src = image.getAttribute("src") ?? "";
1086
+ if (/^(https?:|data:image\/)/i.test(src)) continue;
1087
+ const placeholder = document.createElement("span");
1088
+ placeholder.className = "fl-md-image";
1089
+ placeholder.textContent = imageLabel(image.getAttribute("alt") || src.split("/").pop() || "");
1090
+ image.replaceWith(placeholder);
1091
+ }
1092
+ return template.innerHTML;
1093
+ }
1006
1094
  // src/client/PreviewDialog.vue
1007
1095
  const _hoisted_1$1 = ["aria-label"];
1008
1096
  const _hoisted_2$1 = {
@@ -1027,33 +1115,40 @@ const _hoisted_13$1 = [
1027
1115
  "aria-label",
1028
1116
  "title"
1029
1117
  ];
1030
- const _hoisted_14$1 = { class: "fl-preview-tools fl-preview-actions" };
1031
- const _hoisted_15$1 = ["aria-label", "title"];
1032
- const _hoisted_16$1 = ["aria-label", "title"];
1033
- const _hoisted_17$1 = ["data-mode"];
1034
- const _hoisted_18$1 = {
1118
+ const _hoisted_14$1 = ["aria-label"];
1119
+ const _hoisted_15$1 = ["aria-pressed", "onClick"];
1120
+ const _hoisted_16$1 = { class: "fl-preview-tools fl-preview-actions" };
1121
+ const _hoisted_17$1 = ["aria-label", "title"];
1122
+ const _hoisted_18$1 = ["aria-label", "title"];
1123
+ const _hoisted_19$1 = ["data-mode"];
1124
+ const _hoisted_20$1 = {
1035
1125
  key: 0,
1036
1126
  class: "fl-preview-status",
1037
1127
  role: "status"
1038
1128
  };
1039
- const _hoisted_19$1 = ["src", "alt"];
1040
- const _hoisted_20$1 = ["src"];
1041
- const _hoisted_21$1 = ["src"];
1042
- const _hoisted_22$1 = {
1043
- key: 4,
1044
- class: "fl-preview-text"
1045
- };
1046
- const _hoisted_23$1 = {
1129
+ const _hoisted_21$1 = ["src", "alt"];
1130
+ const _hoisted_22$1 = ["src"];
1131
+ const _hoisted_23$1 = ["src"];
1132
+ const _hoisted_24$1 = {
1047
1133
  key: 0,
1048
1134
  class: "fl-preview-note"
1049
1135
  };
1050
- const _hoisted_24$1 = ["src", "title"];
1051
- const _hoisted_25$1 = {
1136
+ const _hoisted_25$1 = ["innerHTML"];
1137
+ const _hoisted_26$1 = {
1138
+ key: 1,
1139
+ class: "fl-preview-status",
1140
+ role: "status"
1141
+ };
1142
+ const _hoisted_27$1 = { class: "fl-preview-note" };
1143
+ const _hoisted_28$1 = ["srcdoc", "title"];
1144
+ const _hoisted_29$1 = { key: 3 };
1145
+ const _hoisted_30$1 = ["src", "title"];
1146
+ const _hoisted_31$1 = {
1052
1147
  key: 6,
1053
1148
  class: "fl-preview-info"
1054
1149
  };
1055
- const _hoisted_26$1 = ["data-kind"];
1056
- const _hoisted_27$1 = { class: "fl-preview-message" };
1150
+ const _hoisted_32$1 = ["data-kind"];
1151
+ const _hoisted_33$1 = { class: "fl-preview-message" };
1057
1152
  const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1058
1153
  __name: "PreviewDialog",
1059
1154
  props: {
@@ -1086,7 +1181,9 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1086
1181
  /**
1087
1182
  * File preview in a native <dialog>, without leaving the page.
1088
1183
  *
1089
- * image / video / audio: native elements; text: the first `maxTextBytes`
1184
+ * image / video / audio: native elements; text: the first `maxTextBytes`,
1185
+ * with Markdown and HTML switchable between source (default) and rendered
1186
+ * (Markdown sanitised in VitePress' doc style, HTML in a script-less sandbox);
1090
1187
  * through a Range request; pdf: the first matching OpenList `iframe_previews`
1091
1188
  * viewer, else the browser's reader through a Blob. Anything else (Office
1092
1189
  * included), failures, oversized PDFs and demo mode: file info + download.
@@ -1105,8 +1202,18 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1105
1202
  const blobUrl = ref("");
1106
1203
  const rotation = ref(0);
1107
1204
  const actualSize = ref(false);
1205
+ /** Markdown / HTML: show the source or render it. Kept while the page is open. */
1206
+ const view = ref("source");
1207
+ const renderedHtml = ref("");
1108
1208
  let controller;
1109
1209
  let loadSeq = 0;
1210
+ /** Text files that can also be rendered. */
1211
+ const renderAs = computed(() => {
1212
+ const name = props.file?.name ?? "";
1213
+ if (/\.(md|markdown)$/i.test(name)) return "markdown";
1214
+ if (/\.html?$/i.test(name)) return "html";
1215
+ return null;
1216
+ });
1110
1217
  const tooLarge = computed(() => kind.value === "pdf" && !props.viewer && (props.file?.size ?? 0) > props.maxPdfBytes);
1111
1218
  const mode = computed(() => {
1112
1219
  if (props.demo) return "demo";
@@ -1116,6 +1223,7 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1116
1223
  return kind.value;
1117
1224
  });
1118
1225
  const busy = computed(() => !props.demo && kind.value !== "none" && !props.error && !bodyFailed.value && (props.resolving || loadingBody.value || !props.src && !tooLarge.value));
1226
+ const canRender = computed(() => !!renderAs.value && mode.value === "text" && !busy.value);
1119
1227
  const infoMessage = computed(() => {
1120
1228
  const file = props.file;
1121
1229
  if (!file) return "";
@@ -1136,6 +1244,7 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1136
1244
  loadingBody.value = false;
1137
1245
  bodyFailed.value = false;
1138
1246
  textContent.value = "";
1247
+ renderedHtml.value = "";
1139
1248
  truncated.value = false;
1140
1249
  rotation.value = 0;
1141
1250
  actualSize.value = false;
@@ -1217,6 +1326,20 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1217
1326
  props.error,
1218
1327
  props.demo
1219
1328
  ], () => void loadBody(), { immediate: true });
1329
+ watch(() => [
1330
+ view.value,
1331
+ renderAs.value,
1332
+ textContent.value
1333
+ ], async ([current, as, text]) => {
1334
+ if (current !== "rendered" || as !== "markdown" || !text) return;
1335
+ const seq = loadSeq;
1336
+ try {
1337
+ const html = await renderMarkdown(text, (alt) => t("imageOmitted", { alt }));
1338
+ if (seq === loadSeq) renderedHtml.value = html;
1339
+ } catch {
1340
+ if (seq === loadSeq) view.value = "source";
1341
+ }
1342
+ });
1220
1343
  watch(() => props.open, (open) => {
1221
1344
  const el = dialog.value;
1222
1345
  if (!el) return;
@@ -1266,49 +1389,66 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1266
1389
  "aria-hidden": "true"
1267
1390
  }, toDisplayString(unref(extOf)(__props.file.name) || "FILE"), 9, _hoisted_4$1),
1268
1391
  createElementVNode("div", _hoisted_5$1, [createElementVNode("span", _hoisted_6$1, toDisplayString(__props.file.name), 1), createElementVNode("span", _hoisted_7$1, toDisplayString(unref(formatSize)(__props.file.size)) + " · " + toDisplayString(unref(formatDate)(__props.file.modified)), 1)]),
1269
- __props.total > 1 || mode.value === "image" && !busy.value ? (openBlock(), createElementBlock("div", _hoisted_8$1, [__props.total > 1 ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
1270
- createElementVNode("button", {
1271
- class: "fl-preview-tool",
1392
+ __props.total > 1 || mode.value === "image" && !busy.value || canRender.value ? (openBlock(), createElementBlock("div", _hoisted_8$1, [
1393
+ __props.total > 1 ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
1394
+ createElementVNode("button", {
1395
+ class: "fl-preview-tool",
1396
+ type: "button",
1397
+ "aria-label": t("previous"),
1398
+ title: t("previous"),
1399
+ onClick: _cache[0] || (_cache[0] = ($event) => emit("prev"))
1400
+ }, [createVNode(_sfc_main$2, {
1401
+ name: "previous",
1402
+ size: 18
1403
+ })], 8, _hoisted_9$1),
1404
+ createElementVNode("span", _hoisted_10$1, toDisplayString(__props.position) + " / " + toDisplayString(__props.total), 1),
1405
+ createElementVNode("button", {
1406
+ class: "fl-preview-tool",
1407
+ type: "button",
1408
+ "aria-label": t("next"),
1409
+ title: t("next"),
1410
+ onClick: _cache[1] || (_cache[1] = ($event) => emit("next"))
1411
+ }, [createVNode(_sfc_main$2, {
1412
+ name: "next",
1413
+ size: 18
1414
+ })], 8, _hoisted_11$1)
1415
+ ], 64)) : createCommentVNode("v-if", true),
1416
+ mode.value === "image" && !busy.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createElementVNode("button", {
1417
+ class: "fl-preview-tool fl-preview-rotate",
1272
1418
  type: "button",
1273
- "aria-label": t("previous"),
1274
- title: t("previous"),
1275
- onClick: _cache[0] || (_cache[0] = ($event) => emit("prev"))
1419
+ "aria-label": t("rotate"),
1420
+ title: t("rotate"),
1421
+ onClick: _cache[2] || (_cache[2] = ($event) => rotation.value = (rotation.value + 90) % 360)
1276
1422
  }, [createVNode(_sfc_main$2, {
1277
- name: "previous",
1278
- size: 18
1279
- })], 8, _hoisted_9$1),
1280
- createElementVNode("span", _hoisted_10$1, toDisplayString(__props.position) + " / " + toDisplayString(__props.total), 1),
1281
- createElementVNode("button", {
1282
- class: "fl-preview-tool",
1423
+ name: "refresh",
1424
+ size: 17
1425
+ })], 8, _hoisted_12$1), createElementVNode("button", {
1426
+ class: "fl-preview-tool fl-preview-zoom",
1283
1427
  type: "button",
1284
- "aria-label": t("next"),
1285
- title: t("next"),
1286
- onClick: _cache[1] || (_cache[1] = ($event) => emit("next"))
1428
+ "aria-pressed": actualSize.value,
1429
+ "aria-label": t("actualSize"),
1430
+ title: t("actualSize"),
1431
+ onClick: _cache[3] || (_cache[3] = ($event) => actualSize.value = !actualSize.value)
1287
1432
  }, [createVNode(_sfc_main$2, {
1288
- name: "next",
1289
- size: 18
1290
- })], 8, _hoisted_11$1)
1291
- ], 64)) : createCommentVNode("v-if", true), mode.value === "image" && !busy.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createElementVNode("button", {
1292
- class: "fl-preview-tool fl-preview-rotate",
1293
- type: "button",
1294
- "aria-label": t("rotate"),
1295
- title: t("rotate"),
1296
- onClick: _cache[2] || (_cache[2] = ($event) => rotation.value = (rotation.value + 90) % 360)
1297
- }, [createVNode(_sfc_main$2, {
1298
- name: "refresh",
1299
- size: 17
1300
- })], 8, _hoisted_12$1), createElementVNode("button", {
1301
- class: "fl-preview-tool fl-preview-zoom",
1302
- type: "button",
1303
- "aria-pressed": actualSize.value,
1304
- "aria-label": t("actualSize"),
1305
- title: t("actualSize"),
1306
- onClick: _cache[3] || (_cache[3] = ($event) => actualSize.value = !actualSize.value)
1307
- }, [createVNode(_sfc_main$2, {
1308
- name: actualSize.value ? "zoomOut" : "zoomIn",
1309
- size: 17
1310
- }, null, 8, ["name"])], 8, _hoisted_13$1)], 64)) : createCommentVNode("v-if", true)])) : createCommentVNode("v-if", true),
1311
- createElementVNode("div", _hoisted_14$1, [!__props.demo ? (openBlock(), createElementBlock("button", {
1433
+ name: actualSize.value ? "zoomOut" : "zoomIn",
1434
+ size: 17
1435
+ }, null, 8, ["name"])], 8, _hoisted_13$1)], 64)) : createCommentVNode("v-if", true),
1436
+ canRender.value ? (openBlock(), createElementBlock("span", {
1437
+ key: 2,
1438
+ class: "fl-preview-view",
1439
+ role: "group",
1440
+ "aria-label": t("viewMode")
1441
+ }, [(openBlock(), createElementBlock(Fragment, null, renderList(["source", "rendered"], (option) => {
1442
+ return createElementVNode("button", {
1443
+ key: option,
1444
+ class: "fl-preview-view-btn",
1445
+ type: "button",
1446
+ "aria-pressed": view.value === option,
1447
+ onClick: ($event) => view.value = option
1448
+ }, toDisplayString(t(option === "source" ? "viewSource" : "viewRendered")), 9, _hoisted_15$1);
1449
+ }), 64))], 8, _hoisted_14$1)) : createCommentVNode("v-if", true)
1450
+ ])) : createCommentVNode("v-if", true),
1451
+ createElementVNode("div", _hoisted_16$1, [!__props.demo ? (openBlock(), createElementBlock("button", {
1312
1452
  key: 0,
1313
1453
  class: "fl-preview-tool fl-preview-download",
1314
1454
  type: "button",
@@ -1318,7 +1458,7 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1318
1458
  }, [createVNode(_sfc_main$2, {
1319
1459
  name: "download",
1320
1460
  size: 17
1321
- })], 8, _hoisted_15$1)) : createCommentVNode("v-if", true), createElementVNode("button", {
1461
+ })], 8, _hoisted_17$1)) : createCommentVNode("v-if", true), createElementVNode("button", {
1322
1462
  class: "fl-preview-tool fl-preview-close",
1323
1463
  type: "button",
1324
1464
  autofocus: "",
@@ -1328,18 +1468,18 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1328
1468
  }, [createVNode(_sfc_main$2, {
1329
1469
  name: "close",
1330
1470
  size: 18
1331
- })], 8, _hoisted_16$1)])
1471
+ })], 8, _hoisted_18$1)])
1332
1472
  ]), createElementVNode("div", {
1333
1473
  class: "fl-preview-body",
1334
1474
  "data-mode": busy.value ? "loading" : mode.value
1335
- }, [busy.value ? (openBlock(), createElementBlock("div", _hoisted_18$1, toDisplayString(t("loading")), 1)) : mode.value === "image" ? (openBlock(), createElementBlock("img", {
1475
+ }, [busy.value ? (openBlock(), createElementBlock("div", _hoisted_20$1, toDisplayString(t("loading")), 1)) : mode.value === "image" ? (openBlock(), createElementBlock("img", {
1336
1476
  key: 1,
1337
1477
  class: normalizeClass(["fl-preview-image", { "fl-preview-actual": actualSize.value }]),
1338
1478
  src: __props.src,
1339
1479
  alt: __props.file.name,
1340
1480
  style: normalizeStyle({ transform: rotation.value ? `rotate(${rotation.value}deg)` : void 0 }),
1341
1481
  onError: markFailed
1342
- }, null, 46, _hoisted_19$1)) : mode.value === "video" ? (openBlock(), createElementBlock("video", {
1482
+ }, null, 46, _hoisted_21$1)) : mode.value === "video" ? (openBlock(), createElementBlock("video", {
1343
1483
  key: 2,
1344
1484
  class: "fl-preview-video",
1345
1485
  src: __props.src,
@@ -1347,25 +1487,42 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1347
1487
  playsinline: "",
1348
1488
  preload: "metadata",
1349
1489
  onError: markFailed
1350
- }, null, 40, _hoisted_20$1)) : mode.value === "audio" ? (openBlock(), createElementBlock("audio", {
1490
+ }, null, 40, _hoisted_22$1)) : mode.value === "audio" ? (openBlock(), createElementBlock("audio", {
1351
1491
  key: 3,
1352
1492
  class: "fl-preview-audio",
1353
1493
  src: __props.src,
1354
1494
  controls: "",
1355
1495
  preload: "metadata",
1356
1496
  onError: markFailed
1357
- }, null, 40, _hoisted_21$1)) : mode.value === "text" ? (openBlock(), createElementBlock("div", _hoisted_22$1, [truncated.value ? (openBlock(), createElementBlock("p", _hoisted_23$1, toDisplayString(t("textTruncated", { size: unref(formatSize)(__props.maxTextBytes) })), 1)) : createCommentVNode("v-if", true), createElementVNode("pre", null, toDisplayString(textContent.value), 1)])) : mode.value === "pdf" ? (openBlock(), createElementBlock("iframe", {
1497
+ }, null, 40, _hoisted_23$1)) : mode.value === "text" ? (openBlock(), createElementBlock("div", {
1498
+ key: 4,
1499
+ class: normalizeClass(["fl-preview-text", { "fl-preview-rendered": renderAs.value && view.value === "rendered" }])
1500
+ }, [truncated.value ? (openBlock(), createElementBlock("p", _hoisted_24$1, toDisplayString(t("textTruncated", { size: unref(formatSize)(__props.maxTextBytes) })), 1)) : createCommentVNode("v-if", true), renderAs.value === "markdown" && view.value === "rendered" ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" sanitised by renderMarkdown (DOMPurify) "), renderedHtml.value ? (openBlock(), createElementBlock("div", {
1501
+ key: 0,
1502
+ class: "vp-doc fl-md",
1503
+ innerHTML: renderedHtml.value
1504
+ }, null, 8, _hoisted_25$1)) : (openBlock(), createElementBlock("div", _hoisted_26$1, toDisplayString(t("loading")), 1))], 64)) : renderAs.value === "html" && view.value === "rendered" ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
1505
+ createElementVNode("p", _hoisted_27$1, toDisplayString(t("htmlSandboxNote")), 1),
1506
+ createCommentVNode(" empty sandbox: no scripts, no forms, no popups, an opaque origin "),
1507
+ createElementVNode("iframe", {
1508
+ class: "fl-preview-html",
1509
+ sandbox: "",
1510
+ referrerpolicy: "no-referrer",
1511
+ srcdoc: textContent.value,
1512
+ title: __props.file.name
1513
+ }, null, 8, _hoisted_28$1)
1514
+ ], 64)) : (openBlock(), createElementBlock("pre", _hoisted_29$1, toDisplayString(textContent.value), 1))], 2)) : mode.value === "pdf" ? (openBlock(), createElementBlock("iframe", {
1358
1515
  key: 5,
1359
1516
  class: "fl-preview-frame",
1360
1517
  src: __props.viewer || blobUrl.value,
1361
1518
  title: __props.file.name
1362
- }, null, 8, _hoisted_24$1)) : (openBlock(), createElementBlock("div", _hoisted_25$1, [
1519
+ }, null, 8, _hoisted_30$1)) : (openBlock(), createElementBlock("div", _hoisted_31$1, [
1363
1520
  createElementVNode("span", {
1364
1521
  class: "fl-type fl-preview-badge",
1365
1522
  "data-kind": unref(fileKind)(__props.file.name),
1366
1523
  "aria-hidden": "true"
1367
- }, toDisplayString(unref(extOf)(__props.file.name) || "FILE"), 9, _hoisted_26$1),
1368
- createElementVNode("p", _hoisted_27$1, toDisplayString(infoMessage.value), 1),
1524
+ }, toDisplayString(unref(extOf)(__props.file.name) || "FILE"), 9, _hoisted_32$1),
1525
+ createElementVNode("p", _hoisted_33$1, toDisplayString(infoMessage.value), 1),
1369
1526
  !__props.demo ? (openBlock(), createElementBlock("button", {
1370
1527
  key: 0,
1371
1528
  class: "fl-btn fl-btn-primary",
@@ -1375,7 +1532,7 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1375
1532
  name: "download",
1376
1533
  size: 15
1377
1534
  }), createTextVNode(" " + toDisplayString(t("download")), 1)])) : createCommentVNode("v-if", true)
1378
- ]))], 8, _hoisted_17$1)])) : createCommentVNode("v-if", true)], 40, _hoisted_1$1);
1535
+ ]))], 8, _hoisted_19$1)])) : createCommentVNode("v-if", true)], 40, _hoisted_1$1);
1379
1536
  };
1380
1537
  }
1381
1538
  });
@@ -1402,9 +1559,9 @@ function toNodes(items, parent, password) {
1402
1559
  zipPath: [...parent.zipPath, item.name]
1403
1560
  }));
1404
1561
  }
1405
- function sortTree(nodes, key, ascending) {
1406
- nodes.sort((a, b) => compareNodes(a, b, key, ascending));
1407
- for (const node of nodes) if (node.children.length) sortTree(node.children, key, ascending);
1562
+ function sortTree(nodes, key, ascending, locale) {
1563
+ nodes.sort((a, b) => compareNodes(a, b, key, ascending, locale));
1564
+ for (const node of nodes) if (node.children.length) sortTree(node.children, key, ascending, locale);
1408
1565
  }
1409
1566
  /** Loaded files at or under a node. */
1410
1567
  function descendantFiles(node) {
@@ -1720,24 +1877,28 @@ const _hoisted_52 = {
1720
1877
  const _hoisted_53 = ["data-kind"];
1721
1878
  const _hoisted_54 = { class: "fl-label" };
1722
1879
  const _hoisted_55 = ["onClick"];
1723
- const _hoisted_56 = { class: "fl-meta" };
1724
- const _hoisted_57 = {
1880
+ const _hoisted_56 = {
1881
+ key: 0,
1882
+ class: "fl-hit"
1883
+ };
1884
+ const _hoisted_57 = { class: "fl-meta" };
1885
+ const _hoisted_58 = {
1725
1886
  key: 5,
1726
1887
  class: "fl-count"
1727
1888
  };
1728
- const _hoisted_58 = ["id"];
1729
- const _hoisted_59 = [
1889
+ const _hoisted_59 = ["id"];
1890
+ const _hoisted_60 = [
1730
1891
  "aria-label",
1731
1892
  "title",
1732
1893
  "onClick"
1733
1894
  ];
1734
- const _hoisted_60 = {
1895
+ const _hoisted_61 = {
1735
1896
  key: 8,
1736
1897
  class: "fl-loading",
1737
1898
  "aria-hidden": "true"
1738
1899
  };
1739
- const _hoisted_61 = ["onSubmit", "onKeydown"];
1740
- const _hoisted_62 = [
1900
+ const _hoisted_62 = ["onSubmit", "onKeydown"];
1901
+ const _hoisted_63 = [
1741
1902
  "onUpdate:modelValue",
1742
1903
  "data-unlock",
1743
1904
  "placeholder",
@@ -1745,40 +1906,49 @@ const _hoisted_62 = [
1745
1906
  "aria-invalid",
1746
1907
  "aria-describedby"
1747
1908
  ];
1748
- const _hoisted_63 = [
1909
+ const _hoisted_64 = [
1749
1910
  "aria-pressed",
1750
1911
  "aria-label",
1751
1912
  "title",
1752
1913
  "onClick"
1753
1914
  ];
1754
- const _hoisted_64 = ["disabled"];
1755
- const _hoisted_65 = [
1915
+ const _hoisted_65 = ["disabled"];
1916
+ const _hoisted_66 = [
1756
1917
  "aria-label",
1757
1918
  "title",
1758
1919
  "onClick"
1759
1920
  ];
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 = [
1921
+ const _hoisted_67 = { class: "fl-cell fl-cell-size" };
1922
+ const _hoisted_68 = { class: "fl-cell fl-cell-date" };
1923
+ const _hoisted_69 = { class: "fl-cell fl-cell-op" };
1924
+ const _hoisted_70 = ["aria-label", "onClick"];
1925
+ const _hoisted_71 = [
1765
1926
  "aria-label",
1766
1927
  "aria-expanded",
1767
1928
  "onClick"
1768
1929
  ];
1769
- const _hoisted_71 = [
1930
+ const _hoisted_72 = [
1770
1931
  "aria-label",
1771
1932
  "title",
1772
1933
  "onClick"
1773
1934
  ];
1774
- const _hoisted_72 = [
1935
+ const _hoisted_73 = [
1775
1936
  "aria-label",
1776
1937
  "title",
1777
1938
  "onClick"
1778
1939
  ];
1779
- const _hoisted_73 = { class: "fl-foot" };
1780
1940
  const _hoisted_74 = {
1781
1941
  key: 4,
1942
+ class: "fl-row fl-deep",
1943
+ role: "status"
1944
+ };
1945
+ const _hoisted_75 = { class: "fl-cell fl-more-cell" };
1946
+ const _hoisted_76 = { class: "fl-more-count" };
1947
+ const _hoisted_77 = { class: "fl-more-count" };
1948
+ const _hoisted_78 = { class: "fl-more-count" };
1949
+ const _hoisted_79 = { class: "fl-foot" };
1950
+ const _hoisted_80 = {
1951
+ key: 5,
1782
1952
  class: "fl-toast",
1783
1953
  role: "status"
1784
1954
  };
@@ -1802,6 +1972,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1802
1972
  default: true
1803
1973
  },
1804
1974
  pageSize: { default: 200 },
1975
+ deepSearchLimit: { default: 500 },
1805
1976
  zipWarnSize: {},
1806
1977
  zipMaxSize: {},
1807
1978
  locale: { default: "zh-CN" },
@@ -1866,7 +2037,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1866
2037
  return isNetworkError(error) ? `${message} ${t("corsHint")}` : message;
1867
2038
  }
1868
2039
  function sortNodes(nodes) {
1869
- sortTree(nodes, sortKey.value, sortAsc.value);
2040
+ sortTree(nodes, sortKey.value, sortAsc.value, props.locale);
1870
2041
  }
1871
2042
  function focusUnlock(path) {
1872
2043
  nextTick(() => {
@@ -1971,7 +2142,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1971
2142
  * Load a folder's children. A password error locks the folder instead of
1972
2143
  * toasting; `prompt` also shows its same-row input (and focuses it).
1973
2144
  */
1974
- async function loadChildren(node, prompt = false) {
2145
+ async function loadChildren(node, prompt = false, quiet = false) {
1975
2146
  if (node.loaded) return true;
1976
2147
  if (node.lock) {
1977
2148
  if (prompt) startPrompt(node, prompt === "focus");
@@ -1987,7 +2158,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1987
2158
  node.expanded = false;
1988
2159
  node.lock = newLock(false, passwords.dropStale(rememberedAt) ? "stale" : "");
1989
2160
  if (prompt) startPrompt(node, prompt === "focus");
1990
- } else showToast(failureMessage("folderFailed", error));
2161
+ } else if (!quiet) showToast(failureMessage("folderFailed", error));
1991
2162
  return false;
1992
2163
  } finally {
1993
2164
  node.loading = false;
@@ -2074,6 +2245,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2074
2245
  }
2075
2246
  /** First load, or a retry after an error: shows the loading state. */
2076
2247
  async function loadRoot() {
2248
+ stopDeepSearch();
2077
2249
  refreshSeq += 1;
2078
2250
  refreshing.value = false;
2079
2251
  loadingRoot.value = true;
@@ -2097,6 +2269,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2097
2269
  */
2098
2270
  async function refresh() {
2099
2271
  if (refreshing.value || loadingRoot.value) return;
2272
+ stopDeepSearch();
2100
2273
  if (!roots.value.length || errorMsg.value || rootLock.value) {
2101
2274
  await loadRoot();
2102
2275
  return;
@@ -2196,6 +2369,124 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2196
2369
  return out;
2197
2370
  });
2198
2371
  const visible = computed(() => rows.value.filter((row) => !isMore(row)));
2372
+ /** A name split around the filter keyword, for highlighting. */
2373
+ function nameParts(name) {
2374
+ const query = keyword.value.trim().toLowerCase();
2375
+ if (!query) return [{
2376
+ text: name,
2377
+ hit: false
2378
+ }];
2379
+ const parts = [];
2380
+ const lower = name.toLowerCase();
2381
+ let from = 0;
2382
+ for (let at = lower.indexOf(query); at !== -1; at = lower.indexOf(query, from)) {
2383
+ if (at > from) parts.push({
2384
+ text: name.slice(from, at),
2385
+ hit: false
2386
+ });
2387
+ parts.push({
2388
+ text: name.slice(at, at + query.length),
2389
+ hit: true
2390
+ });
2391
+ from = at + query.length;
2392
+ }
2393
+ if (from < name.length) parts.push({
2394
+ text: name.slice(from),
2395
+ hit: false
2396
+ });
2397
+ return parts;
2398
+ }
2399
+ /**
2400
+ * The filter only sees loaded folders. Deep search loads the rest under the
2401
+ * mounted paths (four at a time, `deepSearchLimit` per run) so matches appear
2402
+ * in place, in their folders, like any other row. Locked folders are skipped.
2403
+ */
2404
+ const deep = reactive({
2405
+ running: false,
2406
+ searched: 0,
2407
+ skipped: 0,
2408
+ failed: 0,
2409
+ capped: false,
2410
+ done: false
2411
+ });
2412
+ let deepSeq = 0;
2413
+ /** Folders that are known but not loaded yet (locked ones excluded). */
2414
+ function unloadedFolders() {
2415
+ const out = [];
2416
+ const walk = (nodes) => {
2417
+ for (const node of nodes) {
2418
+ if (!node.is_dir || node.lock) continue;
2419
+ if (!node.loaded) out.push(node);
2420
+ else walk(node.children);
2421
+ }
2422
+ };
2423
+ walk(roots.value);
2424
+ return out;
2425
+ }
2426
+ const hasUnloaded = computed(() => unloadedFolders().length > 0);
2427
+ const deepRow = computed(() => {
2428
+ if (!keyword.value.trim()) return null;
2429
+ if (deep.running) return "running";
2430
+ if (deep.capped) return "capped";
2431
+ if (hasUnloaded.value) return "idle";
2432
+ return deep.done ? "done" : null;
2433
+ });
2434
+ async function deepSearch() {
2435
+ const seq = ++deepSeq;
2436
+ const limit = Math.max(1, Math.floor(props.deepSearchLimit ?? 500));
2437
+ const runStart = deep.searched;
2438
+ Object.assign(deep, {
2439
+ running: true,
2440
+ capped: false,
2441
+ done: false,
2442
+ skipped: 0,
2443
+ failed: 0
2444
+ });
2445
+ let wave = unloadedFolders();
2446
+ while (wave.length && seq === deepSeq) {
2447
+ const next = [];
2448
+ const queue = [...wave];
2449
+ const worker = async () => {
2450
+ for (let node = queue.shift(); node && seq === deepSeq; node = queue.shift()) {
2451
+ if (deep.searched - runStart >= limit) return;
2452
+ deep.searched += 1;
2453
+ if (!await loadChildren(node, false, true)) {
2454
+ if (node.lock) deep.skipped += 1;
2455
+ else deep.failed += 1;
2456
+ continue;
2457
+ }
2458
+ for (const child of node.children) if (child.is_dir && !child.loaded && !child.lock) next.push(child);
2459
+ }
2460
+ };
2461
+ await Promise.all(Array.from({ length: Math.min(4, queue.length) }, worker));
2462
+ if (deep.searched - runStart >= limit) break;
2463
+ wave = next;
2464
+ }
2465
+ if (seq !== deepSeq) return;
2466
+ deep.running = false;
2467
+ deep.capped = hasUnloaded.value && deep.searched - runStart >= limit;
2468
+ deep.done = !deep.capped;
2469
+ }
2470
+ function stopDeepSearch() {
2471
+ deepSeq += 1;
2472
+ deep.running = false;
2473
+ }
2474
+ watch(keyword, (value) => {
2475
+ if (value.trim()) return;
2476
+ stopDeepSearch();
2477
+ Object.assign(deep, {
2478
+ searched: 0,
2479
+ skipped: 0,
2480
+ failed: 0,
2481
+ capped: false,
2482
+ done: false
2483
+ });
2484
+ });
2485
+ const deepSummary = computed(() => [
2486
+ deepRow.value === "capped" ? t("deepCapped", { count: deep.searched }) : t("deepDone", { count: deep.searched }),
2487
+ deep.skipped ? t("deepSkipped", { count: deep.skipped }) : "",
2488
+ deep.failed ? t("deepFailed", { count: deep.failed }) : ""
2489
+ ].filter(Boolean).join(" · "));
2199
2490
  const stats = computed(() => folderStats(roots.value, selected.value));
2200
2491
  const noStats = {
2201
2492
  files: 0,
@@ -2298,9 +2589,28 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2298
2589
  max: bytes(props.zipMaxSize, touch ? 1024 : 2048)
2299
2590
  };
2300
2591
  }
2592
+ /** A ZIP question or notice: over the warning size, over the limit, or failed. */
2301
2593
  const zipNotice = ref(null);
2302
2594
  /** Only desktop browsers get “use Chrome or Edge”; on iPhone every browser is Safari. */
2303
2595
  const zipTip = computed(() => typeof window !== "undefined" && !window.matchMedia?.("(pointer: coarse)").matches ? t("zipStreamTip") : "");
2596
+ const zipDialog = computed(() => {
2597
+ const notice = zipNotice.value;
2598
+ if (!notice) return null;
2599
+ if (notice.kind === "failed") return {
2600
+ title: t("batchFailedTitle"),
2601
+ text: notice.message,
2602
+ tip: t("batchFailedHint"),
2603
+ cancel: t("gotIt")
2604
+ };
2605
+ const warn = notice.kind === "warn";
2606
+ return {
2607
+ title: t(warn ? "zipWarnTitle" : "zipBlockTitle", { size: formatSize(notice.size) }),
2608
+ text: t(warn ? "zipWarnText" : "zipBlockText", { limit: formatSize(notice.limit) }),
2609
+ tip: zipTip.value,
2610
+ confirm: warn ? t("zipContinue") : void 0,
2611
+ cancel: warn ? t("cancel") : t("gotIt")
2612
+ };
2613
+ });
2304
2614
  function zipName() {
2305
2615
  return zipFileName(props.title || t("defaultZip"));
2306
2616
  }
@@ -2369,7 +2679,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2369
2679
  }));
2370
2680
  selected.value = /* @__PURE__ */ new Set();
2371
2681
  } catch (error) {
2372
- showToast(controller.signal.aborted ? t("batchCanceled") : t("batchFailed", { message: errorText(error) }));
2682
+ if (controller.signal.aborted) showToast(t("batchCanceled"));
2683
+ else zipNotice.value = {
2684
+ kind: "failed",
2685
+ message: errorText(error)
2686
+ };
2373
2687
  } finally {
2374
2688
  zipAbort = null;
2375
2689
  batchBusy.value = false;
@@ -2709,14 +3023,16 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2709
3023
  createElementVNode("span", _hoisted_54, [createElementVNode("span", {
2710
3024
  class: normalizeClass(["fl-name", { "fl-name-dir": node.is_dir }]),
2711
3025
  onClick: ($event) => node.lock || node.is_dir ? toggleExpand(node) : download(node)
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),
3026
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(nameParts(node.name), (part, at) => {
3027
+ return openBlock(), createElementBlock(Fragment, { key: at }, [part.hit ? (openBlock(), createElementBlock("mark", _hoisted_56, toDisplayString(part.text), 1)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(part.text), 1)], 64))], 64);
3028
+ }), 128))], 10, _hoisted_55), createElementVNode("span", _hoisted_57, [!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)])]),
3029
+ node.is_dir && node.loaded ? (openBlock(), createElementBlock("span", _hoisted_58, toDisplayString(t("folderItems", { count: statsOf(node).files })), 1)) : createCommentVNode("v-if", true),
2714
3030
  node.lock?.prompting && node.lock.error ? (openBlock(), createElementBlock("span", {
2715
3031
  key: 6,
2716
3032
  id: `${unref(uid)}-error-${index}`,
2717
3033
  class: "fl-unlock-message",
2718
3034
  role: "alert"
2719
- }, toDisplayString(unlockErrorText(node.lock.error)), 9, _hoisted_58)) : createCommentVNode("v-if", true),
3035
+ }, toDisplayString(unlockErrorText(node.lock.error)), 9, _hoisted_59)) : createCommentVNode("v-if", true),
2720
3036
  !node.lock && unref(remembered).has(node.path) ? (openBlock(), createElementBlock("button", {
2721
3037
  key: 7,
2722
3038
  class: "fl-remembered",
@@ -2727,8 +3043,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2727
3043
  }, [createVNode(_sfc_main$2, {
2728
3044
  name: "key",
2729
3045
  size: 11
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)
3046
+ }), createTextVNode(" " + toDisplayString(t("remembered")) + " · " + toDisplayString(t("forgetPassword")), 1)], 8, _hoisted_60)) : createCommentVNode("v-if", true),
3047
+ node.loading ? (openBlock(), createElementBlock("span", _hoisted_61, "…")) : createCommentVNode("v-if", true)
2732
3048
  ], 8, _hoisted_48),
2733
3049
  node.lock?.prompting ? (openBlock(), createElementBlock("form", {
2734
3050
  key: 0,
@@ -2750,7 +3066,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2750
3066
  "aria-label": t("passwordFor", { name: node.name }),
2751
3067
  "aria-invalid": !!node.lock.error,
2752
3068
  "aria-describedby": node.lock.error ? `${unref(uid)}-error-${index}` : void 0
2753
- }, null, 8, _hoisted_62), [[vModelText, node.lock.input]]),
3069
+ }, null, 8, _hoisted_63), [[vModelText, node.lock.input]]),
2754
3070
  canRemember.value ? (openBlock(), createElementBlock("button", {
2755
3071
  key: 0,
2756
3072
  class: "fl-unlock-remember",
@@ -2759,12 +3075,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2759
3075
  "aria-label": t("rememberPassword"),
2760
3076
  title: t("rememberPassword"),
2761
3077
  onClick: ($event) => node.lock.remember = !node.lock.remember
2762
- }, [createVNode(_sfc_main$2, { name: "key" })], 8, _hoisted_63)) : createCommentVNode("v-if", true),
3078
+ }, [createVNode(_sfc_main$2, { name: "key" })], 8, _hoisted_64)) : createCommentVNode("v-if", true),
2763
3079
  createElementVNode("button", {
2764
3080
  class: "fl-unlock-submit",
2765
3081
  type: "submit",
2766
3082
  disabled: node.lock.busy || !node.lock.input
2767
- }, toDisplayString(t("open")), 9, _hoisted_64),
3083
+ }, toDisplayString(t("open")), 9, _hoisted_65),
2768
3084
  createElementVNode("button", {
2769
3085
  class: "fl-unlock-cancel",
2770
3086
  type: "button",
@@ -2774,11 +3090,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2774
3090
  }, [createVNode(_sfc_main$2, {
2775
3091
  name: "close",
2776
3092
  size: 14
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", {
3093
+ })], 8, _hoisted_66)
3094
+ ], 40, _hoisted_62)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
3095
+ createElementVNode("span", _hoisted_67, toDisplayString(node.is_dir ? "—" : unref(formatSize)(node.size)), 1),
3096
+ createElementVNode("span", _hoisted_68, toDisplayString(unref(formatDate)(node.modified)), 1),
3097
+ createElementVNode("span", _hoisted_69, [node.lock ? (openBlock(), createElementBlock("button", {
2782
3098
  key: 0,
2783
3099
  class: "fl-action fl-arrow-end fl-lock",
2784
3100
  type: "button",
@@ -2787,7 +3103,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2787
3103
  }, [createVNode(_sfc_main$2, {
2788
3104
  name: "lock",
2789
3105
  size: 18
2790
- })], 8, _hoisted_69)) : node.is_dir ? (openBlock(), createElementBlock("button", {
3106
+ })], 8, _hoisted_70)) : node.is_dir ? (openBlock(), createElementBlock("button", {
2791
3107
  key: 1,
2792
3108
  class: normalizeClass(["fl-action fl-arrow-end", { "fl-arrow-open": node.expanded }]),
2793
3109
  type: "button",
@@ -2797,32 +3113,56 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2797
3113
  }, [createVNode(_sfc_main$2, {
2798
3114
  name: "next",
2799
3115
  size: 18
2800
- })], 10, _hoisted_70)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [canPreview(node) ? (openBlock(), createElementBlock("button", {
3116
+ })], 10, _hoisted_71)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [canPreview(node) ? (openBlock(), createElementBlock("button", {
2801
3117
  key: 0,
2802
3118
  class: "fl-action fl-preview-btn",
2803
3119
  type: "button",
2804
3120
  "aria-label": `${t("preview")} ${node.name}`,
2805
3121
  title: t("preview"),
2806
3122
  onClick: ($event) => preview(node)
2807
- }, [createVNode(_sfc_main$2, { name: "eye" })], 8, _hoisted_71)) : createCommentVNode("v-if", true), createElementVNode("button", {
3123
+ }, [createVNode(_sfc_main$2, { name: "eye" })], 8, _hoisted_72)) : createCommentVNode("v-if", true), createElementVNode("button", {
2808
3124
  class: "fl-action fl-download",
2809
3125
  type: "button",
2810
3126
  "aria-label": `${t("download")} ${node.name}`,
2811
3127
  title: t("download"),
2812
3128
  onClick: ($event) => download(node)
2813
- }, [createVNode(_sfc_main$2, { name: "download" })], 8, _hoisted_72)], 64))])
3129
+ }, [createVNode(_sfc_main$2, { name: "download" })], 8, _hoisted_73)], 64))])
2814
3130
  ], 64))
2815
3131
  ], 6))], 64);
2816
3132
  }), 128)),
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),
3133
+ deepRow.value ? (openBlock(), createElementBlock("div", _hoisted_74, [_cache[14] || (_cache[14] = createElementVNode("span", { class: "fl-cell fl-cell-check" }, null, -1)), createElementVNode("span", _hoisted_75, [_cache[13] || (_cache[13] = createElementVNode("span", {
3134
+ class: "fl-indent",
3135
+ "aria-hidden": "true"
3136
+ }, null, -1)), deepRow.value === "running" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
3137
+ createElementVNode("span", _hoisted_76, toDisplayString(t("deepRunning", { count: deep.searched })), 1),
3138
+ createElementVNode("button", {
3139
+ class: "fl-more-btn fl-more-all",
3140
+ type: "button",
3141
+ onClick: stopDeepSearch
3142
+ }, toDisplayString(t("deepStop")), 1),
3143
+ _cache[12] || (_cache[12] = createElementVNode("span", {
3144
+ class: "fl-deep-bar",
3145
+ "aria-hidden": "true"
3146
+ }, null, -1))
3147
+ ], 64)) : deepRow.value === "idle" ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createElementVNode("span", _hoisted_77, toDisplayString(deep.searched ? deepSummary.value : t("deepHint")), 1), createElementVNode("button", {
3148
+ class: "fl-more-btn",
3149
+ type: "button",
3150
+ onClick: deepSearch
3151
+ }, toDisplayString(t("deepSearch")), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [createElementVNode("span", _hoisted_78, toDisplayString(deepSummary.value), 1), deepRow.value === "capped" ? (openBlock(), createElementBlock("button", {
3152
+ key: 0,
3153
+ class: "fl-more-btn",
3154
+ type: "button",
3155
+ onClick: deepSearch
3156
+ }, toDisplayString(t("deepContinue")), 1)) : createCommentVNode("v-if", true)], 64))])])) : createCommentVNode("v-if", true),
3157
+ createElementVNode("div", _hoisted_79, [createElementVNode("span", null, toDisplayString(t("loaded", { count: allFiles.value.length })) + " · " + toDisplayString(t("totalSize", { size: unref(formatSize)(unref(totalSize)(allFiles.value)) })), 1)]),
3158
+ toast.value ? (openBlock(), createElementBlock("div", _hoisted_80, toDisplayString(toast.value), 1)) : createCommentVNode("v-if", true),
2819
3159
  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"),
3160
+ open: !!zipDialog.value,
3161
+ title: zipDialog.value?.title ?? "",
3162
+ text: zipDialog.value?.text ?? "",
3163
+ tip: zipDialog.value?.tip,
3164
+ "confirm-label": zipDialog.value?.confirm,
3165
+ "cancel-label": zipDialog.value?.cancel ?? "",
2826
3166
  onConfirm: confirmZip,
2827
3167
  onClose: _cache[4] || (_cache[4] = ($event) => zipNotice.value = null)
2828
3168
  }, null, 8, [