vitepress-plugin-filelist 0.6.2 → 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,20 @@ 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}]",
902
933
  batchFailedTitle: "打包失败",
903
934
  batchFailedHint: "可以减少勾选的文件后重试。",
904
935
  downloadStarted: "已交给浏览器下载:{name}",
@@ -979,6 +1010,20 @@ const fileListLocales = {
979
1010
  forgetPassword: "Forget",
980
1011
  unlockFolder: "Enter the password for {name}",
981
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}]",
982
1027
  batchFailedTitle: "Could not create the ZIP",
983
1028
  batchFailedHint: "Try again with fewer files selected.",
984
1029
  downloadStarted: "Downloading {name} in your browser",
@@ -1007,6 +1052,45 @@ function resolveLabels(locale = "zh-CN", overrides) {
1007
1052
  ...overrides
1008
1053
  };
1009
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
+ }
1010
1094
  // src/client/PreviewDialog.vue
1011
1095
  const _hoisted_1$1 = ["aria-label"];
1012
1096
  const _hoisted_2$1 = {
@@ -1031,33 +1115,40 @@ const _hoisted_13$1 = [
1031
1115
  "aria-label",
1032
1116
  "title"
1033
1117
  ];
1034
- const _hoisted_14$1 = { class: "fl-preview-tools fl-preview-actions" };
1035
- const _hoisted_15$1 = ["aria-label", "title"];
1036
- const _hoisted_16$1 = ["aria-label", "title"];
1037
- const _hoisted_17$1 = ["data-mode"];
1038
- 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 = {
1039
1125
  key: 0,
1040
1126
  class: "fl-preview-status",
1041
1127
  role: "status"
1042
1128
  };
1043
- const _hoisted_19$1 = ["src", "alt"];
1044
- const _hoisted_20$1 = ["src"];
1045
- const _hoisted_21$1 = ["src"];
1046
- const _hoisted_22$1 = {
1047
- key: 4,
1048
- class: "fl-preview-text"
1049
- };
1050
- 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 = {
1051
1133
  key: 0,
1052
1134
  class: "fl-preview-note"
1053
1135
  };
1054
- const _hoisted_24$1 = ["src", "title"];
1055
- 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 = {
1056
1147
  key: 6,
1057
1148
  class: "fl-preview-info"
1058
1149
  };
1059
- const _hoisted_26$1 = ["data-kind"];
1060
- const _hoisted_27$1 = { class: "fl-preview-message" };
1150
+ const _hoisted_32$1 = ["data-kind"];
1151
+ const _hoisted_33$1 = { class: "fl-preview-message" };
1061
1152
  const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1062
1153
  __name: "PreviewDialog",
1063
1154
  props: {
@@ -1090,7 +1181,9 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1090
1181
  /**
1091
1182
  * File preview in a native <dialog>, without leaving the page.
1092
1183
  *
1093
- * 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);
1094
1187
  * through a Range request; pdf: the first matching OpenList `iframe_previews`
1095
1188
  * viewer, else the browser's reader through a Blob. Anything else (Office
1096
1189
  * included), failures, oversized PDFs and demo mode: file info + download.
@@ -1109,8 +1202,18 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1109
1202
  const blobUrl = ref("");
1110
1203
  const rotation = ref(0);
1111
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("");
1112
1208
  let controller;
1113
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
+ });
1114
1217
  const tooLarge = computed(() => kind.value === "pdf" && !props.viewer && (props.file?.size ?? 0) > props.maxPdfBytes);
1115
1218
  const mode = computed(() => {
1116
1219
  if (props.demo) return "demo";
@@ -1120,6 +1223,7 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1120
1223
  return kind.value;
1121
1224
  });
1122
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);
1123
1227
  const infoMessage = computed(() => {
1124
1228
  const file = props.file;
1125
1229
  if (!file) return "";
@@ -1140,6 +1244,7 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1140
1244
  loadingBody.value = false;
1141
1245
  bodyFailed.value = false;
1142
1246
  textContent.value = "";
1247
+ renderedHtml.value = "";
1143
1248
  truncated.value = false;
1144
1249
  rotation.value = 0;
1145
1250
  actualSize.value = false;
@@ -1221,6 +1326,20 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1221
1326
  props.error,
1222
1327
  props.demo
1223
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
+ });
1224
1343
  watch(() => props.open, (open) => {
1225
1344
  const el = dialog.value;
1226
1345
  if (!el) return;
@@ -1270,49 +1389,66 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1270
1389
  "aria-hidden": "true"
1271
1390
  }, toDisplayString(unref(extOf)(__props.file.name) || "FILE"), 9, _hoisted_4$1),
1272
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)]),
1273
- __props.total > 1 || mode.value === "image" && !busy.value ? (openBlock(), createElementBlock("div", _hoisted_8$1, [__props.total > 1 ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
1274
- createElementVNode("button", {
1275
- 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",
1276
1418
  type: "button",
1277
- "aria-label": t("previous"),
1278
- title: t("previous"),
1279
- 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)
1280
1422
  }, [createVNode(_sfc_main$2, {
1281
- name: "previous",
1282
- size: 18
1283
- })], 8, _hoisted_9$1),
1284
- createElementVNode("span", _hoisted_10$1, toDisplayString(__props.position) + " / " + toDisplayString(__props.total), 1),
1285
- createElementVNode("button", {
1286
- 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",
1287
1427
  type: "button",
1288
- "aria-label": t("next"),
1289
- title: t("next"),
1290
- 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)
1291
1432
  }, [createVNode(_sfc_main$2, {
1292
- name: "next",
1293
- size: 18
1294
- })], 8, _hoisted_11$1)
1295
- ], 64)) : createCommentVNode("v-if", true), mode.value === "image" && !busy.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createElementVNode("button", {
1296
- class: "fl-preview-tool fl-preview-rotate",
1297
- type: "button",
1298
- "aria-label": t("rotate"),
1299
- title: t("rotate"),
1300
- onClick: _cache[2] || (_cache[2] = ($event) => rotation.value = (rotation.value + 90) % 360)
1301
- }, [createVNode(_sfc_main$2, {
1302
- name: "refresh",
1303
- size: 17
1304
- })], 8, _hoisted_12$1), createElementVNode("button", {
1305
- class: "fl-preview-tool fl-preview-zoom",
1306
- type: "button",
1307
- "aria-pressed": actualSize.value,
1308
- "aria-label": t("actualSize"),
1309
- title: t("actualSize"),
1310
- onClick: _cache[3] || (_cache[3] = ($event) => actualSize.value = !actualSize.value)
1311
- }, [createVNode(_sfc_main$2, {
1312
- name: actualSize.value ? "zoomOut" : "zoomIn",
1313
- size: 17
1314
- }, null, 8, ["name"])], 8, _hoisted_13$1)], 64)) : createCommentVNode("v-if", true)])) : createCommentVNode("v-if", true),
1315
- 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", {
1316
1452
  key: 0,
1317
1453
  class: "fl-preview-tool fl-preview-download",
1318
1454
  type: "button",
@@ -1322,7 +1458,7 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1322
1458
  }, [createVNode(_sfc_main$2, {
1323
1459
  name: "download",
1324
1460
  size: 17
1325
- })], 8, _hoisted_15$1)) : createCommentVNode("v-if", true), createElementVNode("button", {
1461
+ })], 8, _hoisted_17$1)) : createCommentVNode("v-if", true), createElementVNode("button", {
1326
1462
  class: "fl-preview-tool fl-preview-close",
1327
1463
  type: "button",
1328
1464
  autofocus: "",
@@ -1332,18 +1468,18 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1332
1468
  }, [createVNode(_sfc_main$2, {
1333
1469
  name: "close",
1334
1470
  size: 18
1335
- })], 8, _hoisted_16$1)])
1471
+ })], 8, _hoisted_18$1)])
1336
1472
  ]), createElementVNode("div", {
1337
1473
  class: "fl-preview-body",
1338
1474
  "data-mode": busy.value ? "loading" : mode.value
1339
- }, [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", {
1340
1476
  key: 1,
1341
1477
  class: normalizeClass(["fl-preview-image", { "fl-preview-actual": actualSize.value }]),
1342
1478
  src: __props.src,
1343
1479
  alt: __props.file.name,
1344
1480
  style: normalizeStyle({ transform: rotation.value ? `rotate(${rotation.value}deg)` : void 0 }),
1345
1481
  onError: markFailed
1346
- }, null, 46, _hoisted_19$1)) : mode.value === "video" ? (openBlock(), createElementBlock("video", {
1482
+ }, null, 46, _hoisted_21$1)) : mode.value === "video" ? (openBlock(), createElementBlock("video", {
1347
1483
  key: 2,
1348
1484
  class: "fl-preview-video",
1349
1485
  src: __props.src,
@@ -1351,25 +1487,42 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1351
1487
  playsinline: "",
1352
1488
  preload: "metadata",
1353
1489
  onError: markFailed
1354
- }, null, 40, _hoisted_20$1)) : mode.value === "audio" ? (openBlock(), createElementBlock("audio", {
1490
+ }, null, 40, _hoisted_22$1)) : mode.value === "audio" ? (openBlock(), createElementBlock("audio", {
1355
1491
  key: 3,
1356
1492
  class: "fl-preview-audio",
1357
1493
  src: __props.src,
1358
1494
  controls: "",
1359
1495
  preload: "metadata",
1360
1496
  onError: markFailed
1361
- }, 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", {
1362
1515
  key: 5,
1363
1516
  class: "fl-preview-frame",
1364
1517
  src: __props.viewer || blobUrl.value,
1365
1518
  title: __props.file.name
1366
- }, null, 8, _hoisted_24$1)) : (openBlock(), createElementBlock("div", _hoisted_25$1, [
1519
+ }, null, 8, _hoisted_30$1)) : (openBlock(), createElementBlock("div", _hoisted_31$1, [
1367
1520
  createElementVNode("span", {
1368
1521
  class: "fl-type fl-preview-badge",
1369
1522
  "data-kind": unref(fileKind)(__props.file.name),
1370
1523
  "aria-hidden": "true"
1371
- }, toDisplayString(unref(extOf)(__props.file.name) || "FILE"), 9, _hoisted_26$1),
1372
- 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),
1373
1526
  !__props.demo ? (openBlock(), createElementBlock("button", {
1374
1527
  key: 0,
1375
1528
  class: "fl-btn fl-btn-primary",
@@ -1379,7 +1532,7 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1379
1532
  name: "download",
1380
1533
  size: 15
1381
1534
  }), createTextVNode(" " + toDisplayString(t("download")), 1)])) : createCommentVNode("v-if", true)
1382
- ]))], 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);
1383
1536
  };
1384
1537
  }
1385
1538
  });
@@ -1406,9 +1559,9 @@ function toNodes(items, parent, password) {
1406
1559
  zipPath: [...parent.zipPath, item.name]
1407
1560
  }));
1408
1561
  }
1409
- function sortTree(nodes, key, ascending) {
1410
- nodes.sort((a, b) => compareNodes(a, b, key, ascending));
1411
- 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);
1412
1565
  }
1413
1566
  /** Loaded files at or under a node. */
1414
1567
  function descendantFiles(node) {
@@ -1724,24 +1877,28 @@ const _hoisted_52 = {
1724
1877
  const _hoisted_53 = ["data-kind"];
1725
1878
  const _hoisted_54 = { class: "fl-label" };
1726
1879
  const _hoisted_55 = ["onClick"];
1727
- const _hoisted_56 = { class: "fl-meta" };
1728
- 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 = {
1729
1886
  key: 5,
1730
1887
  class: "fl-count"
1731
1888
  };
1732
- const _hoisted_58 = ["id"];
1733
- const _hoisted_59 = [
1889
+ const _hoisted_59 = ["id"];
1890
+ const _hoisted_60 = [
1734
1891
  "aria-label",
1735
1892
  "title",
1736
1893
  "onClick"
1737
1894
  ];
1738
- const _hoisted_60 = {
1895
+ const _hoisted_61 = {
1739
1896
  key: 8,
1740
1897
  class: "fl-loading",
1741
1898
  "aria-hidden": "true"
1742
1899
  };
1743
- const _hoisted_61 = ["onSubmit", "onKeydown"];
1744
- const _hoisted_62 = [
1900
+ const _hoisted_62 = ["onSubmit", "onKeydown"];
1901
+ const _hoisted_63 = [
1745
1902
  "onUpdate:modelValue",
1746
1903
  "data-unlock",
1747
1904
  "placeholder",
@@ -1749,40 +1906,49 @@ const _hoisted_62 = [
1749
1906
  "aria-invalid",
1750
1907
  "aria-describedby"
1751
1908
  ];
1752
- const _hoisted_63 = [
1909
+ const _hoisted_64 = [
1753
1910
  "aria-pressed",
1754
1911
  "aria-label",
1755
1912
  "title",
1756
1913
  "onClick"
1757
1914
  ];
1758
- const _hoisted_64 = ["disabled"];
1759
- const _hoisted_65 = [
1915
+ const _hoisted_65 = ["disabled"];
1916
+ const _hoisted_66 = [
1760
1917
  "aria-label",
1761
1918
  "title",
1762
1919
  "onClick"
1763
1920
  ];
1764
- const _hoisted_66 = { class: "fl-cell fl-cell-size" };
1765
- const _hoisted_67 = { class: "fl-cell fl-cell-date" };
1766
- const _hoisted_68 = { class: "fl-cell fl-cell-op" };
1767
- const _hoisted_69 = ["aria-label", "onClick"];
1768
- 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 = [
1769
1926
  "aria-label",
1770
1927
  "aria-expanded",
1771
1928
  "onClick"
1772
1929
  ];
1773
- const _hoisted_71 = [
1930
+ const _hoisted_72 = [
1774
1931
  "aria-label",
1775
1932
  "title",
1776
1933
  "onClick"
1777
1934
  ];
1778
- const _hoisted_72 = [
1935
+ const _hoisted_73 = [
1779
1936
  "aria-label",
1780
1937
  "title",
1781
1938
  "onClick"
1782
1939
  ];
1783
- const _hoisted_73 = { class: "fl-foot" };
1784
1940
  const _hoisted_74 = {
1785
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,
1786
1952
  class: "fl-toast",
1787
1953
  role: "status"
1788
1954
  };
@@ -1806,6 +1972,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1806
1972
  default: true
1807
1973
  },
1808
1974
  pageSize: { default: 200 },
1975
+ deepSearchLimit: { default: 500 },
1809
1976
  zipWarnSize: {},
1810
1977
  zipMaxSize: {},
1811
1978
  locale: { default: "zh-CN" },
@@ -1870,7 +2037,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1870
2037
  return isNetworkError(error) ? `${message} ${t("corsHint")}` : message;
1871
2038
  }
1872
2039
  function sortNodes(nodes) {
1873
- sortTree(nodes, sortKey.value, sortAsc.value);
2040
+ sortTree(nodes, sortKey.value, sortAsc.value, props.locale);
1874
2041
  }
1875
2042
  function focusUnlock(path) {
1876
2043
  nextTick(() => {
@@ -1975,7 +2142,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1975
2142
  * Load a folder's children. A password error locks the folder instead of
1976
2143
  * toasting; `prompt` also shows its same-row input (and focuses it).
1977
2144
  */
1978
- async function loadChildren(node, prompt = false) {
2145
+ async function loadChildren(node, prompt = false, quiet = false) {
1979
2146
  if (node.loaded) return true;
1980
2147
  if (node.lock) {
1981
2148
  if (prompt) startPrompt(node, prompt === "focus");
@@ -1991,7 +2158,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1991
2158
  node.expanded = false;
1992
2159
  node.lock = newLock(false, passwords.dropStale(rememberedAt) ? "stale" : "");
1993
2160
  if (prompt) startPrompt(node, prompt === "focus");
1994
- } else showToast(failureMessage("folderFailed", error));
2161
+ } else if (!quiet) showToast(failureMessage("folderFailed", error));
1995
2162
  return false;
1996
2163
  } finally {
1997
2164
  node.loading = false;
@@ -2078,6 +2245,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2078
2245
  }
2079
2246
  /** First load, or a retry after an error: shows the loading state. */
2080
2247
  async function loadRoot() {
2248
+ stopDeepSearch();
2081
2249
  refreshSeq += 1;
2082
2250
  refreshing.value = false;
2083
2251
  loadingRoot.value = true;
@@ -2101,6 +2269,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2101
2269
  */
2102
2270
  async function refresh() {
2103
2271
  if (refreshing.value || loadingRoot.value) return;
2272
+ stopDeepSearch();
2104
2273
  if (!roots.value.length || errorMsg.value || rootLock.value) {
2105
2274
  await loadRoot();
2106
2275
  return;
@@ -2200,6 +2369,124 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2200
2369
  return out;
2201
2370
  });
2202
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(" · "));
2203
2490
  const stats = computed(() => folderStats(roots.value, selected.value));
2204
2491
  const noStats = {
2205
2492
  files: 0,
@@ -2736,14 +3023,16 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2736
3023
  createElementVNode("span", _hoisted_54, [createElementVNode("span", {
2737
3024
  class: normalizeClass(["fl-name", { "fl-name-dir": node.is_dir }]),
2738
3025
  onClick: ($event) => node.lock || node.is_dir ? toggleExpand(node) : download(node)
2739
- }, 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)])]),
2740
- 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),
2741
3030
  node.lock?.prompting && node.lock.error ? (openBlock(), createElementBlock("span", {
2742
3031
  key: 6,
2743
3032
  id: `${unref(uid)}-error-${index}`,
2744
3033
  class: "fl-unlock-message",
2745
3034
  role: "alert"
2746
- }, 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),
2747
3036
  !node.lock && unref(remembered).has(node.path) ? (openBlock(), createElementBlock("button", {
2748
3037
  key: 7,
2749
3038
  class: "fl-remembered",
@@ -2754,8 +3043,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2754
3043
  }, [createVNode(_sfc_main$2, {
2755
3044
  name: "key",
2756
3045
  size: 11
2757
- }), createTextVNode(" " + toDisplayString(t("remembered")) + " · " + toDisplayString(t("forgetPassword")), 1)], 8, _hoisted_59)) : createCommentVNode("v-if", true),
2758
- 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)
2759
3048
  ], 8, _hoisted_48),
2760
3049
  node.lock?.prompting ? (openBlock(), createElementBlock("form", {
2761
3050
  key: 0,
@@ -2777,7 +3066,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2777
3066
  "aria-label": t("passwordFor", { name: node.name }),
2778
3067
  "aria-invalid": !!node.lock.error,
2779
3068
  "aria-describedby": node.lock.error ? `${unref(uid)}-error-${index}` : void 0
2780
- }, null, 8, _hoisted_62), [[vModelText, node.lock.input]]),
3069
+ }, null, 8, _hoisted_63), [[vModelText, node.lock.input]]),
2781
3070
  canRemember.value ? (openBlock(), createElementBlock("button", {
2782
3071
  key: 0,
2783
3072
  class: "fl-unlock-remember",
@@ -2786,12 +3075,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2786
3075
  "aria-label": t("rememberPassword"),
2787
3076
  title: t("rememberPassword"),
2788
3077
  onClick: ($event) => node.lock.remember = !node.lock.remember
2789
- }, [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),
2790
3079
  createElementVNode("button", {
2791
3080
  class: "fl-unlock-submit",
2792
3081
  type: "submit",
2793
3082
  disabled: node.lock.busy || !node.lock.input
2794
- }, toDisplayString(t("open")), 9, _hoisted_64),
3083
+ }, toDisplayString(t("open")), 9, _hoisted_65),
2795
3084
  createElementVNode("button", {
2796
3085
  class: "fl-unlock-cancel",
2797
3086
  type: "button",
@@ -2801,11 +3090,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2801
3090
  }, [createVNode(_sfc_main$2, {
2802
3091
  name: "close",
2803
3092
  size: 14
2804
- })], 8, _hoisted_65)
2805
- ], 40, _hoisted_61)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
2806
- createElementVNode("span", _hoisted_66, toDisplayString(node.is_dir ? "—" : unref(formatSize)(node.size)), 1),
2807
- createElementVNode("span", _hoisted_67, toDisplayString(unref(formatDate)(node.modified)), 1),
2808
- 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", {
2809
3098
  key: 0,
2810
3099
  class: "fl-action fl-arrow-end fl-lock",
2811
3100
  type: "button",
@@ -2814,7 +3103,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2814
3103
  }, [createVNode(_sfc_main$2, {
2815
3104
  name: "lock",
2816
3105
  size: 18
2817
- })], 8, _hoisted_69)) : node.is_dir ? (openBlock(), createElementBlock("button", {
3106
+ })], 8, _hoisted_70)) : node.is_dir ? (openBlock(), createElementBlock("button", {
2818
3107
  key: 1,
2819
3108
  class: normalizeClass(["fl-action fl-arrow-end", { "fl-arrow-open": node.expanded }]),
2820
3109
  type: "button",
@@ -2824,25 +3113,49 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2824
3113
  }, [createVNode(_sfc_main$2, {
2825
3114
  name: "next",
2826
3115
  size: 18
2827
- })], 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", {
2828
3117
  key: 0,
2829
3118
  class: "fl-action fl-preview-btn",
2830
3119
  type: "button",
2831
3120
  "aria-label": `${t("preview")} ${node.name}`,
2832
3121
  title: t("preview"),
2833
3122
  onClick: ($event) => preview(node)
2834
- }, [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", {
2835
3124
  class: "fl-action fl-download",
2836
3125
  type: "button",
2837
3126
  "aria-label": `${t("download")} ${node.name}`,
2838
3127
  title: t("download"),
2839
3128
  onClick: ($event) => download(node)
2840
- }, [createVNode(_sfc_main$2, { name: "download" })], 8, _hoisted_72)], 64))])
3129
+ }, [createVNode(_sfc_main$2, { name: "download" })], 8, _hoisted_73)], 64))])
2841
3130
  ], 64))
2842
3131
  ], 6))], 64);
2843
3132
  }), 128)),
2844
- 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)]),
2845
- 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),
2846
3159
  createVNode(_sfc_main$3, {
2847
3160
  open: !!zipDialog.value,
2848
3161
  title: zipDialog.value?.title ?? "",