vitepress-plugin-filelist 0.1.3 → 0.2.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.
package/README.md CHANGED
@@ -73,6 +73,8 @@ export default {
73
73
  | --- | --- | --- | --- |
74
74
  | `base` | `string` | `''` | OpenList 站点地址;为空时进入演示模式 |
75
75
  | `path` | `string` | `'/'` | 初始目录 |
76
+ | `sources` | `FileListSource[]` | `[]` | 多根挂载;设置后忽略 `path` |
77
+ | `expanded` | `boolean` | `false` | 目录根是否默认展开 |
76
78
  | `token` | `string` | `''` | 可选只读 token;不传则以游客身份请求 |
77
79
  | `password` | `string` | `''` | 目录密码,也可以在运行时输入 |
78
80
  | `title` | `string` | `''` | 工具栏标题;为空时回退到 OpenList 的 `site_title` |
@@ -81,6 +83,35 @@ export default {
81
83
 
82
84
  同时注册了 `<FileList>` 和 `<VPFileList>` 两个组件名。
83
85
 
86
+ ## 多根挂载
87
+
88
+ ```md
89
+ <FileList
90
+ base="https://pan.example.com"
91
+ title="课程资料"
92
+ :sources="[
93
+ { path: '/课程资料/语文' },
94
+ { path: '/课程资料/数学', expanded: true },
95
+ { path: '/课程资料/说明.pdf' }
96
+ ]"
97
+ />
98
+ ```
99
+
100
+ - 目录路径挂载为根文件夹,默认折叠;`expanded` 可为全局或单个 source 打开
101
+ - 文件路径挂载为一条文件记录,直接预览/下载
102
+ - 根目录复选框与勾选、全选效果一致:选中根目录会选中其已加载的子项
103
+ - 每个 source 可单独设置 `title`、`password`
104
+ - 根节点顺序按 `sources` 顺序,子项仍按名称/大小/日期排序
105
+
106
+ ```ts
107
+ interface FileListSource {
108
+ path: string
109
+ title?: string
110
+ password?: string
111
+ expanded?: boolean
112
+ }
113
+ ```
114
+
84
115
  ## 国际化
85
116
 
86
117
  ```md
@@ -34,11 +34,25 @@ interface FileListLabels {
34
34
  batchFailed: string;
35
35
  defaultZip: string;
36
36
  }
37
+ interface FileListSource {
38
+ /** OpenList path, directory or single file. */
39
+ path: string;
40
+ /** Root label. Defaults to the last path segment. */
41
+ title?: string;
42
+ /** Per-source folder password. */
43
+ password?: string;
44
+ /** Initial expanded state for a directory source. Overrides the global `expanded` prop. */
45
+ expanded?: boolean;
46
+ }
37
47
  interface FileListProps {
38
48
  /** OpenList base URL, e.g. `https://pan.example.com`. Empty string enables demo mode. */
39
49
  base?: string;
40
50
  /** Initial directory, defaults to `/`. */
41
51
  path?: string;
52
+ /** Mount multiple paths as root nodes. When set, `path` is ignored. */
53
+ sources?: FileListSource[];
54
+ /** Expand directory roots initially. Defaults to `false`. */
55
+ expanded?: boolean;
42
56
  /** Optional read-only token. Guest access is used when omitted. */
43
57
  token?: string;
44
58
  /** Folder password. The component also asks at runtime when OpenList reports one. */
@@ -64,6 +78,8 @@ interface OpenListEntry {
64
78
  declare const _default: import("vue").DefineComponent<FileListProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<FileListProps> & Readonly<{}>, {
65
79
  base: string;
66
80
  path: string;
81
+ sources: FileListSource[];
82
+ expanded: boolean;
67
83
  token: string;
68
84
  password: string;
69
85
  title: string;
@@ -176,4 +192,4 @@ export declare function compareNodes(a: SortableNode, b: SortableNode, key: Sort
176
192
  * ```
177
193
  */
178
194
  export declare function enhanceAppWithFileList({ app }: EnhanceAppContext): void;
179
- export { _default as FileList, _default as VPFileList, type FileListLabels, type FileListLocale, type FileListProps, type OpenListEntry };
195
+ export { _default as FileList, _default as VPFileList, type FileListLabels, type FileListLocale, type FileListProps, type FileListSource, type OpenListEntry };
@@ -173,32 +173,33 @@ const _hoisted_23 = [
173
173
  ".indeterminate",
174
174
  "onChange"
175
175
  ];
176
- const _hoisted_24 = ["onClick"];
177
- const _hoisted_25 = {
176
+ const _hoisted_24 = ["title"];
177
+ const _hoisted_25 = ["onClick"];
178
+ const _hoisted_26 = {
178
179
  key: 1,
179
180
  class: "fl-arrow-placeholder"
180
181
  };
181
- const _hoisted_26 = ["onClick"];
182
- const _hoisted_27 = {
182
+ const _hoisted_27 = ["onClick"];
183
+ const _hoisted_28 = {
183
184
  key: 0,
184
185
  class: "fl-folder-icon"
185
186
  };
186
- const _hoisted_28 = {
187
+ const _hoisted_29 = {
187
188
  key: 1,
188
189
  class: "fl-ext-badge"
189
190
  };
190
- const _hoisted_29 = {
191
+ const _hoisted_30 = {
191
192
  key: 2,
192
193
  class: "fl-loading"
193
194
  };
194
- const _hoisted_30 = { class: "fl-cell fl-cell-size" };
195
- const _hoisted_31 = { class: "fl-cell fl-cell-date" };
196
- const _hoisted_32 = { class: "fl-cell fl-cell-op" };
197
- const _hoisted_33 = ["title", "onClick"];
195
+ const _hoisted_31 = { class: "fl-cell fl-cell-size" };
196
+ const _hoisted_32 = { class: "fl-cell fl-cell-date" };
197
+ const _hoisted_33 = { class: "fl-cell fl-cell-op" };
198
198
  const _hoisted_34 = ["title", "onClick"];
199
- const _hoisted_35 = { class: "fl-foot" };
200
- const _hoisted_36 = { key: 0 };
201
- const _hoisted_37 = {
199
+ const _hoisted_35 = ["title", "onClick"];
200
+ const _hoisted_36 = { class: "fl-foot" };
201
+ const _hoisted_37 = { key: 0 };
202
+ const _hoisted_38 = {
202
203
  key: 4,
203
204
  class: "fl-toast"
204
205
  };
@@ -207,6 +208,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
207
208
  props: {
208
209
  base: { default: "" },
209
210
  path: { default: "/" },
211
+ sources: { default: () => [] },
212
+ expanded: {
213
+ type: Boolean,
214
+ default: false
215
+ },
210
216
  token: { default: "" },
211
217
  password: { default: "" },
212
218
  title: { default: "" },
@@ -409,14 +415,14 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
409
415
  if (payload.code !== 200) throw new ApiError(payload.message || `HTTP ${response.status}`, payload.code ?? response.status);
410
416
  return payload.data;
411
417
  }
412
- async function listDir(path) {
418
+ async function listDir(path, password = activePassword.value) {
413
419
  if (demo.value) {
414
420
  await new Promise((resolve) => setTimeout(resolve, 220));
415
421
  return DEMO_TREE[path] ?? [];
416
422
  }
417
423
  return (await request("/api/fs/list", {
418
424
  path,
419
- password: activePassword.value,
425
+ password,
420
426
  page: 1,
421
427
  per_page: 0,
422
428
  refresh: false
@@ -424,13 +430,14 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
424
430
  }
425
431
  async function resolveUrl(node) {
426
432
  if (demo.value) return "";
433
+ if (node.raw_url) return node.raw_url;
427
434
  const cached = downloadCache.get(node.path);
428
435
  if (cached) return cached;
429
436
  let url = "";
430
437
  try {
431
438
  url = (await request("/api/fs/get", {
432
439
  path: node.path,
433
- password: activePassword.value
440
+ password: node.password || activePassword.value
434
441
  }))?.raw_url ?? "";
435
442
  } catch {
436
443
  url = "";
@@ -455,7 +462,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
455
462
  nodes.sort((a, b) => compareNodes(a, b, sortKey.value, sortAsc.value));
456
463
  for (const node of nodes) if (node.children.length) sortTree(node.children);
457
464
  }
458
- function toNodes(items, parent, depth) {
465
+ function toNodes(items, parent, depth, password = "") {
459
466
  return items.map((item) => ({
460
467
  ...item,
461
468
  path: joinPath(parent, item.name),
@@ -463,7 +470,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
463
470
  expanded: false,
464
471
  loaded: false,
465
472
  loading: false,
466
- children: []
473
+ children: [],
474
+ password
467
475
  }));
468
476
  }
469
477
  function setSort(key) {
@@ -472,15 +480,82 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
472
480
  sortKey.value = key;
473
481
  sortAsc.value = true;
474
482
  }
475
- sortTree(roots.value);
483
+ if (props.sources.length) {
484
+ for (const root of roots.value) if (root.children.length) sortTree(root.children);
485
+ } else sortTree(roots.value);
486
+ }
487
+ function sourceName(path) {
488
+ const parts = path.split("/").filter(Boolean);
489
+ return parts[parts.length - 1] || path;
490
+ }
491
+ async function loadChildren(node) {
492
+ if (node.loaded) return true;
493
+ node.loading = true;
494
+ try {
495
+ node.children = toNodes(await listDir(node.path, node.password), node.path, node.depth + 1, node.password);
496
+ sortTree(node.children);
497
+ node.loaded = true;
498
+ return true;
499
+ } catch (error) {
500
+ showToast(t("folderFailed", { message: errorText(error) }));
501
+ return false;
502
+ } finally {
503
+ node.loading = false;
504
+ }
505
+ }
506
+ async function loadSourceRoot(source) {
507
+ const path = source.path;
508
+ const password = source.password ?? "";
509
+ let item = {
510
+ name: sourceName(path),
511
+ size: 0,
512
+ is_dir: true,
513
+ modified: ""
514
+ };
515
+ if (demo.value) {
516
+ if (/\/[^/]+\.[^/]+$/.test(path)) item = {
517
+ ...item,
518
+ is_dir: false,
519
+ modified: "2026-09-01T08:00:00+08:00"
520
+ };
521
+ } else try {
522
+ const data = await request("/api/fs/get", {
523
+ path,
524
+ password
525
+ });
526
+ item = {
527
+ ...item,
528
+ ...data
529
+ };
530
+ } catch {}
531
+ return {
532
+ ...item,
533
+ name: source.title || item.name || sourceName(path),
534
+ path,
535
+ depth: 0,
536
+ expanded: source.expanded ?? props.expanded,
537
+ loaded: false,
538
+ loading: false,
539
+ children: [],
540
+ password,
541
+ isRoot: true
542
+ };
476
543
  }
477
544
  async function loadRoot() {
478
545
  loadingRoot.value = true;
479
546
  errorMsg.value = "";
480
547
  needsPassword.value = false;
481
548
  try {
549
+ if (props.sources.length) {
550
+ const nodes = await Promise.all(props.sources.map((source) => loadSourceRoot(source)));
551
+ roots.value = nodes;
552
+ for (const node of roots.value) if (node.is_dir && node.expanded) node.expanded = await loadChildren(node);
553
+ selected.value = /* @__PURE__ */ new Set();
554
+ downloadCache.clear();
555
+ return;
556
+ }
482
557
  const items = await listDir(props.path);
483
- roots.value = toNodes(items, props.path, 0);
558
+ roots.value = toNodes(items, props.path, 0, activePassword.value);
484
559
  sortTree(roots.value);
485
560
  selected.value = /* @__PURE__ */ new Set();
486
561
  downloadCache.clear();
@@ -498,19 +573,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
498
573
  node.expanded = false;
499
574
  return;
500
575
  }
501
- if (!node.loaded) {
502
- node.loading = true;
503
- try {
504
- node.children = toNodes(await listDir(node.path), node.path, node.depth + 1);
505
- sortTree(node.children);
506
- node.loaded = true;
507
- } catch (error) {
508
- showToast(t("folderFailed", { message: errorText(error) }));
509
- return;
510
- } finally {
511
- node.loading = false;
512
- }
513
- }
576
+ if (!await loadChildren(node)) return;
514
577
  node.expanded = true;
515
578
  }
516
579
  const visible = computed(() => {
@@ -735,6 +798,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
735
798
  key: node.path,
736
799
  class: normalizeClass(["fl-row", {
737
800
  "fl-row-dir": node.is_dir,
801
+ "fl-row-root": node.isRoot,
738
802
  "fl-row-selected": selected.value.has(node.path)
739
803
  }])
740
804
  }, [
@@ -746,23 +810,24 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
746
810
  }, null, 40, _hoisted_23)]),
747
811
  createElementVNode("span", {
748
812
  class: "fl-cell fl-cell-name",
813
+ title: node.isRoot ? node.path : void 0,
749
814
  style: normalizeStyle({ paddingLeft: `${node.depth * 22}px` })
750
815
  }, [node.is_dir ? (openBlock(), createElementBlock("button", {
751
816
  key: 0,
752
817
  class: normalizeClass(["fl-arrow", { "fl-arrow-open": node.expanded }]),
753
818
  type: "button",
754
819
  onClick: ($event) => toggleExpand(node)
755
- }, "▸", 10, _hoisted_24)) : (openBlock(), createElementBlock("span", _hoisted_25)), createElementVNode("span", {
820
+ }, "▸", 10, _hoisted_25)) : (openBlock(), createElementBlock("span", _hoisted_26)), createElementVNode("span", {
756
821
  class: normalizeClass(["fl-name", { "fl-name-link": node.is_dir }]),
757
822
  onClick: ($event) => node.is_dir ? toggleExpand(node) : download(node)
758
823
  }, [
759
- node.is_dir ? (openBlock(), createElementBlock("span", _hoisted_27, "📁")) : (openBlock(), createElementBlock("span", _hoisted_28, toDisplayString(unref(extOf)(node.name) || "FILE"), 1)),
824
+ node.is_dir ? (openBlock(), createElementBlock("span", _hoisted_28, "📁")) : (openBlock(), createElementBlock("span", _hoisted_29, toDisplayString(unref(extOf)(node.name) || "FILE"), 1)),
760
825
  createTextVNode(" " + toDisplayString(node.name) + " ", 1),
761
- node.loading ? (openBlock(), createElementBlock("span", _hoisted_29, "…")) : createCommentVNode("v-if", true)
762
- ], 10, _hoisted_26)], 4),
763
- createElementVNode("span", _hoisted_30, toDisplayString(node.is_dir ? "-" : unref(formatSize)(node.size)), 1),
764
- createElementVNode("span", _hoisted_31, toDisplayString(unref(formatDate)(node.modified)), 1),
765
- createElementVNode("span", _hoisted_32, [!node.is_dir ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [canPreview(node) ? (openBlock(), createElementBlock("button", {
826
+ node.loading ? (openBlock(), createElementBlock("span", _hoisted_30, "…")) : createCommentVNode("v-if", true)
827
+ ], 10, _hoisted_27)], 12, _hoisted_24),
828
+ createElementVNode("span", _hoisted_31, toDisplayString(node.is_dir ? "-" : unref(formatSize)(node.size)), 1),
829
+ createElementVNode("span", _hoisted_32, toDisplayString(unref(formatDate)(node.modified)), 1),
830
+ createElementVNode("span", _hoisted_33, [!node.is_dir ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [canPreview(node) ? (openBlock(), createElementBlock("button", {
766
831
  key: 0,
767
832
  class: "fl-download",
768
833
  type: "button",
@@ -782,7 +847,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
782
847
  cx: "12",
783
848
  cy: "12",
784
849
  r: "3"
785
- })], -1)])], 8, _hoisted_33)) : createCommentVNode("v-if", true), createElementVNode("button", {
850
+ })], -1)])], 8, _hoisted_34)) : createCommentVNode("v-if", true), createElementVNode("button", {
786
851
  class: "fl-download",
787
852
  type: "button",
788
853
  title: t("download"),
@@ -801,11 +866,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
801
866
  createElementVNode("path", { d: "M12 3v12" }),
802
867
  createElementVNode("path", { d: "m7 10 5 5 5-5" }),
803
868
  createElementVNode("path", { d: "M5 21h14" })
804
- ], -1)])], 8, _hoisted_34)], 64)) : createCommentVNode("v-if", true)])
869
+ ], -1)])], 8, _hoisted_35)], 64)) : createCommentVNode("v-if", true)])
805
870
  ], 2);
806
871
  }), 128)),
807
- createElementVNode("div", _hoisted_35, [createElementVNode("span", null, toDisplayString(t("loaded", { count: allFiles.value.length })) + " · " + toDisplayString(t("selected", { count: selected.value.size })), 1), unloadedFolders.value ? (openBlock(), createElementBlock("span", _hoisted_36, " · " + toDisplayString(t("unloaded", { count: unloadedFolders.value })), 1)) : createCommentVNode("v-if", true)]),
808
- toast.value ? (openBlock(), createElementBlock("div", _hoisted_37, toDisplayString(toast.value), 1)) : createCommentVNode("v-if", true)
872
+ createElementVNode("div", _hoisted_36, [createElementVNode("span", null, toDisplayString(t("loaded", { count: allFiles.value.length })) + " · " + toDisplayString(t("selected", { count: selected.value.size })), 1), unloadedFolders.value ? (openBlock(), createElementBlock("span", _hoisted_37, " · " + toDisplayString(t("unloaded", { count: unloadedFolders.value })), 1)) : createCommentVNode("v-if", true)]),
873
+ toast.value ? (openBlock(), createElementBlock("div", _hoisted_38, toDisplayString(toast.value), 1)) : createCommentVNode("v-if", true)
809
874
  ]);
810
875
  };
811
876
  }
@@ -34,11 +34,25 @@ interface FileListLabels {
34
34
  batchFailed: string;
35
35
  defaultZip: string;
36
36
  }
37
+ interface FileListSource {
38
+ /** OpenList path, directory or single file. */
39
+ path: string;
40
+ /** Root label. Defaults to the last path segment. */
41
+ title?: string;
42
+ /** Per-source folder password. */
43
+ password?: string;
44
+ /** Initial expanded state for a directory source. Overrides the global `expanded` prop. */
45
+ expanded?: boolean;
46
+ }
37
47
  interface FileListProps {
38
48
  /** OpenList base URL, e.g. `https://pan.example.com`. Empty string enables demo mode. */
39
49
  base?: string;
40
50
  /** Initial directory, defaults to `/`. */
41
51
  path?: string;
52
+ /** Mount multiple paths as root nodes. When set, `path` is ignored. */
53
+ sources?: FileListSource[];
54
+ /** Expand directory roots initially. Defaults to `false`. */
55
+ expanded?: boolean;
42
56
  /** Optional read-only token. Guest access is used when omitted. */
43
57
  token?: string;
44
58
  /** Folder password. The component also asks at runtime when OpenList reports one. */
@@ -64,6 +78,8 @@ interface OpenListEntry {
64
78
  declare const _default: import("vue").DefineComponent<FileListProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<FileListProps> & Readonly<{}>, {
65
79
  base: string;
66
80
  path: string;
81
+ sources: FileListSource[];
82
+ expanded: boolean;
67
83
  token: string;
68
84
  password: string;
69
85
  title: string;
@@ -176,4 +192,4 @@ export declare function compareNodes(a: SortableNode, b: SortableNode, key: Sort
176
192
  * ```
177
193
  */
178
194
  export declare function enhanceAppWithFileList({ app }: EnhanceAppContext): void;
179
- export { _default as FileList, _default as VPFileList, type FileListLabels, type FileListLocale, type FileListProps, type OpenListEntry };
195
+ export { _default as FileList, _default as VPFileList, type FileListLabels, type FileListLocale, type FileListProps, type FileListSource, type OpenListEntry };
@@ -131,6 +131,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
131
131
  props: {
132
132
  base: { default: "" },
133
133
  path: { default: "/" },
134
+ sources: { default: () => [] },
135
+ expanded: {
136
+ type: Boolean,
137
+ default: false
138
+ },
134
139
  token: { default: "" },
135
140
  password: { default: "" },
136
141
  title: { default: "" },
@@ -167,6 +172,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
167
172
  const batchBusy = ref(false);
168
173
  const batchProgress = ref(0);
169
174
  const downloadCache = /* @__PURE__ */ new Map();
175
+ let toastTimer;
176
+ function showToast(msg) {
177
+ toast.value = msg;
178
+ clearTimeout(toastTimer);
179
+ toastTimer = setTimeout(() => toast.value = "", 3200);
180
+ }
170
181
  function errorText(error) {
171
182
  return error instanceof Error ? error.message : String(error);
172
183
  }
@@ -327,14 +338,14 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
327
338
  if (payload.code !== 200) throw new ApiError(payload.message || `HTTP ${response.status}`, payload.code ?? response.status);
328
339
  return payload.data;
329
340
  }
330
- async function listDir(path) {
341
+ async function listDir(path, password = activePassword.value) {
331
342
  if (demo.value) {
332
343
  await new Promise((resolve) => setTimeout(resolve, 220));
333
344
  return DEMO_TREE[path] ?? [];
334
345
  }
335
346
  return (await request("/api/fs/list", {
336
347
  path,
337
- password: activePassword.value,
348
+ password,
338
349
  page: 1,
339
350
  per_page: 0,
340
351
  refresh: false
@@ -353,7 +364,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
353
364
  nodes.sort((a, b) => compareNodes(a, b, sortKey.value, sortAsc.value));
354
365
  for (const node of nodes) if (node.children.length) sortTree(node.children);
355
366
  }
356
- function toNodes(items, parent, depth) {
367
+ function toNodes(items, parent, depth, password = "") {
357
368
  return items.map((item) => ({
358
369
  ...item,
359
370
  path: joinPath(parent, item.name),
@@ -361,16 +372,82 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
361
372
  expanded: false,
362
373
  loaded: false,
363
374
  loading: false,
364
- children: []
375
+ children: [],
376
+ password
365
377
  }));
366
378
  }
379
+ function sourceName(path) {
380
+ const parts = path.split("/").filter(Boolean);
381
+ return parts[parts.length - 1] || path;
382
+ }
383
+ async function loadChildren(node) {
384
+ if (node.loaded) return true;
385
+ node.loading = true;
386
+ try {
387
+ node.children = toNodes(await listDir(node.path, node.password), node.path, node.depth + 1, node.password);
388
+ sortTree(node.children);
389
+ node.loaded = true;
390
+ return true;
391
+ } catch (error) {
392
+ showToast(t("folderFailed", { message: errorText(error) }));
393
+ return false;
394
+ } finally {
395
+ node.loading = false;
396
+ }
397
+ }
398
+ async function loadSourceRoot(source) {
399
+ const path = source.path;
400
+ const password = source.password ?? "";
401
+ let item = {
402
+ name: sourceName(path),
403
+ size: 0,
404
+ is_dir: true,
405
+ modified: ""
406
+ };
407
+ if (demo.value) {
408
+ if (/\/[^/]+\.[^/]+$/.test(path)) item = {
409
+ ...item,
410
+ is_dir: false,
411
+ modified: "2026-09-01T08:00:00+08:00"
412
+ };
413
+ } else try {
414
+ const data = await request("/api/fs/get", {
415
+ path,
416
+ password
417
+ });
418
+ item = {
419
+ ...item,
420
+ ...data
421
+ };
422
+ } catch {}
423
+ return {
424
+ ...item,
425
+ name: source.title || item.name || sourceName(path),
426
+ path,
427
+ depth: 0,
428
+ expanded: source.expanded ?? props.expanded,
429
+ loaded: false,
430
+ loading: false,
431
+ children: [],
432
+ password,
433
+ isRoot: true
434
+ };
435
+ }
367
436
  async function loadRoot() {
368
437
  loadingRoot.value = true;
369
438
  errorMsg.value = "";
370
439
  needsPassword.value = false;
371
440
  try {
441
+ if (props.sources.length) {
442
+ const nodes = await Promise.all(props.sources.map((source) => loadSourceRoot(source)));
443
+ roots.value = nodes;
444
+ for (const node of roots.value) if (node.is_dir && node.expanded) node.expanded = await loadChildren(node);
445
+ selected.value = /* @__PURE__ */ new Set();
446
+ downloadCache.clear();
447
+ return;
448
+ }
372
449
  const items = await listDir(props.path);
373
- roots.value = toNodes(items, props.path, 0);
450
+ roots.value = toNodes(items, props.path, 0, activePassword.value);
374
451
  sortTree(roots.value);
375
452
  selected.value = /* @__PURE__ */ new Set();
376
453
  downloadCache.clear();
@@ -454,8 +531,9 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
454
531
  ssrRenderList(visible.value, (node) => {
455
532
  _push(`<div class="${ssrRenderClass([{
456
533
  "fl-row-dir": node.is_dir,
534
+ "fl-row-root": node.isRoot,
457
535
  "fl-row-selected": selected.value.has(node.path)
458
- }, "fl-row"])}"><span class="fl-cell fl-cell-check"><input type="checkbox"${ssrIncludeBooleanAttr(isChecked(node)) ? " checked" : ""}${ssrRenderAttr("indeterminate", isIndeterminate(node))}></span><span class="fl-cell fl-cell-name" style="${ssrRenderStyle({ paddingLeft: `${node.depth * 22}px` })}">`);
536
+ }, "fl-row"])}"><span class="fl-cell fl-cell-check"><input type="checkbox"${ssrIncludeBooleanAttr(isChecked(node)) ? " checked" : ""}${ssrRenderAttr("indeterminate", isIndeterminate(node))}></span><span class="fl-cell fl-cell-name"${ssrRenderAttr("title", node.isRoot ? node.path : void 0)} style="${ssrRenderStyle({ paddingLeft: `${node.depth * 22}px` })}">`);
459
537
  if (node.is_dir) _push(`<button type="button" class="${ssrRenderClass([{ "fl-arrow-open": node.expanded }, "fl-arrow"])}">▸</button>`);
460
538
  else _push(`<span class="fl-arrow-placeholder"></span>`);
461
539
  _push(`<span class="${ssrRenderClass([{ "fl-name-link": node.is_dir }, "fl-name"])}">`);
@@ -125,6 +125,10 @@
125
125
  background: var(--vp-c-bg-soft);
126
126
  }
127
127
 
128
+ .fl-row-root .fl-name {
129
+ font-weight: 600;
130
+ }
131
+
128
132
  .fl-row-selected {
129
133
  background: var(--vp-c-brand-soft);
130
134
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitepress-plugin-filelist",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
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.",