vitepress-plugin-filelist 0.1.3 → 0.2.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.
- package/README.md +31 -0
- package/dist/client/browser/index.d.ts +17 -1
- package/dist/client/browser/index.js +121 -46
- package/dist/client/ssr/index.d.ts +17 -1
- package/dist/client/ssr/index.js +84 -6
- package/dist/client/style.css +4 -0
- package/package.json +1 -1
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 = ["
|
|
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
|
|
182
|
-
const
|
|
182
|
+
const _hoisted_27 = ["onClick"];
|
|
183
|
+
const _hoisted_28 = {
|
|
183
184
|
key: 0,
|
|
184
185
|
class: "fl-folder-icon"
|
|
185
186
|
};
|
|
186
|
-
const
|
|
187
|
+
const _hoisted_29 = {
|
|
187
188
|
key: 1,
|
|
188
189
|
class: "fl-ext-badge"
|
|
189
190
|
};
|
|
190
|
-
const
|
|
191
|
+
const _hoisted_30 = {
|
|
191
192
|
key: 2,
|
|
192
193
|
class: "fl-loading"
|
|
193
194
|
};
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
const
|
|
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 =
|
|
200
|
-
const _hoisted_36 = {
|
|
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,29 +415,40 @@ 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
|
|
425
|
+
password,
|
|
420
426
|
page: 1,
|
|
421
427
|
per_page: 0,
|
|
422
428
|
refresh: false
|
|
423
429
|
}))?.content ?? [];
|
|
424
430
|
}
|
|
431
|
+
function absoluteUrl(raw) {
|
|
432
|
+
if (!raw) return "";
|
|
433
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) return raw;
|
|
434
|
+
const root = baseUrl.value || (typeof window !== "undefined" ? window.location.origin : "");
|
|
435
|
+
try {
|
|
436
|
+
return new URL(raw, `${root}/`).href;
|
|
437
|
+
} catch {
|
|
438
|
+
return raw;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
425
441
|
async function resolveUrl(node) {
|
|
426
442
|
if (demo.value) return "";
|
|
443
|
+
if (node.raw_url) return absoluteUrl(node.raw_url);
|
|
427
444
|
const cached = downloadCache.get(node.path);
|
|
428
445
|
if (cached) return cached;
|
|
429
446
|
let url = "";
|
|
430
447
|
try {
|
|
431
|
-
url = (await request("/api/fs/get", {
|
|
448
|
+
url = absoluteUrl((await request("/api/fs/get", {
|
|
432
449
|
path: node.path,
|
|
433
|
-
password: activePassword.value
|
|
434
|
-
}))?.raw_url ?? "";
|
|
450
|
+
password: node.password || activePassword.value
|
|
451
|
+
}))?.raw_url ?? "");
|
|
435
452
|
} catch {
|
|
436
453
|
url = "";
|
|
437
454
|
}
|
|
@@ -455,7 +472,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
455
472
|
nodes.sort((a, b) => compareNodes(a, b, sortKey.value, sortAsc.value));
|
|
456
473
|
for (const node of nodes) if (node.children.length) sortTree(node.children);
|
|
457
474
|
}
|
|
458
|
-
function toNodes(items, parent, depth) {
|
|
475
|
+
function toNodes(items, parent, depth, password = "") {
|
|
459
476
|
return items.map((item) => ({
|
|
460
477
|
...item,
|
|
461
478
|
path: joinPath(parent, item.name),
|
|
@@ -463,7 +480,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
463
480
|
expanded: false,
|
|
464
481
|
loaded: false,
|
|
465
482
|
loading: false,
|
|
466
|
-
children: []
|
|
483
|
+
children: [],
|
|
484
|
+
password
|
|
467
485
|
}));
|
|
468
486
|
}
|
|
469
487
|
function setSort(key) {
|
|
@@ -472,15 +490,82 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
472
490
|
sortKey.value = key;
|
|
473
491
|
sortAsc.value = true;
|
|
474
492
|
}
|
|
475
|
-
|
|
493
|
+
if (props.sources.length) {
|
|
494
|
+
for (const root of roots.value) if (root.children.length) sortTree(root.children);
|
|
495
|
+
} else sortTree(roots.value);
|
|
496
|
+
}
|
|
497
|
+
function sourceName(path) {
|
|
498
|
+
const parts = path.split("/").filter(Boolean);
|
|
499
|
+
return parts[parts.length - 1] || path;
|
|
500
|
+
}
|
|
501
|
+
async function loadChildren(node) {
|
|
502
|
+
if (node.loaded) return true;
|
|
503
|
+
node.loading = true;
|
|
504
|
+
try {
|
|
505
|
+
node.children = toNodes(await listDir(node.path, node.password), node.path, node.depth + 1, node.password);
|
|
506
|
+
sortTree(node.children);
|
|
507
|
+
node.loaded = true;
|
|
508
|
+
return true;
|
|
509
|
+
} catch (error) {
|
|
510
|
+
showToast(t("folderFailed", { message: errorText(error) }));
|
|
511
|
+
return false;
|
|
512
|
+
} finally {
|
|
513
|
+
node.loading = false;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
async function loadSourceRoot(source) {
|
|
517
|
+
const path = source.path;
|
|
518
|
+
const password = source.password ?? "";
|
|
519
|
+
let item = {
|
|
520
|
+
name: sourceName(path),
|
|
521
|
+
size: 0,
|
|
522
|
+
is_dir: true,
|
|
523
|
+
modified: ""
|
|
524
|
+
};
|
|
525
|
+
if (demo.value) {
|
|
526
|
+
if (/\/[^/]+\.[^/]+$/.test(path)) item = {
|
|
527
|
+
...item,
|
|
528
|
+
is_dir: false,
|
|
529
|
+
modified: "2026-09-01T08:00:00+08:00"
|
|
530
|
+
};
|
|
531
|
+
} else try {
|
|
532
|
+
const data = await request("/api/fs/get", {
|
|
533
|
+
path,
|
|
534
|
+
password
|
|
535
|
+
});
|
|
536
|
+
item = {
|
|
537
|
+
...item,
|
|
538
|
+
...data
|
|
539
|
+
};
|
|
540
|
+
} catch {}
|
|
541
|
+
return {
|
|
542
|
+
...item,
|
|
543
|
+
name: source.title || item.name || sourceName(path),
|
|
544
|
+
path,
|
|
545
|
+
depth: 0,
|
|
546
|
+
expanded: source.expanded ?? props.expanded,
|
|
547
|
+
loaded: false,
|
|
548
|
+
loading: false,
|
|
549
|
+
children: [],
|
|
550
|
+
password,
|
|
551
|
+
isRoot: true
|
|
552
|
+
};
|
|
476
553
|
}
|
|
477
554
|
async function loadRoot() {
|
|
478
555
|
loadingRoot.value = true;
|
|
479
556
|
errorMsg.value = "";
|
|
480
557
|
needsPassword.value = false;
|
|
481
558
|
try {
|
|
559
|
+
if (props.sources.length) {
|
|
560
|
+
const nodes = await Promise.all(props.sources.map((source) => loadSourceRoot(source)));
|
|
561
|
+
roots.value = nodes;
|
|
562
|
+
for (const node of roots.value) if (node.is_dir && node.expanded) node.expanded = await loadChildren(node);
|
|
563
|
+
selected.value = /* @__PURE__ */ new Set();
|
|
564
|
+
downloadCache.clear();
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
482
567
|
const items = await listDir(props.path);
|
|
483
|
-
roots.value = toNodes(items, props.path, 0);
|
|
568
|
+
roots.value = toNodes(items, props.path, 0, activePassword.value);
|
|
484
569
|
sortTree(roots.value);
|
|
485
570
|
selected.value = /* @__PURE__ */ new Set();
|
|
486
571
|
downloadCache.clear();
|
|
@@ -498,19 +583,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
498
583
|
node.expanded = false;
|
|
499
584
|
return;
|
|
500
585
|
}
|
|
501
|
-
if (!node
|
|
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
|
-
}
|
|
586
|
+
if (!await loadChildren(node)) return;
|
|
514
587
|
node.expanded = true;
|
|
515
588
|
}
|
|
516
589
|
const visible = computed(() => {
|
|
@@ -735,6 +808,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
735
808
|
key: node.path,
|
|
736
809
|
class: normalizeClass(["fl-row", {
|
|
737
810
|
"fl-row-dir": node.is_dir,
|
|
811
|
+
"fl-row-root": node.isRoot,
|
|
738
812
|
"fl-row-selected": selected.value.has(node.path)
|
|
739
813
|
}])
|
|
740
814
|
}, [
|
|
@@ -746,23 +820,24 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
746
820
|
}, null, 40, _hoisted_23)]),
|
|
747
821
|
createElementVNode("span", {
|
|
748
822
|
class: "fl-cell fl-cell-name",
|
|
823
|
+
title: node.isRoot ? node.path : void 0,
|
|
749
824
|
style: normalizeStyle({ paddingLeft: `${node.depth * 22}px` })
|
|
750
825
|
}, [node.is_dir ? (openBlock(), createElementBlock("button", {
|
|
751
826
|
key: 0,
|
|
752
827
|
class: normalizeClass(["fl-arrow", { "fl-arrow-open": node.expanded }]),
|
|
753
828
|
type: "button",
|
|
754
829
|
onClick: ($event) => toggleExpand(node)
|
|
755
|
-
}, "▸", 10,
|
|
830
|
+
}, "▸", 10, _hoisted_25)) : (openBlock(), createElementBlock("span", _hoisted_26)), createElementVNode("span", {
|
|
756
831
|
class: normalizeClass(["fl-name", { "fl-name-link": node.is_dir }]),
|
|
757
832
|
onClick: ($event) => node.is_dir ? toggleExpand(node) : download(node)
|
|
758
833
|
}, [
|
|
759
|
-
node.is_dir ? (openBlock(), createElementBlock("span",
|
|
834
|
+
node.is_dir ? (openBlock(), createElementBlock("span", _hoisted_28, "📁")) : (openBlock(), createElementBlock("span", _hoisted_29, toDisplayString(unref(extOf)(node.name) || "FILE"), 1)),
|
|
760
835
|
createTextVNode(" " + toDisplayString(node.name) + " ", 1),
|
|
761
|
-
node.loading ? (openBlock(), createElementBlock("span",
|
|
762
|
-
], 10,
|
|
763
|
-
createElementVNode("span",
|
|
764
|
-
createElementVNode("span",
|
|
765
|
-
createElementVNode("span",
|
|
836
|
+
node.loading ? (openBlock(), createElementBlock("span", _hoisted_30, "…")) : createCommentVNode("v-if", true)
|
|
837
|
+
], 10, _hoisted_27)], 12, _hoisted_24),
|
|
838
|
+
createElementVNode("span", _hoisted_31, toDisplayString(node.is_dir ? "-" : unref(formatSize)(node.size)), 1),
|
|
839
|
+
createElementVNode("span", _hoisted_32, toDisplayString(unref(formatDate)(node.modified)), 1),
|
|
840
|
+
createElementVNode("span", _hoisted_33, [!node.is_dir ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [canPreview(node) ? (openBlock(), createElementBlock("button", {
|
|
766
841
|
key: 0,
|
|
767
842
|
class: "fl-download",
|
|
768
843
|
type: "button",
|
|
@@ -782,7 +857,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
782
857
|
cx: "12",
|
|
783
858
|
cy: "12",
|
|
784
859
|
r: "3"
|
|
785
|
-
})], -1)])], 8,
|
|
860
|
+
})], -1)])], 8, _hoisted_34)) : createCommentVNode("v-if", true), createElementVNode("button", {
|
|
786
861
|
class: "fl-download",
|
|
787
862
|
type: "button",
|
|
788
863
|
title: t("download"),
|
|
@@ -801,11 +876,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
801
876
|
createElementVNode("path", { d: "M12 3v12" }),
|
|
802
877
|
createElementVNode("path", { d: "m7 10 5 5 5-5" }),
|
|
803
878
|
createElementVNode("path", { d: "M5 21h14" })
|
|
804
|
-
], -1)])], 8,
|
|
879
|
+
], -1)])], 8, _hoisted_35)], 64)) : createCommentVNode("v-if", true)])
|
|
805
880
|
], 2);
|
|
806
881
|
}), 128)),
|
|
807
|
-
createElementVNode("div",
|
|
808
|
-
toast.value ? (openBlock(), createElementBlock("div",
|
|
882
|
+
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)]),
|
|
883
|
+
toast.value ? (openBlock(), createElementBlock("div", _hoisted_38, toDisplayString(toast.value), 1)) : createCommentVNode("v-if", true)
|
|
809
884
|
]);
|
|
810
885
|
};
|
|
811
886
|
}
|
|
@@ -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 };
|
package/dist/client/ssr/index.js
CHANGED
|
@@ -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
|
|
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"])}">`);
|
package/dist/client/style.css
CHANGED
package/package.json
CHANGED