vitepress-plugin-filelist 0.7.0 → 0.7.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.
@@ -102,6 +102,10 @@ interface FileListLabels {
102
102
  deepContinue: string;
103
103
  deepSkipped: string;
104
104
  deepFailed: string;
105
+ /** Beside a locked folder shown in deep search results. */
106
+ deepLocked: string;
107
+ /** The × in the filter box. */
108
+ clearFilter: string;
105
109
  /** Markdown / HTML preview: the source / rendered switch. */
106
110
  viewMode: string;
107
111
  viewSource: string;
@@ -179,6 +183,11 @@ interface FileListProps {
179
183
  * Defaults to `500`.
180
184
  */
181
185
  deepSearchLimit?: number;
186
+ /**
187
+ * `manual` (default): “Try deep search” starts deep search mode.
188
+ * `auto`: typing a keyword starts it at once (for small mounts).
189
+ */
190
+ deepSearch?: 'manual' | 'auto';
182
191
  /**
183
192
  * Browsers that must build the ZIP in memory (Firefox, Safari) ask first above
184
193
  * this many MB. Defaults to 500 (200 on touch devices); `0` never asks.
@@ -216,6 +225,7 @@ declare const _default: import("vue").DefineComponent<FileListProps, {}, {}, {},
216
225
  rememberPassword: boolean;
217
226
  pageSize: number;
218
227
  deepSearchLimit: number;
228
+ deepSearch: "manual" | "auto";
219
229
  locale: FileListLocale;
220
230
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
221
231
  declare global {
@@ -869,8 +869,8 @@ const fileListLocales = {
869
869
  forgetPassword: "忘记",
870
870
  unlockFolder: "输入 {name} 的密码",
871
871
  networkHint: "可能被跨域拦截:请检查 OpenList 的 cors.allow_origins,或 OpenList-Worker 的 ALLOW_URLS。",
872
- deepHint: "只筛选了已加载的内容",
873
- deepSearch: "深度搜索",
872
+ deepHint: "只筛选了已加载的内容,",
873
+ deepSearch: "尝试深度搜索",
874
874
  deepRunning: "正在深度搜索 · 已查找 {count} 个文件夹",
875
875
  deepStop: "停止",
876
876
  deepDone: "已搜索全部 {count} 个文件夹",
@@ -878,6 +878,8 @@ const fileListLocales = {
878
878
  deepContinue: "继续",
879
879
  deepSkipped: "跳过 {count} 个加密文件夹",
880
880
  deepFailed: "{count} 个文件夹读取失败",
881
+ deepLocked: "未搜索(需要密码)",
882
+ clearFilter: "清除筛选",
881
883
  viewMode: "显示方式",
882
884
  viewSource: "源码",
883
885
  viewRendered: "渲染",
@@ -963,8 +965,8 @@ const fileListLocales = {
963
965
  forgetPassword: "Forget",
964
966
  unlockFolder: "Enter the password for {name}",
965
967
  networkHint: "Possibly blocked by CORS: check cors.allow_origins in OpenList, or ALLOW_URLS in OpenList-Worker.",
966
- deepHint: "Only loaded folders are filtered",
967
- deepSearch: "Deep search",
968
+ deepHint: "Only loaded folders are filtered. ",
969
+ deepSearch: "Try deep search",
968
970
  deepRunning: "Searching · {count} folders checked",
969
971
  deepStop: "Stop",
970
972
  deepDone: "Searched all {count} folders",
@@ -972,6 +974,8 @@ const fileListLocales = {
972
974
  deepContinue: "Continue",
973
975
  deepSkipped: "{count} locked folders skipped",
974
976
  deepFailed: "{count} folders could not be read",
977
+ deepLocked: "Not searched (password needed)",
978
+ clearFilter: "Clear filter",
975
979
  viewMode: "View",
976
980
  viewSource: "Source",
977
981
  viewRendered: "Rendered",
@@ -1563,6 +1567,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1563
1567
  },
1564
1568
  pageSize: { default: 200 },
1565
1569
  deepSearchLimit: { default: 500 },
1570
+ deepSearch: { default: "manual" },
1566
1571
  zipWarnSize: {},
1567
1572
  zipMaxSize: {},
1568
1573
  locale: { default: "zh-CN" },
@@ -1722,6 +1727,21 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1722
1727
  zipPath: [item.is_dir ? name : item.name || sourceName(path)]
1723
1728
  };
1724
1729
  }
1730
+ /** Run at most `size` tasks at once. */
1731
+ function concurrency(size) {
1732
+ let active = 0;
1733
+ const waiting = [];
1734
+ return async (task) => {
1735
+ if (active >= size) await new Promise((resolve) => waiting.push(resolve));
1736
+ active += 1;
1737
+ try {
1738
+ return await task();
1739
+ } finally {
1740
+ active -= 1;
1741
+ waiting.shift()?.();
1742
+ }
1743
+ };
1744
+ }
1725
1745
  class RootLockedError extends Error {
1726
1746
  rememberedAt;
1727
1747
  constructor(rememberedAt) {
@@ -1733,7 +1753,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1733
1753
  * Build a fresh tree. `expandedPaths` re-opens those folders (refresh keeps
1734
1754
  * what was open); without it the configured `expanded` defaults apply.
1735
1755
  */
1736
- async function buildTree(expandedPaths) {
1756
+ async function buildTree(expandedPaths, loadedPaths) {
1737
1757
  let nodes;
1738
1758
  if (props.sources.length) nodes = await Promise.all(props.sources.map((source) => loadSourceRoot(source, expandedPaths ? expandedPaths.has(source.path) : source.expanded ?? props.expanded)));
1739
1759
  else {
@@ -1744,13 +1764,16 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1744
1764
  throw isPasswordError(error) ? new RootLockedError(rememberedAt) : error;
1745
1765
  }
1746
1766
  }
1767
+ const limit = concurrency(4);
1747
1768
  const open = async (list) => {
1748
- for (const node of list) {
1749
- const wanted = node.expanded || expandedPaths?.has(node.path);
1750
- if (!node.is_dir || node.lock || !wanted) continue;
1751
- node.expanded = await loadChildren(node, "show");
1752
- if (node.expanded && expandedPaths) await open(node.children);
1753
- }
1769
+ await Promise.all(list.map(async (node) => {
1770
+ const wanted = !!(node.expanded || expandedPaths?.has(node.path));
1771
+ const keep = !!loadedPaths?.has(node.path);
1772
+ if (!node.is_dir || node.lock || !(wanted || keep)) return;
1773
+ const ok = await limit(() => loadChildren(node, wanted ? "show" : false, !wanted));
1774
+ node.expanded = wanted && ok;
1775
+ if (ok && (expandedPaths || loadedPaths)) await open(node.children);
1776
+ }));
1754
1777
  };
1755
1778
  await open(nodes);
1756
1779
  return nodes;
@@ -1820,7 +1843,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1820
1843
  const rows = computed(() => {
1821
1844
  const out = [];
1822
1845
  const query = keyword.value.trim().toLowerCase();
1823
- const matches = (node) => !query || node.name.toLowerCase().includes(query) || node.is_dir && node.children.some(matches);
1846
+ const matches = (node) => !query || node.name.toLowerCase().includes(query) || node.is_dir && (node.children.some(matches) || deepMode.value && !!node.lock);
1824
1847
  const walk = (nodes, key, depth) => {
1825
1848
  const list = nodes.filter(matches);
1826
1849
  const limit = query ? list.length : Math.min(list.length, limitOf(key));
@@ -1874,11 +1897,16 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1874
1897
  const deep = reactive({
1875
1898
  running: false,
1876
1899
  searched: 0,
1877
- skipped: 0,
1878
1900
  failed: 0,
1879
1901
  capped: false,
1880
1902
  done: false
1881
1903
  });
1904
+ /**
1905
+ * Deep search mode: entered by “Try deep search” (or at once with
1906
+ * `deep-search="auto"`), left by clearing the filter. While on, new keywords,
1907
+ * refreshes and newly unlocked folders are searched without asking again.
1908
+ */
1909
+ const deepMode = ref(false);
1882
1910
  let deepSeq = 0;
1883
1911
  /** Folders that are known but not loaded yet (locked ones excluded). */
1884
1912
  function unloadedFolders() {
@@ -1898,27 +1926,97 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1898
1926
  if (!keyword.value.trim()) return null;
1899
1927
  if (deep.running) return "running";
1900
1928
  if (deep.capped) return "capped";
1901
- if (hasUnloaded.value) return "idle";
1929
+ if (!deepMode.value) return hasUnloaded.value ? "idle" : null;
1902
1930
  return deep.done ? "done" : null;
1903
1931
  });
1932
+ /** “Try deep search”, “Continue”, and the automatic runs of deep search mode. */
1933
+ async function deepSearch() {
1934
+ deepMode.value = true;
1935
+ const seq = ++deepSeq;
1936
+ const limit = Math.max(1, Math.floor(props.deepSearchLimit ?? 500));
1937
+ const runStart = deep.searched;
1938
+ Object.assign(deep, {
1939
+ running: true,
1940
+ capped: false,
1941
+ done: false
1942
+ });
1943
+ let wave = unloadedFolders();
1944
+ while (wave.length && seq === deepSeq) {
1945
+ const next = [];
1946
+ const queue = [...wave];
1947
+ const worker = async () => {
1948
+ for (let node = queue.shift(); node && seq === deepSeq; node = queue.shift()) {
1949
+ if (deep.searched - runStart >= limit) return;
1950
+ deep.searched += 1;
1951
+ if (!await loadChildren(node, false, true)) {
1952
+ if (!node.lock) deep.failed += 1;
1953
+ continue;
1954
+ }
1955
+ for (const child of node.children) if (child.is_dir && !child.loaded && !child.lock) next.push(child);
1956
+ }
1957
+ };
1958
+ await Promise.all(Array.from({ length: Math.min(4, queue.length) }, worker));
1959
+ if (deep.searched - runStart >= limit) break;
1960
+ wave = next;
1961
+ }
1962
+ if (seq !== deepSeq) return;
1963
+ deep.running = false;
1964
+ deep.capped = hasUnloaded.value && deep.searched - runStart >= limit;
1965
+ deep.done = !deep.capped;
1966
+ }
1967
+ /** Pause a run (internal: refresh, reload). */
1904
1968
  function stopDeepSearch() {
1905
1969
  deepSeq += 1;
1906
1970
  deep.running = false;
1907
1971
  }
1908
1972
  watch(keyword, (value) => {
1909
- if (value.trim()) return;
1973
+ if (value.trim()) {
1974
+ if (props.deepSearch === "auto") deepMode.value = true;
1975
+ return;
1976
+ }
1910
1977
  stopDeepSearch();
1978
+ deepMode.value = false;
1911
1979
  Object.assign(deep, {
1912
1980
  searched: 0,
1913
- skipped: 0,
1914
1981
  failed: 0,
1915
1982
  capped: false,
1916
1983
  done: false
1917
1984
  });
1918
1985
  });
1986
+ watch(() => deepMode.value && hasUnloaded.value && !deep.running && !deep.capped && !refreshing.value, (ready) => {
1987
+ if (ready) deepSearch();
1988
+ });
1989
+ /**
1990
+ * Counted from the tree, so a refresh that re-reads the same folders shows the
1991
+ * same numbers: folders read (or found locked), and the locked ones.
1992
+ */
1993
+ const folderCounts = computed(() => {
1994
+ const counts = {
1995
+ checked: 0,
1996
+ locked: 0
1997
+ };
1998
+ const walk = (nodes) => {
1999
+ for (const node of nodes) {
2000
+ if (!node.is_dir) continue;
2001
+ if (node.lock) {
2002
+ counts.checked += 1;
2003
+ counts.locked += 1;
2004
+ } else if (node.loaded) {
2005
+ counts.checked += 1;
2006
+ walk(node.children);
2007
+ }
2008
+ }
2009
+ };
2010
+ walk(roots.value);
2011
+ return counts;
2012
+ });
2013
+ const lockedFolders = computed(() => folderCounts.value.locked);
2014
+ function lockedInSearch(node) {
2015
+ return deepMode.value && !!node.lock && !!keyword.value.trim();
2016
+ }
1919
2017
  const deepSummary = computed(() => [
1920
- deepRow.value === "capped" ? t("deepCapped", { count: deep.searched }) : t("deepDone", { count: deep.searched }),
1921
- deep.skipped ? t("deepSkipped", { count: deep.skipped }) : "",
2018
+ t(deepRow.value === "capped" ? "deepCapped" : "deepDone", { count: folderCounts.value.checked }),
2019
+ lockedFolders.value ? t("deepSkipped", { count: lockedFolders.value }) : "",
1922
2020
  deep.failed ? t("deepFailed", { count: deep.failed }) : ""
1923
2021
  ].filter(Boolean).join(" · "));
1924
2022
  const stats = computed(() => folderStats(roots.value, selected.value));
@@ -2150,7 +2248,16 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2150
2248
  name: "search",
2151
2249
  size: 15
2152
2250
  }, null, _parent));
2153
- _push(`<input${ssrRenderAttr("value", keyword.value)} type="text"${ssrRenderAttr("placeholder", t("filter"))}${ssrRenderAttr("aria-label", t("filter"))}></label><button class="${ssrRenderClass([{ "fl-spinning": refreshing.value }, "fl-icon-btn fl-refresh"])}" type="button"${ssrIncludeBooleanAttr(refreshing.value || loadingRoot.value) ? " disabled" : ""}${ssrRenderAttr("aria-label", refreshing.value ? t("refreshing") : t("refresh"))}${ssrRenderAttr("title", refreshing.value ? t("refreshing") : t("refresh"))}>`);
2251
+ _push(`<input${ssrRenderAttr("value", keyword.value)} type="text"${ssrRenderAttr("placeholder", t("filter"))}${ssrRenderAttr("aria-label", t("filter"))}>`);
2252
+ if (keyword.value) {
2253
+ _push(`<button class="fl-search-clear" type="button"${ssrRenderAttr("aria-label", t("clearFilter"))}${ssrRenderAttr("title", t("clearFilter"))}>`);
2254
+ _push(ssrRenderComponent(_sfc_main$2, {
2255
+ name: "close",
2256
+ size: 12
2257
+ }, null, _parent));
2258
+ _push(`</button>`);
2259
+ } else _push(`<!---->`);
2260
+ _push(`</label><button class="${ssrRenderClass([{ "fl-spinning": refreshing.value }, "fl-icon-btn fl-refresh"])}" type="button"${ssrIncludeBooleanAttr(refreshing.value || loadingRoot.value) ? " disabled" : ""}${ssrRenderAttr("aria-label", refreshing.value ? t("refreshing") : t("refresh"))}${ssrRenderAttr("title", refreshing.value ? t("refreshing") : t("refresh"))}>`);
2154
2261
  _push(ssrRenderComponent(_sfc_main$2, {
2155
2262
  name: "refresh",
2156
2263
  size: 15
@@ -2251,8 +2358,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2251
2358
  _push(`<!--]--></span><span class="fl-meta">`);
2252
2359
  if (!node.is_dir) _push(`<!--[-->${ssrInterpolate(unref(formatSize)(node.size))} · ${ssrInterpolate(unref(formatDate)(node.modified))}<!--]-->`);
2253
2360
  else if (node.loaded) _push(`<!--[-->${ssrInterpolate(t("folderItems", { count: statsOf(node).files }))} · ${ssrInterpolate(unref(formatSize)(statsOf(node).bytes))}<!--]-->`);
2361
+ else if (lockedInSearch(node)) _push(`<span class="fl-locked-note">${ssrInterpolate(t("deepLocked"))}</span>`);
2254
2362
  else _push(`<!---->`);
2255
2363
  _push(`</span></span>`);
2364
+ if (lockedInSearch(node) && !node.lock?.prompting) _push(`<span class="fl-locked-note fl-locked-hint">${ssrInterpolate(t("deepLocked"))}</span>`);
2365
+ else _push(`<!---->`);
2256
2366
  if (node.is_dir && node.loaded) _push(`<span class="fl-count">${ssrInterpolate(t("folderItems", { count: statsOf(node).files }))}</span>`);
2257
2367
  else _push(`<!---->`);
2258
2368
  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>`);
@@ -2322,16 +2432,15 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
2322
2432
  });
2323
2433
  _push(`<!--]-->`);
2324
2434
  if (deepRow.value) {
2325
- _push(`<div class="fl-row fl-deep" role="status"><span class="fl-cell fl-cell-check"></span><span class="fl-cell fl-more-cell"><span class="fl-indent" aria-hidden="true"></span>`);
2326
- if (deepRow.value === "running") _push(`<!--[--><span class="fl-more-count">${ssrInterpolate(t("deepRunning", { count: deep.searched }))}</span><button class="fl-more-btn fl-more-all" type="button">${ssrInterpolate(t("deepStop"))}</button><span class="fl-deep-bar" aria-hidden="true"></span><!--]-->`);
2327
- else if (deepRow.value === "idle") _push(`<!--[--><span class="fl-more-count">${ssrInterpolate(deep.searched ? deepSummary.value : t("deepHint"))}</span><button class="fl-more-btn" type="button">${ssrInterpolate(t("deepSearch"))}</button><!--]-->`);
2328
- else {
2329
- _push(`<!--[--><span class="fl-more-count">${ssrInterpolate(deepSummary.value)}</span>`);
2330
- if (deepRow.value === "capped") _push(`<button class="fl-more-btn" type="button">${ssrInterpolate(t("deepContinue"))}</button>`);
2331
- else _push(`<!---->`);
2332
- _push(`<!--]-->`);
2333
- }
2334
- _push(`</span></div>`);
2435
+ _push(`<div class="fl-deep" role="status"><p class="fl-deep-text">`);
2436
+ if (deepRow.value === "running") _push(`<!--[-->${ssrInterpolate(t("deepRunning", { count: folderCounts.value.checked }))} · <button class="fl-deep-link" type="button">${ssrInterpolate(t("deepStop"))}</button><!--]-->`);
2437
+ else if (deepRow.value === "idle") _push(`<!--[-->${ssrInterpolate(t("deepHint"))}<button class="fl-deep-link" type="button">${ssrInterpolate(t("deepSearch"))}</button><!--]-->`);
2438
+ else if (deepRow.value === "capped") _push(`<!--[-->${ssrInterpolate(deepSummary.value)} · <button class="fl-deep-link" type="button">${ssrInterpolate(t("deepContinue"))}</button><!--]-->`);
2439
+ else _push(`<!--[-->${ssrInterpolate(deepSummary.value)}<!--]-->`);
2440
+ _push(`</p>`);
2441
+ if (deepRow.value === "running") _push(`<span class="fl-deep-bar" aria-hidden="true"></span>`);
2442
+ else _push(`<!---->`);
2443
+ _push(`</div>`);
2335
2444
  } else _push(`<!---->`);
2336
2445
  _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>`);
2337
2446
  if (toast.value) _push(`<div class="fl-toast" role="status">${ssrInterpolate(toast.value)}</div>`);
@@ -824,8 +824,7 @@
824
824
 
825
825
  /* ---------------- long folders ---------------- */
826
826
 
827
- .fl-more,
828
- .fl-deep {
827
+ .fl-more {
829
828
  background: var(--vp-c-bg-soft);
830
829
  }
831
830
 
@@ -878,8 +877,65 @@
878
877
  border-radius: 3px;
879
878
  }
880
879
 
880
+ .fl-search-clear {
881
+ display: inline-flex;
882
+ flex: none;
883
+ align-items: center;
884
+ justify-content: center;
885
+ width: 20px;
886
+ height: 20px;
887
+ padding: 0;
888
+ color: var(--vp-c-text-2);
889
+ cursor: pointer;
890
+ background: var(--vp-c-default-soft);
891
+ border: 0;
892
+ border-radius: 50%;
893
+ }
894
+
895
+ .fl-search-clear:hover {
896
+ color: var(--vp-c-text-1);
897
+ }
898
+
899
+ /* the status line closing a filtered list: centred, wraps, never overflows */
881
900
  .fl-deep {
882
901
  position: relative;
902
+ padding: 10px 16px;
903
+ text-align: center;
904
+ background: var(--vp-c-bg-soft);
905
+ border-bottom: 1px solid var(--vp-c-divider);
906
+ }
907
+
908
+ .fl-deep-text {
909
+ margin: 0;
910
+ font-size: 13px;
911
+ line-height: 1.6;
912
+ color: var(--vp-c-text-2);
913
+ }
914
+
915
+ .fl-deep-link {
916
+ padding: 0;
917
+ font: inherit;
918
+ font-weight: 600;
919
+ color: var(--vp-c-brand-1);
920
+ text-decoration: underline;
921
+ text-underline-offset: 3px;
922
+ cursor: pointer;
923
+ background: none;
924
+ border: 0;
925
+ }
926
+
927
+ .fl-deep-link:hover {
928
+ color: var(--vp-c-brand-2);
929
+ }
930
+
931
+ .fl-locked-note {
932
+ font-size: 12px;
933
+ color: var(--fl-lock-fg);
934
+ white-space: nowrap;
935
+ }
936
+
937
+ .fl-locked-hint {
938
+ margin-left: 8px;
883
939
  }
884
940
 
885
941
  .fl-deep-bar {
@@ -963,7 +1019,31 @@
963
1019
  flex: 1 1 auto;
964
1020
  width: auto;
965
1021
  height: 40px;
966
- padding: 0 12px;
1022
+ padding: 0 4px 0 12px;
1023
+ }
1024
+
1025
+ .fl-search-clear {
1026
+ width: 36px;
1027
+ height: 36px;
1028
+ background: transparent;
1029
+ }
1030
+
1031
+ .fl-deep {
1032
+ padding: 12px 16px;
1033
+ }
1034
+
1035
+ .fl-deep-text {
1036
+ font-size: 14px;
1037
+ }
1038
+
1039
+ /* a taller tap target without changing the line */
1040
+ .fl-deep-link {
1041
+ padding: 10px 2px;
1042
+ margin: -10px 0;
1043
+ }
1044
+
1045
+ .fl-locked-hint {
1046
+ display: none;
967
1047
  }
968
1048
 
969
1049
  /* 16px keeps iOS Safari from zooming in on focus */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitepress-plugin-filelist",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "packageManager": "pnpm@11.25.0",
5
5
  "type": "module",
6
6
  "description": "OpenList file browser for VitePress: tree view, batch ZIP download, i18n, password and token support.",