vitepress-plugin-filelist 0.6.0 → 0.6.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
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
- ✅ 勾选文件;选中文件夹会自动加载并选中其下所有层级的文件
|
|
9
9
|
- 📦 批量打包 ZIP(基于 `client-zip`),带进度、可取消;Chrome / Edge 边下载边写入磁盘,不受大小限制;保留目录结构,不同目录的同名文件不会互相覆盖
|
|
10
10
|
- 📄 大目录每个文件夹先显示 200 条,可“显示更多 / 全部显示”
|
|
11
|
-
- ⬇️ 单文件下载(`fs/get` 的 `raw_url`,回退 `/d
|
|
11
|
+
- ⬇️ 单文件下载(`fs/get` 的 `raw_url`,回退 `/d/`):交给浏览器下载;OpenList 自己的 `/p/`、`/d/` 链接在当前页直接下载、不打开新标签页,存储直链在新标签页打开
|
|
12
12
|
- 👁️ 弹窗预览(不跳转):图片、音视频、文本、PDF;Office 等其它类型显示文件信息与下载
|
|
13
13
|
- 🔐 目录密码输入,可选只读 token
|
|
14
14
|
- 🔎 当前树过滤,按名称 / 大小 / 日期排序
|
|
@@ -135,6 +135,7 @@ interface FileListSource {
|
|
|
135
135
|
- Chrome / Edge(含较新的 Android Chrome):点“下载 ZIP”先弹出系统“另存为”窗口,之后边下载边写入,内存占用很小,不受大小限制
|
|
136
136
|
- Firefox、Safari、iPhone:浏览器只能先把文件放进内存再打包。按已选总大小判断:超过 `zipWarnSize` 先确认,超过 `zipMaxSize` 不打包并建议分批
|
|
137
137
|
- 打包期间操作条显示进度(大小与文件数),可随时取消;勾选暂时锁定。任一文件失败会整体取消并提示文件名
|
|
138
|
+
- 同时取 4 个文件(顺序不变),减少每个文件的网络等待;进度每 100 ms 更新一次
|
|
138
139
|
- ZIP 只存储不压缩(网盘文件多为已压缩格式),支持超过 4 GB(Zip64)
|
|
139
140
|
|
|
140
141
|
## 大目录
|
|
@@ -92,6 +92,8 @@ interface FileListLabels {
|
|
|
92
92
|
unlockFolder: string;
|
|
93
93
|
/** @deprecated Network failures now append `corsHint`. */
|
|
94
94
|
networkHint: string;
|
|
95
|
+
/** Single file handed to the browser's download manager. `{name}` */
|
|
96
|
+
downloadStarted: string;
|
|
95
97
|
/** Batch progress detail. `{done}` `{total}` sizes, `{files}` `{count}` file counts */
|
|
96
98
|
batchProgress: string;
|
|
97
99
|
/** Batch finished. `{name}` `{count}` `{size}` */
|
|
@@ -329,6 +329,7 @@ function createOpenListApi(base, token) {
|
|
|
329
329
|
return url;
|
|
330
330
|
},
|
|
331
331
|
directUrl,
|
|
332
|
+
isAttachmentUrl: (url) => !!root && (url.startsWith(`${root}/p/`) || url.startsWith(`${root}/d/`)),
|
|
332
333
|
clearCache(path) {
|
|
333
334
|
if (!path) urls.clear();
|
|
334
335
|
else for (const key of [...urls.keys()]) if (isWithin(key, path)) urls.delete(key);
|
|
@@ -517,6 +518,7 @@ function createDemoApi(delay = 220) {
|
|
|
517
518
|
}),
|
|
518
519
|
fileUrl: async () => "",
|
|
519
520
|
directUrl: () => "",
|
|
521
|
+
isAttachmentUrl: () => false,
|
|
520
522
|
clearCache: () => {}
|
|
521
523
|
};
|
|
522
524
|
}
|
|
@@ -594,12 +596,21 @@ const _sfc_main$3 = /*@__PURE__*/ defineComponent({
|
|
|
594
596
|
}
|
|
595
597
|
});
|
|
596
598
|
// src/client/download.ts
|
|
597
|
-
/**
|
|
598
|
-
|
|
599
|
+
/**
|
|
600
|
+
* Hand a file URL to the browser's own download manager.
|
|
601
|
+
*
|
|
602
|
+
* `sameTab` is for links known to answer as attachments (OpenList `/p/`, `/d/`):
|
|
603
|
+
* the browser downloads and the page stays put, with no tab flashing open.
|
|
604
|
+
* Other links (a storage provider's direct URL may display inline) open in a
|
|
605
|
+
* new tab so the document page is never replaced. `download` also keeps a
|
|
606
|
+
* same-origin link out of the VitePress router and names the file.
|
|
607
|
+
*/
|
|
608
|
+
function openUrl(url, options) {
|
|
599
609
|
const anchor = document.createElement("a");
|
|
600
610
|
anchor.href = url;
|
|
601
|
-
anchor.
|
|
611
|
+
anchor.download = options.name;
|
|
602
612
|
anchor.rel = "noopener";
|
|
613
|
+
if (!options.sameTab) anchor.target = "_blank";
|
|
603
614
|
document.body.appendChild(anchor);
|
|
604
615
|
anchor.click();
|
|
605
616
|
anchor.remove();
|
|
@@ -641,10 +652,14 @@ async function pickZipFile(name) {
|
|
|
641
652
|
}
|
|
642
653
|
}
|
|
643
654
|
/**
|
|
644
|
-
* The ZIP as a stream: files are fetched
|
|
655
|
+
* The ZIP as a stream: files are fetched a few at a time and passed through
|
|
645
656
|
* as they arrive, stored without compression (most shared files are already
|
|
646
657
|
* compressed). Clashing entry names get ` (2)`, ` (3)`...
|
|
647
658
|
*/
|
|
659
|
+
/** Files fetched at once: hides per-file latency, stays under the browser's 6 per host. */
|
|
660
|
+
const CONCURRENCY = 4;
|
|
661
|
+
/** Progress updates at most this often (ms), so the bar does not re-render per chunk. */
|
|
662
|
+
const PROGRESS_INTERVAL = 100;
|
|
648
663
|
async function zipStream(files, options) {
|
|
649
664
|
const { makeZip } = await import("client-zip");
|
|
650
665
|
const progress = {
|
|
@@ -653,12 +668,28 @@ async function zipStream(files, options) {
|
|
|
653
668
|
files: 0,
|
|
654
669
|
totalFiles: files.length
|
|
655
670
|
};
|
|
656
|
-
|
|
657
|
-
report()
|
|
671
|
+
let reportedAt = 0;
|
|
672
|
+
const report = (force = false) => {
|
|
673
|
+
const now = Date.now();
|
|
674
|
+
if (!force && now - reportedAt < PROGRESS_INTERVAL) return;
|
|
675
|
+
reportedAt = now;
|
|
676
|
+
options.onProgress({ ...progress });
|
|
677
|
+
};
|
|
678
|
+
report(true);
|
|
658
679
|
async function* entries() {
|
|
659
680
|
const used = /* @__PURE__ */ new Set();
|
|
681
|
+
const ahead = [];
|
|
682
|
+
let started = 0;
|
|
683
|
+
const startNext = () => {
|
|
684
|
+
const file = files[started++];
|
|
685
|
+
const response = options.urlOf(file).then((url) => fetch(url, { signal: options.signal }));
|
|
686
|
+
response.catch(() => {});
|
|
687
|
+
ahead.push(response);
|
|
688
|
+
};
|
|
689
|
+
while (started < files.length && ahead.length < CONCURRENCY) startNext();
|
|
660
690
|
for (const file of files) {
|
|
661
|
-
const response = await
|
|
691
|
+
const response = await ahead.shift();
|
|
692
|
+
if (started < files.length) startNext();
|
|
662
693
|
if (!response.ok || !response.body) throw new Error(`${file.name}:HTTP ${response.status}`);
|
|
663
694
|
const counted = response.body.pipeThrough(new TransformStream({
|
|
664
695
|
transform(chunk, controller) {
|
|
@@ -668,7 +699,7 @@ async function zipStream(files, options) {
|
|
|
668
699
|
},
|
|
669
700
|
flush() {
|
|
670
701
|
progress.files += 1;
|
|
671
|
-
report();
|
|
702
|
+
report(progress.files === progress.totalFiles);
|
|
672
703
|
}
|
|
673
704
|
}));
|
|
674
705
|
const modified = new Date(file.modified);
|
|
@@ -868,6 +899,7 @@ const fileListLocales = {
|
|
|
868
899
|
forgetPassword: "忘记",
|
|
869
900
|
unlockFolder: "输入 {name} 的密码",
|
|
870
901
|
networkHint: "可能被跨域拦截:请检查 OpenList 的 cors.allow_origins,或 OpenList-Worker 的 ALLOW_URLS。",
|
|
902
|
+
downloadStarted: "已交给浏览器下载:{name}",
|
|
871
903
|
batchProgress: "{done} / {total} · {files} / {count} 个文件",
|
|
872
904
|
batchDone: "已打包 {name}({count} 个文件,{size})",
|
|
873
905
|
batchCanceled: "已取消下载",
|
|
@@ -945,6 +977,7 @@ const fileListLocales = {
|
|
|
945
977
|
forgetPassword: "Forget",
|
|
946
978
|
unlockFolder: "Enter the password for {name}",
|
|
947
979
|
networkHint: "Possibly blocked by CORS: check cors.allow_origins in OpenList, or ALLOW_URLS in OpenList-Worker.",
|
|
980
|
+
downloadStarted: "Downloading {name} in your browser",
|
|
948
981
|
batchProgress: "{done} / {total} · {files} / {count} files",
|
|
949
982
|
batchDone: "Saved {name} ({count} files, {size})",
|
|
950
983
|
batchCanceled: "Download cancelled",
|
|
@@ -2244,7 +2277,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
2244
2277
|
return;
|
|
2245
2278
|
}
|
|
2246
2279
|
try {
|
|
2247
|
-
|
|
2280
|
+
const url = await fileUrlOf(node);
|
|
2281
|
+
openUrl(url, {
|
|
2282
|
+
sameTab: api.value.isAttachmentUrl(url),
|
|
2283
|
+
name: node.name
|
|
2284
|
+
});
|
|
2285
|
+
showToast(t("downloadStarted", { name: node.name }));
|
|
2248
2286
|
} catch (error) {
|
|
2249
2287
|
showToast(t("downloadFailed", { message: errorText(error) }));
|
|
2250
2288
|
}
|
|
@@ -92,6 +92,8 @@ interface FileListLabels {
|
|
|
92
92
|
unlockFolder: string;
|
|
93
93
|
/** @deprecated Network failures now append `corsHint`. */
|
|
94
94
|
networkHint: string;
|
|
95
|
+
/** Single file handed to the browser's download manager. `{name}` */
|
|
96
|
+
downloadStarted: string;
|
|
95
97
|
/** Batch progress detail. `{done}` `{total}` sizes, `{files}` `{count}` file counts */
|
|
96
98
|
batchProgress: string;
|
|
97
99
|
/** Batch finished. `{name}` `{count}` `{size}` */
|
package/dist/client/ssr/index.js
CHANGED
|
@@ -329,6 +329,7 @@ function createOpenListApi(base, token) {
|
|
|
329
329
|
return url;
|
|
330
330
|
},
|
|
331
331
|
directUrl,
|
|
332
|
+
isAttachmentUrl: (url) => !!root && (url.startsWith(`${root}/p/`) || url.startsWith(`${root}/d/`)),
|
|
332
333
|
clearCache(path) {
|
|
333
334
|
if (!path) urls.clear();
|
|
334
335
|
else for (const key of [...urls.keys()]) if (isWithin(key, path)) urls.delete(key);
|
|
@@ -517,6 +518,7 @@ function createDemoApi(delay = 220) {
|
|
|
517
518
|
}),
|
|
518
519
|
fileUrl: async () => "",
|
|
519
520
|
directUrl: () => "",
|
|
521
|
+
isAttachmentUrl: () => false,
|
|
520
522
|
clearCache: () => {}
|
|
521
523
|
};
|
|
522
524
|
}
|
|
@@ -575,12 +577,21 @@ _sfc_main$3.setup = (props, ctx) => {
|
|
|
575
577
|
return _sfc_setup$3 ? _sfc_setup$3(props, ctx) : void 0;
|
|
576
578
|
};
|
|
577
579
|
// src/client/download.ts
|
|
578
|
-
/**
|
|
579
|
-
|
|
580
|
+
/**
|
|
581
|
+
* Hand a file URL to the browser's own download manager.
|
|
582
|
+
*
|
|
583
|
+
* `sameTab` is for links known to answer as attachments (OpenList `/p/`, `/d/`):
|
|
584
|
+
* the browser downloads and the page stays put, with no tab flashing open.
|
|
585
|
+
* Other links (a storage provider's direct URL may display inline) open in a
|
|
586
|
+
* new tab so the document page is never replaced. `download` also keeps a
|
|
587
|
+
* same-origin link out of the VitePress router and names the file.
|
|
588
|
+
*/
|
|
589
|
+
function openUrl(url, options) {
|
|
580
590
|
const anchor = document.createElement("a");
|
|
581
591
|
anchor.href = url;
|
|
582
|
-
anchor.
|
|
592
|
+
anchor.download = options.name;
|
|
583
593
|
anchor.rel = "noopener";
|
|
594
|
+
if (!options.sameTab) anchor.target = "_blank";
|
|
584
595
|
document.body.appendChild(anchor);
|
|
585
596
|
anchor.click();
|
|
586
597
|
anchor.remove();
|
|
@@ -600,10 +611,14 @@ function zipFileName(title) {
|
|
|
600
611
|
return `${title.replace(/[\\/:*?"<>|]/g, "_")}.zip`;
|
|
601
612
|
}
|
|
602
613
|
/**
|
|
603
|
-
* The ZIP as a stream: files are fetched
|
|
614
|
+
* The ZIP as a stream: files are fetched a few at a time and passed through
|
|
604
615
|
* as they arrive, stored without compression (most shared files are already
|
|
605
616
|
* compressed). Clashing entry names get ` (2)`, ` (3)`...
|
|
606
617
|
*/
|
|
618
|
+
/** Files fetched at once: hides per-file latency, stays under the browser's 6 per host. */
|
|
619
|
+
const CONCURRENCY = 4;
|
|
620
|
+
/** Progress updates at most this often (ms), so the bar does not re-render per chunk. */
|
|
621
|
+
const PROGRESS_INTERVAL = 100;
|
|
607
622
|
async function zipStream(files, options) {
|
|
608
623
|
const { makeZip } = await import("client-zip");
|
|
609
624
|
const progress = {
|
|
@@ -612,12 +627,28 @@ async function zipStream(files, options) {
|
|
|
612
627
|
files: 0,
|
|
613
628
|
totalFiles: files.length
|
|
614
629
|
};
|
|
615
|
-
|
|
616
|
-
report()
|
|
630
|
+
let reportedAt = 0;
|
|
631
|
+
const report = (force = false) => {
|
|
632
|
+
const now = Date.now();
|
|
633
|
+
if (!force && now - reportedAt < PROGRESS_INTERVAL) return;
|
|
634
|
+
reportedAt = now;
|
|
635
|
+
options.onProgress({ ...progress });
|
|
636
|
+
};
|
|
637
|
+
report(true);
|
|
617
638
|
async function* entries() {
|
|
618
639
|
const used = /* @__PURE__ */ new Set();
|
|
640
|
+
const ahead = [];
|
|
641
|
+
let started = 0;
|
|
642
|
+
const startNext = () => {
|
|
643
|
+
const file = files[started++];
|
|
644
|
+
const response = options.urlOf(file).then((url) => fetch(url, { signal: options.signal }));
|
|
645
|
+
response.catch(() => {});
|
|
646
|
+
ahead.push(response);
|
|
647
|
+
};
|
|
648
|
+
while (started < files.length && ahead.length < CONCURRENCY) startNext();
|
|
619
649
|
for (const file of files) {
|
|
620
|
-
const response = await
|
|
650
|
+
const response = await ahead.shift();
|
|
651
|
+
if (started < files.length) startNext();
|
|
621
652
|
if (!response.ok || !response.body) throw new Error(`${file.name}:HTTP ${response.status}`);
|
|
622
653
|
const counted = response.body.pipeThrough(new TransformStream({
|
|
623
654
|
transform(chunk, controller) {
|
|
@@ -627,7 +658,7 @@ async function zipStream(files, options) {
|
|
|
627
658
|
},
|
|
628
659
|
flush() {
|
|
629
660
|
progress.files += 1;
|
|
630
|
-
report();
|
|
661
|
+
report(progress.files === progress.totalFiles);
|
|
631
662
|
}
|
|
632
663
|
}));
|
|
633
664
|
const modified = new Date(file.modified);
|
|
@@ -821,6 +852,7 @@ const fileListLocales = {
|
|
|
821
852
|
forgetPassword: "忘记",
|
|
822
853
|
unlockFolder: "输入 {name} 的密码",
|
|
823
854
|
networkHint: "可能被跨域拦截:请检查 OpenList 的 cors.allow_origins,或 OpenList-Worker 的 ALLOW_URLS。",
|
|
855
|
+
downloadStarted: "已交给浏览器下载:{name}",
|
|
824
856
|
batchProgress: "{done} / {total} · {files} / {count} 个文件",
|
|
825
857
|
batchDone: "已打包 {name}({count} 个文件,{size})",
|
|
826
858
|
batchCanceled: "已取消下载",
|
|
@@ -898,6 +930,7 @@ const fileListLocales = {
|
|
|
898
930
|
forgetPassword: "Forget",
|
|
899
931
|
unlockFolder: "Enter the password for {name}",
|
|
900
932
|
networkHint: "Possibly blocked by CORS: check cors.allow_origins in OpenList, or ALLOW_URLS in OpenList-Worker.",
|
|
933
|
+
downloadStarted: "Downloading {name} in your browser",
|
|
901
934
|
batchProgress: "{done} / {total} · {files} / {count} files",
|
|
902
935
|
batchDone: "Saved {name} ({count} files, {size})",
|
|
903
936
|
batchCanceled: "Download cancelled",
|
|
@@ -1707,7 +1740,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1707
1740
|
return;
|
|
1708
1741
|
}
|
|
1709
1742
|
try {
|
|
1710
|
-
|
|
1743
|
+
const url = await fileUrlOf(node);
|
|
1744
|
+
openUrl(url, {
|
|
1745
|
+
sameTab: api.value.isAttachmentUrl(url),
|
|
1746
|
+
name: node.name
|
|
1747
|
+
});
|
|
1748
|
+
showToast(t("downloadStarted", { name: node.name }));
|
|
1711
1749
|
} catch (error) {
|
|
1712
1750
|
showToast(t("downloadFailed", { message: errorText(error) }));
|
|
1713
1751
|
}
|
package/package.json
CHANGED