vitepress-plugin-filelist 0.7.1 → 0.8.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 +18 -300
- package/dist/client/browser/index.d.ts +15 -2
- package/dist/client/browser/index.js +385 -134
- package/dist/client/ssr/index.d.ts +15 -2
- package/dist/client/ssr/index.js +217 -28
- package/dist/client/style.css +165 -34
- package/package.json +2 -2
|
@@ -136,12 +136,25 @@ interface FileListLabels {
|
|
|
136
136
|
/** `{limit}` */
|
|
137
137
|
zipBlockText: string;
|
|
138
138
|
gotIt: string;
|
|
139
|
-
/** Last row of a long folder. `{count}` */
|
|
139
|
+
/** Last row of a long folder: how many are not shown yet. `{count}` */
|
|
140
140
|
moreRemaining: string;
|
|
141
|
+
/** Pinned folder row: `{shown}` of `{total}`. */
|
|
142
|
+
moreShown: string;
|
|
143
|
+
/** “Show all”, short form for narrow lists. */
|
|
144
|
+
showAllShort: string;
|
|
145
|
+
/** Show the next page. `{count}` */
|
|
146
|
+
showNext: string;
|
|
147
|
+
/** Show the next page, short form for narrow lists. `{count}` */
|
|
148
|
+
showNextShort: string;
|
|
149
|
+
/** No longer shown (“Show next” replaces it); kept for existing overrides. */
|
|
141
150
|
showMore: string;
|
|
142
151
|
showAll: string;
|
|
143
152
|
/** Fewer than a page left. `{count}` */
|
|
144
153
|
showRest: string;
|
|
154
|
+
/** Pinned folder row: back to the first page. */
|
|
155
|
+
showLess: string;
|
|
156
|
+
/** Pinned folder row once everything is shown. `{count}` */
|
|
157
|
+
allShown: string;
|
|
145
158
|
}
|
|
146
159
|
interface FileListSource {
|
|
147
160
|
/** OpenList path, directory or single file. */
|
|
@@ -174,7 +187,7 @@ interface FileListProps {
|
|
|
174
187
|
*/
|
|
175
188
|
rememberPassword?: boolean;
|
|
176
189
|
/**
|
|
177
|
-
* Rows shown per folder before “Show more”. Defaults to `
|
|
190
|
+
* Rows shown per folder before “Show more”. Defaults to `15`; `0` shows
|
|
178
191
|
* everything. Data is still loaded in full; only rendering is limited.
|
|
179
192
|
*/
|
|
180
193
|
pageSize?: number;
|
package/dist/client/ssr/index.js
CHANGED
|
@@ -353,6 +353,56 @@ function createOpenListApi(base, token) {
|
|
|
353
353
|
}
|
|
354
354
|
};
|
|
355
355
|
}
|
|
356
|
+
// src/client/demoSamples.ts
|
|
357
|
+
const SAMPLE_FOLDER = "/示例文件";
|
|
358
|
+
const SAMPLES = {
|
|
359
|
+
"使用说明.md": {
|
|
360
|
+
type: "text/markdown",
|
|
361
|
+
body: "# 使用说明\n\n这是 **vitepress-plugin-filelist** 演示数据自带的 Markdown 文件。\n\n## 试一试\n\n- 点右上角“渲染”,按 VitePress 正文样式显示\n- 用 ← / → 切换到同一目录里的其它文件\n- 勾选“示例文件”文件夹,点“下载 ZIP”打包\n\n| 文件 | 类型 |\n| --- | --- |\n| 讲义.pdf | PDF |\n| 课程海报.svg | 图片 |\n| 网页示例.html | 网页 |\n\n> 接入 OpenList 后,这里显示的就是网盘里的真实文件。\n\n更多说明见 [文档站](https://nrjycyd.github.io/vitepress-plugin-filelist/)。\n"
|
|
362
|
+
},
|
|
363
|
+
"网页示例.html": {
|
|
364
|
+
type: "text/html",
|
|
365
|
+
body: "<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<title>网页示例</title>\n<style>\n body { font-family: system-ui, sans-serif; margin: 24px; line-height: 1.6; color: #1f2937; }\n h1 { color: #3451b2; }\n .card { padding: 12px 16px; border: 1px solid #d1d5db; border-radius: 8px; }\n</style>\n</head>\n<body>\n<h1>网页示例</h1>\n<p>切换到“渲染”后,这个网页在沙盒中显示:带样式,但脚本不会运行。</p>\n<div class=\"card\">下面的按钮由脚本控制,在沙盒里点了没有反应。</div>\n<p><button onclick=\"alert('脚本运行了')\">点我</button></p>\n<script>document.body.dataset.ran = 'yes'<\/script>\n</body>\n</html>\n"
|
|
366
|
+
},
|
|
367
|
+
"课程海报.svg": {
|
|
368
|
+
type: "image/svg+xml",
|
|
369
|
+
body: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"800\" height=\"450\" viewBox=\"0 0 800 450\">\n<defs><linearGradient id=\"g\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\"><stop offset=\"0\" stop-color=\"#3451b2\"/><stop offset=\"1\" stop-color=\"#5672cd\"/></linearGradient></defs>\n<rect width=\"800\" height=\"450\" fill=\"url(#g)\"/>\n<circle cx=\"660\" cy=\"90\" r=\"120\" fill=\"#ffffff\" opacity=\"0.08\"/>\n<circle cx=\"120\" cy=\"420\" r=\"160\" fill=\"#ffffff\" opacity=\"0.06\"/>\n<text x=\"64\" y=\"190\" font-family=\"system-ui, sans-serif\" font-size=\"56\" font-weight=\"700\" fill=\"#ffffff\">课程海报</text>\n<text x=\"64\" y=\"250\" font-family=\"system-ui, sans-serif\" font-size=\"26\" fill=\"#dfe5ff\">线性代数 · 2026 秋季学期</text>\n<text x=\"64\" y=\"380\" font-family=\"system-ui, sans-serif\" font-size=\"18\" fill=\"#dfe5ff\">vitepress-plugin-filelist 演示图片</text>\n</svg>\n"
|
|
370
|
+
},
|
|
371
|
+
"讲义.pdf": {
|
|
372
|
+
type: "application/pdf",
|
|
373
|
+
body: "%PDF-1.4\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>\nendobj\n4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n5 0 obj\n<< /Length 257 >>\nstream\nBT\n/F1 20 Tf\n72 760 Td\n(FileList sample PDF) Tj\n/F1 12 Tf\n0 -22 Td\n() Tj\n0 -22 Td\n(This small PDF ships with the demo data of) Tj\n0 -22 Td\n(vitepress-plugin-filelist, so preview, download) Tj\n0 -22 Td\n(and ZIP can be tried without an OpenList server.) Tj\nET\nendstream\nendobj\nxref\n0 6\n0000000000 65535 f \n0000000009 00000 n \n0000000058 00000 n \n0000000115 00000 n \n0000000241 00000 n \n0000000311 00000 n \ntrailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n619\n%%EOF\n"
|
|
374
|
+
},
|
|
375
|
+
"课程表.txt": {
|
|
376
|
+
type: "text/plain",
|
|
377
|
+
body: "课程表(演示文本)\n\n周一 08:00 线性代数 教 301\n周三 10:00 线性代数习题课 教 205\n周五 14:00 答疑 图书馆 2 楼\n\n纯文本文件只读取前 1 MB,按原样显示。\n"
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
const SAMPLE_NAMES = Object.keys(SAMPLES);
|
|
381
|
+
/** Size in bytes of a sample, as UTF-8. */
|
|
382
|
+
function sampleSize(name) {
|
|
383
|
+
return new TextEncoder().encode(SAMPLES[name].body).byteLength;
|
|
384
|
+
}
|
|
385
|
+
function nameOf(path) {
|
|
386
|
+
const name = path.slice(6);
|
|
387
|
+
return path.startsWith(`/示例文件/`) && name in SAMPLES ? name : void 0;
|
|
388
|
+
}
|
|
389
|
+
/** Whether a demo path is one of the sample files (which have a real URL). */
|
|
390
|
+
function isSample(path) {
|
|
391
|
+
return nameOf(path) !== void 0;
|
|
392
|
+
}
|
|
393
|
+
const urls = /* @__PURE__ */ new Map();
|
|
394
|
+
/** A blob: URL for a sample file, `''` for any other path or outside a browser. */
|
|
395
|
+
function sampleUrl(path) {
|
|
396
|
+
const name = nameOf(path);
|
|
397
|
+
if (!name || typeof URL.createObjectURL !== "function") return "";
|
|
398
|
+
let url = urls.get(name);
|
|
399
|
+
if (!url) {
|
|
400
|
+
const { type, body } = SAMPLES[name];
|
|
401
|
+
url = URL.createObjectURL(new Blob([body], { type: `${type};charset=utf-8` }));
|
|
402
|
+
urls.set(name, url);
|
|
403
|
+
}
|
|
404
|
+
return url;
|
|
405
|
+
}
|
|
356
406
|
// src/client/demo.ts
|
|
357
407
|
/** Built-in sample data, used when no `base` is given. */
|
|
358
408
|
const TREE = {
|
|
@@ -392,8 +442,32 @@ const TREE = {
|
|
|
392
442
|
size: 0,
|
|
393
443
|
is_dir: true,
|
|
394
444
|
modified: "2026-10-20T18:00:00+08:00"
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: "示例文件",
|
|
448
|
+
size: 0,
|
|
449
|
+
is_dir: true,
|
|
450
|
+
modified: "2026-11-11T09:00:00+08:00"
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
name: "照片(260 张)",
|
|
454
|
+
size: 0,
|
|
455
|
+
is_dir: true,
|
|
456
|
+
modified: "2026-10-01T12:00:00+08:00"
|
|
395
457
|
}
|
|
396
458
|
],
|
|
459
|
+
[SAMPLE_FOLDER]: SAMPLE_NAMES.map((name) => ({
|
|
460
|
+
name,
|
|
461
|
+
size: sampleSize(name),
|
|
462
|
+
is_dir: false,
|
|
463
|
+
modified: "2026-11-11T09:00:00+08:00"
|
|
464
|
+
})),
|
|
465
|
+
"/照片(260 张)": Array.from({ length: 260 }, (_, index) => ({
|
|
466
|
+
name: `IMG_${String(index + 1).padStart(4, "0")}.jpg`,
|
|
467
|
+
size: 2e6 + index * 7919 % 15e5,
|
|
468
|
+
is_dir: false,
|
|
469
|
+
modified: "2026-10-01T12:00:00+08:00"
|
|
470
|
+
})),
|
|
397
471
|
"/assignments": [{
|
|
398
472
|
name: "线代作业参考答案",
|
|
399
473
|
size: 0,
|
|
@@ -514,7 +588,7 @@ function guard(path, password) {
|
|
|
514
588
|
function parentOf(path) {
|
|
515
589
|
return path.slice(0, path.lastIndexOf("/")) || "/";
|
|
516
590
|
}
|
|
517
|
-
/** Demo OpenList: same API, no network; files have
|
|
591
|
+
/** Demo OpenList: same API, no network; only the sample files have a URL. */
|
|
518
592
|
function createDemoApi(delay = 220) {
|
|
519
593
|
return {
|
|
520
594
|
async list(path, password) {
|
|
@@ -533,12 +607,14 @@ function createDemoApi(delay = 220) {
|
|
|
533
607
|
title: "",
|
|
534
608
|
iframePreviews: ""
|
|
535
609
|
}),
|
|
536
|
-
fileUrl: async () =>
|
|
610
|
+
fileUrl: async (file) => sampleUrl(file.path),
|
|
537
611
|
directUrl: () => "",
|
|
538
|
-
isAttachmentUrl: () =>
|
|
612
|
+
isAttachmentUrl: (url) => url.startsWith("blob:"),
|
|
539
613
|
clearCache: () => {}
|
|
540
614
|
};
|
|
541
615
|
}
|
|
616
|
+
/** Whether a demo file can really be previewed, downloaded and zipped. */
|
|
617
|
+
const isDemoFile = isSample;
|
|
542
618
|
// src/client/ConfirmDialog.vue
|
|
543
619
|
const _sfc_main$3 = /*@__PURE__*/ defineComponent({
|
|
544
620
|
__name: "ConfirmDialog",
|
|
@@ -575,8 +651,8 @@ const _sfc_main$3 = /*@__PURE__*/ defineComponent({
|
|
|
575
651
|
"aria-label": __props.title
|
|
576
652
|
}, _attrs))}>`);
|
|
577
653
|
if (__props.open) {
|
|
578
|
-
_push(`<div class="fl-confirm-panel"><
|
|
579
|
-
if (__props.tip) _push(`<
|
|
654
|
+
_push(`<div class="fl-confirm-panel"><div class="fl-confirm-title">${ssrInterpolate(__props.title)}</div><div class="fl-confirm-text">${ssrInterpolate(__props.text)}</div>`);
|
|
655
|
+
if (__props.tip) _push(`<div class="fl-confirm-text">${ssrInterpolate(__props.tip)}</div>`);
|
|
580
656
|
else _push(`<!---->`);
|
|
581
657
|
_push(`<div class="fl-confirm-actions">`);
|
|
582
658
|
if (__props.confirmLabel) _push(`<button class="fl-btn" type="button">${ssrInterpolate(__props.cancelLabel)}</button>`);
|
|
@@ -754,6 +830,14 @@ const ICONS = {
|
|
|
754
830
|
stroke: 2.2,
|
|
755
831
|
paths: ["m15 6-6 6 6 6"]
|
|
756
832
|
},
|
|
833
|
+
down: {
|
|
834
|
+
stroke: 2.4,
|
|
835
|
+
paths: ["m6 9 6 6 6-6"]
|
|
836
|
+
},
|
|
837
|
+
up: {
|
|
838
|
+
stroke: 2.4,
|
|
839
|
+
paths: ["m18 15-6-6-6 6"]
|
|
840
|
+
},
|
|
757
841
|
zoomIn: {
|
|
758
842
|
stroke: 2,
|
|
759
843
|
paths: [
|
|
@@ -901,7 +985,13 @@ const fileListLocales = {
|
|
|
901
985
|
moreRemaining: "还有 {count} 项",
|
|
902
986
|
showMore: "显示更多",
|
|
903
987
|
showAll: "全部显示",
|
|
904
|
-
showRest: "显示剩余 {count} 项"
|
|
988
|
+
showRest: "显示剩余 {count} 项",
|
|
989
|
+
showLess: "收起",
|
|
990
|
+
allShown: "已显示全部 {count} 项",
|
|
991
|
+
moreShown: "已显示 {shown} / {total} 项",
|
|
992
|
+
showAllShort: "全部",
|
|
993
|
+
showNext: "再显示 {count} 项",
|
|
994
|
+
showNextShort: "+{count}"
|
|
905
995
|
},
|
|
906
996
|
"en-US": {
|
|
907
997
|
allFiles: "All files",
|
|
@@ -997,7 +1087,13 @@ const fileListLocales = {
|
|
|
997
1087
|
moreRemaining: "{count} more",
|
|
998
1088
|
showMore: "Show more",
|
|
999
1089
|
showAll: "Show all",
|
|
1000
|
-
showRest: "Show the remaining {count}"
|
|
1090
|
+
showRest: "Show the remaining {count}",
|
|
1091
|
+
showLess: "Show less",
|
|
1092
|
+
allShown: "All {count} shown",
|
|
1093
|
+
moreShown: "Showing {shown} of {total}",
|
|
1094
|
+
showAllShort: "All",
|
|
1095
|
+
showNext: "Show {count} more",
|
|
1096
|
+
showNextShort: "+{count}"
|
|
1001
1097
|
}
|
|
1002
1098
|
};
|
|
1003
1099
|
function formatLabel(template, values = {}) {
|
|
@@ -1319,19 +1415,19 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
|
|
|
1319
1415
|
else if (mode.value === "audio") _push(`<audio class="fl-preview-audio"${ssrRenderAttr("src", __props.src)} controls preload="metadata"></audio>`);
|
|
1320
1416
|
else if (mode.value === "text") {
|
|
1321
1417
|
_push(`<div class="${ssrRenderClass([{ "fl-preview-rendered": renderAs.value && view.value === "rendered" }, "fl-preview-text"])}">`);
|
|
1322
|
-
if (truncated.value) _push(`<
|
|
1418
|
+
if (truncated.value) _push(`<div class="fl-preview-note">${ssrInterpolate(t("textTruncated", { size: unref(formatSize)(__props.maxTextBytes) }))}</div>`);
|
|
1323
1419
|
else _push(`<!---->`);
|
|
1324
1420
|
if (renderAs.value === "markdown" && view.value === "rendered") {
|
|
1325
1421
|
_push(`<!--[--><!-- sanitised by renderMarkdown (DOMPurify) -->`);
|
|
1326
1422
|
if (renderedHtml.value) _push(`<div class="vp-doc fl-md">${renderedHtml.value ?? ""}</div>`);
|
|
1327
1423
|
else _push(`<div class="fl-preview-status" role="status">${ssrInterpolate(t("loading"))}</div>`);
|
|
1328
1424
|
_push(`<!--]-->`);
|
|
1329
|
-
} else if (renderAs.value === "html" && view.value === "rendered") _push(`<!--[--><
|
|
1425
|
+
} else if (renderAs.value === "html" && view.value === "rendered") _push(`<!--[--><div class="fl-preview-note">${ssrInterpolate(t("htmlSandboxNote"))}</div><!-- empty sandbox: no scripts, no forms, no popups, an opaque origin --><iframe class="fl-preview-html" sandbox="" referrerpolicy="no-referrer"${ssrRenderAttr("srcdoc", textContent.value)}${ssrRenderAttr("title", __props.file.name)}></iframe><!--]-->`);
|
|
1330
1426
|
else _push(`<pre>${ssrInterpolate(textContent.value)}</pre>`);
|
|
1331
1427
|
_push(`</div>`);
|
|
1332
1428
|
} else if (mode.value === "pdf") _push(`<iframe class="fl-preview-frame"${ssrRenderAttr("src", __props.viewer || blobUrl.value)}${ssrRenderAttr("title", __props.file.name)}></iframe>`);
|
|
1333
1429
|
else {
|
|
1334
|
-
_push(`<div class="fl-preview-info"><span class="fl-type fl-preview-badge"${ssrRenderAttr("data-kind", unref(fileKind)(__props.file.name))} aria-hidden="true">${ssrInterpolate(unref(extOf)(__props.file.name) || "FILE")}</span><
|
|
1430
|
+
_push(`<div class="fl-preview-info"><span class="fl-type fl-preview-badge"${ssrRenderAttr("data-kind", unref(fileKind)(__props.file.name))} aria-hidden="true">${ssrInterpolate(unref(extOf)(__props.file.name) || "FILE")}</span><div class="fl-preview-message">${ssrInterpolate(infoMessage.value)}</div>`);
|
|
1335
1431
|
if (!__props.demo) {
|
|
1336
1432
|
_push(`<button class="fl-btn fl-btn-primary" type="button">`);
|
|
1337
1433
|
_push(ssrRenderComponent(_sfc_main$2, {
|
|
@@ -1565,7 +1661,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1565
1661
|
type: Boolean,
|
|
1566
1662
|
default: true
|
|
1567
1663
|
},
|
|
1568
|
-
pageSize: { default:
|
|
1664
|
+
pageSize: { default: 15 },
|
|
1569
1665
|
deepSearchLimit: { default: 500 },
|
|
1570
1666
|
deepSearch: { default: "manual" },
|
|
1571
1667
|
zipWarnSize: {},
|
|
@@ -1831,18 +1927,23 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1831
1927
|
applySort(key, direction !== "desc");
|
|
1832
1928
|
}
|
|
1833
1929
|
});
|
|
1834
|
-
/**
|
|
1930
|
+
/** The row closing a long folder while its children are cut at `pageSize`. */
|
|
1835
1931
|
const shown = reactive(/* @__PURE__ */ new Map());
|
|
1932
|
+
function pageSizeOf() {
|
|
1933
|
+
return Math.max(0, Math.floor(props.pageSize ?? 15));
|
|
1934
|
+
}
|
|
1836
1935
|
function limitOf(key) {
|
|
1837
|
-
const size =
|
|
1936
|
+
const size = pageSizeOf();
|
|
1838
1937
|
return size ? shown.get(key) ?? size : Number.POSITIVE_INFINITY;
|
|
1839
1938
|
}
|
|
1840
1939
|
function isMore(row) {
|
|
1841
1940
|
return "more" in row;
|
|
1842
1941
|
}
|
|
1843
|
-
const
|
|
1942
|
+
const layout = computed(() => {
|
|
1844
1943
|
const out = [];
|
|
1944
|
+
const long = [];
|
|
1845
1945
|
const query = keyword.value.trim().toLowerCase();
|
|
1946
|
+
const size = pageSizeOf();
|
|
1846
1947
|
const matches = (node) => !query || node.name.toLowerCase().includes(query) || node.is_dir && (node.children.some(matches) || deepMode.value && !!node.lock);
|
|
1847
1948
|
const walk = (nodes, key, depth) => {
|
|
1848
1949
|
const list = nodes.filter(matches);
|
|
@@ -1857,10 +1958,70 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1857
1958
|
depth,
|
|
1858
1959
|
remaining: list.length - limit
|
|
1859
1960
|
});
|
|
1961
|
+
let end = out.length - 1;
|
|
1962
|
+
while (end >= 0 && isMore(out[end])) end -= 1;
|
|
1963
|
+
const last = out[end];
|
|
1964
|
+
if (!query && size > 0 && limit > size && last && !isMore(last)) long.push({
|
|
1965
|
+
key,
|
|
1966
|
+
depth: depth - 1,
|
|
1967
|
+
shown: limit,
|
|
1968
|
+
total: list.length,
|
|
1969
|
+
last: last.path
|
|
1970
|
+
});
|
|
1860
1971
|
};
|
|
1861
1972
|
walk(roots.value, props.sources.length ? "" : props.path, 0);
|
|
1862
|
-
return
|
|
1973
|
+
return {
|
|
1974
|
+
rows: out,
|
|
1975
|
+
long
|
|
1976
|
+
};
|
|
1977
|
+
});
|
|
1978
|
+
const rows = computed(() => layout.value.rows);
|
|
1979
|
+
const pinned = ref(null);
|
|
1980
|
+
/** Height of the sticky selection bar above the pinned row (narrow lists only). */
|
|
1981
|
+
const pinOffset = ref(0);
|
|
1982
|
+
function rowElement(path) {
|
|
1983
|
+
return [...rootEl.value?.querySelectorAll("[data-path]") ?? []].find((el) => el.dataset.path === path);
|
|
1984
|
+
}
|
|
1985
|
+
/** The pinned folder's name: its own, or for a single path's top level, the list's title. */
|
|
1986
|
+
const pinnedName = computed(() => {
|
|
1987
|
+
const folder = pinned.value;
|
|
1988
|
+
if (!folder) return "";
|
|
1989
|
+
if (folder.depth < 0) return props.title || folder.key.split("/").filter(Boolean).pop() || "/";
|
|
1990
|
+
const find = (nodes) => {
|
|
1991
|
+
for (const node of nodes) {
|
|
1992
|
+
if (node.path === folder.key) return node;
|
|
1993
|
+
const inner = isWithin(folder.key, node.path) ? find(node.children) : void 0;
|
|
1994
|
+
if (inner) return inner;
|
|
1995
|
+
}
|
|
1996
|
+
};
|
|
1997
|
+
return find(roots.value)?.name ?? "";
|
|
1863
1998
|
});
|
|
1999
|
+
/** Pin the innermost long folder whose own row has scrolled away while its files are still on screen. */
|
|
2000
|
+
function updatePin() {
|
|
2001
|
+
const root = rootEl.value;
|
|
2002
|
+
if (!root) return;
|
|
2003
|
+
const bar = root.querySelector(".fl-bar");
|
|
2004
|
+
pinOffset.value = bar && getComputedStyle(bar).position === "sticky" ? bar.offsetHeight : 0;
|
|
2005
|
+
const top = stickyTop.value + pinOffset.value;
|
|
2006
|
+
const rowHeight = root.querySelector(".fl-head")?.offsetHeight ?? 40;
|
|
2007
|
+
let found = null;
|
|
2008
|
+
for (const folder of layout.value.long) {
|
|
2009
|
+
const head = folder.depth < 0 ? root.querySelector(".fl-head") : rowElement(folder.key);
|
|
2010
|
+
const last = rowElement(folder.last);
|
|
2011
|
+
if (!head || !last) continue;
|
|
2012
|
+
if (head.getBoundingClientRect().bottom <= top && last.getBoundingClientRect().bottom > top + rowHeight && (!found || folder.depth > found.depth)) found = folder;
|
|
2013
|
+
}
|
|
2014
|
+
pinned.value = found;
|
|
2015
|
+
}
|
|
2016
|
+
let pinFrame = 0;
|
|
2017
|
+
function schedulePin() {
|
|
2018
|
+
if (pinFrame) return;
|
|
2019
|
+
pinFrame = requestAnimationFrame(() => {
|
|
2020
|
+
pinFrame = 0;
|
|
2021
|
+
updatePin();
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
watch(layout, () => void nextTick(schedulePin));
|
|
1864
2025
|
const visible = computed(() => rows.value.filter((row) => !isMore(row)));
|
|
1865
2026
|
/** A name split around the filter keyword, for highlighting. */
|
|
1866
2027
|
function nameParts(name) {
|
|
@@ -2047,7 +2208,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
2047
2208
|
}
|
|
2048
2209
|
async function download(node) {
|
|
2049
2210
|
if (node.is_dir) return;
|
|
2050
|
-
if (demo.value) {
|
|
2211
|
+
if (demo.value && !isDemoFile(node.path)) {
|
|
2051
2212
|
showToast(t("demoDownload", { name: node.name }));
|
|
2052
2213
|
return;
|
|
2053
2214
|
}
|
|
@@ -2151,7 +2312,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
2151
2312
|
previewViewer.value = "";
|
|
2152
2313
|
previewError.value = "";
|
|
2153
2314
|
previewResolving.value = false;
|
|
2154
|
-
if (demo.value || !canPreview(node)) return;
|
|
2315
|
+
if (demo.value && !isDemoFile(node.path) || !canPreview(node)) return;
|
|
2155
2316
|
previewResolving.value = true;
|
|
2156
2317
|
try {
|
|
2157
2318
|
const url = await fileUrlOf(node);
|
|
@@ -2200,10 +2361,15 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
2200
2361
|
loadSettings();
|
|
2201
2362
|
measureStickyTop();
|
|
2202
2363
|
window.addEventListener("resize", measureStickyTop, { passive: true });
|
|
2364
|
+
window.addEventListener("scroll", schedulePin, { passive: true });
|
|
2365
|
+
window.addEventListener("resize", schedulePin, { passive: true });
|
|
2203
2366
|
});
|
|
2204
2367
|
onBeforeUnmount(() => {
|
|
2205
2368
|
zipAbort?.abort();
|
|
2206
2369
|
window.removeEventListener("resize", measureStickyTop);
|
|
2370
|
+
window.removeEventListener("scroll", schedulePin);
|
|
2371
|
+
window.removeEventListener("resize", schedulePin);
|
|
2372
|
+
cancelAnimationFrame(pinFrame);
|
|
2207
2373
|
clearTimeout(toastTimer);
|
|
2208
2374
|
});
|
|
2209
2375
|
return (_ctx, _push, _parent, _attrs) => {
|
|
@@ -2287,7 +2453,30 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
2287
2453
|
ssrRenderList(sortOptions.value, (option) => {
|
|
2288
2454
|
_push(`<option${ssrRenderAttr("value", option.value)}${ssrIncludeBooleanAttr(Array.isArray(sortValue.value) ? ssrLooseContain(sortValue.value, option.value) : ssrLooseEqual(sortValue.value, option.value)) ? " selected" : ""}>${ssrInterpolate(option.label)}</option>`);
|
|
2289
2455
|
});
|
|
2290
|
-
_push(`<!--]--></select></label></span></div>`);
|
|
2456
|
+
_push(`<!--]--></select></label></span></div><!-- While scrolling through a long folder, its row stays at the top with “show less”. --><div class="fl-pin-anchor" style="${ssrRenderStyle({ "--fl-pin-offset": `${pinOffset.value}px` })}">`);
|
|
2457
|
+
if (pinned.value) {
|
|
2458
|
+
_push(`<div class="fl-pin" style="${ssrRenderStyle({ "--fl-depth": Math.max(pinned.value.depth, 0) })}"><span class="fl-cell fl-cell-check"></span><span class="fl-cell fl-cell-name">`);
|
|
2459
|
+
if (pinned.value.depth >= 0) _push(`<span class="fl-indent" aria-hidden="true"></span>`);
|
|
2460
|
+
else _push(`<!---->`);
|
|
2461
|
+
if (pinned.value.depth >= 0) {
|
|
2462
|
+
_push(`<span class="fl-pin-arrow" aria-hidden="true">`);
|
|
2463
|
+
_push(ssrRenderComponent(_sfc_main$2, {
|
|
2464
|
+
name: "next",
|
|
2465
|
+
size: 14
|
|
2466
|
+
}, null, _parent));
|
|
2467
|
+
_push(`</span>`);
|
|
2468
|
+
} else _push(`<!---->`);
|
|
2469
|
+
_push(`<svg class="fl-folder-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path></svg><span class="fl-pin-name">${ssrInterpolate(pinnedName.value)}</span><span class="fl-pin-count">${ssrInterpolate(pinned.value.shown < pinned.value.total ? t("moreShown", {
|
|
2470
|
+
shown: pinned.value.shown,
|
|
2471
|
+
total: pinned.value.total
|
|
2472
|
+
}) : t("allShown", { count: pinned.value.total }))}</span></span><button class="fl-pin-less" type="button">${ssrInterpolate(t("showLess"))}`);
|
|
2473
|
+
_push(ssrRenderComponent(_sfc_main$2, {
|
|
2474
|
+
name: "up",
|
|
2475
|
+
size: 12
|
|
2476
|
+
}, null, _parent));
|
|
2477
|
+
_push(`</button></div>`);
|
|
2478
|
+
} else _push(`<!---->`);
|
|
2479
|
+
_push(`</div>`);
|
|
2291
2480
|
if (loadingRoot.value) _push(`<div class="fl-status">${ssrInterpolate(t("loading"))}</div>`);
|
|
2292
2481
|
else if (rootLock.value) {
|
|
2293
2482
|
_push(`<form class="${ssrRenderClass([{ "fl-unlock-error": rootLock.value.error }, "fl-unlock-root"])}">`);
|
|
@@ -2307,9 +2496,9 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
2307
2496
|
_push(`<button class="fl-unlock-submit" type="submit"${ssrIncludeBooleanAttr(rootLock.value.busy || !rootLock.value.input) ? " disabled" : ""}>${ssrInterpolate(t("open"))}</button></span></form>`);
|
|
2308
2497
|
} else if (errorMsg.value) {
|
|
2309
2498
|
_push(`<div class="fl-status fl-error">${ssrInterpolate(t("loadFailed", { message: errorMsg.value }))} `);
|
|
2310
|
-
if (/guest/i.test(errorMsg.value)) _push(`<
|
|
2499
|
+
if (/guest/i.test(errorMsg.value)) _push(`<div class="fl-hint">${ssrInterpolate(t("guestHint"))}</div>`);
|
|
2311
2500
|
else _push(`<!---->`);
|
|
2312
|
-
if (unref(isNetworkError)(errorMsg.value)) _push(`<
|
|
2501
|
+
if (unref(isNetworkError)(errorMsg.value)) _push(`<div class="fl-hint">${ssrInterpolate(t("corsHint"))}</div>`);
|
|
2313
2502
|
else _push(`<!---->`);
|
|
2314
2503
|
_push(`<button class="fl-btn" type="button">${ssrInterpolate(t("retry"))}</button></div>`);
|
|
2315
2504
|
} else if (visible.value.length === 0) _push(`<div class="fl-status">${ssrInterpolate(t("empty"))}</div>`);
|
|
@@ -2318,10 +2507,10 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
2318
2507
|
ssrRenderList(rows.value, (node, index) => {
|
|
2319
2508
|
_push(`<!--[-->`);
|
|
2320
2509
|
if (isMore(node)) {
|
|
2321
|
-
_push(`<div class="fl-row fl-more" style="${ssrRenderStyle({ "--fl-depth": node.depth })}"><span class="fl-cell fl-cell-check"></span><span class="fl-cell fl-more-cell"
|
|
2322
|
-
if (node.remaining > (__props.pageSize || 0)) _push(`<!--[--><button class="fl-more-
|
|
2323
|
-
else _push(`<button class="fl-more-
|
|
2324
|
-
_push(`</span></div>`);
|
|
2510
|
+
_push(`<div class="fl-row fl-more" style="${ssrRenderStyle({ "--fl-depth": node.depth })}"><span class="fl-cell fl-cell-check"></span><span class="fl-cell fl-more-cell"><!-- Reads as one more row of the folder: “⋯” in the type column, the count where names start. --><span class="fl-indent" aria-hidden="true"></span><span class="fl-arrow-placeholder" aria-hidden="true"></span><span class="fl-more-tag" aria-hidden="true">⋯</span><span class="fl-more-count">${ssrInterpolate(t("moreRemaining", { count: node.remaining }))}</span><span class="fl-more-actions">`);
|
|
2511
|
+
if (node.remaining > (__props.pageSize || 0)) _push(`<!--[--><button class="fl-more-link" type="button"${ssrRenderAttr("aria-label", t("showNext", { count: __props.pageSize }))}><span class="fl-more-long">${ssrInterpolate(t("showNext", { count: __props.pageSize }))}</span><span class="fl-more-short" aria-hidden="true">${ssrInterpolate(t("showNextShort", { count: __props.pageSize }))}</span></button><button class="fl-more-link fl-more-all" type="button"${ssrRenderAttr("aria-label", t("showAll"))}><span class="fl-more-long">${ssrInterpolate(t("showAll"))}</span><span class="fl-more-short" aria-hidden="true">${ssrInterpolate(t("showAllShort"))}</span></button><!--]-->`);
|
|
2512
|
+
else _push(`<button class="fl-more-link fl-more-all" type="button">${ssrInterpolate(t("showRest", { count: node.remaining }))}</button>`);
|
|
2513
|
+
_push(`</span></span></div>`);
|
|
2325
2514
|
} else {
|
|
2326
2515
|
_push(`<div class="${ssrRenderClass([{
|
|
2327
2516
|
"fl-row-dir": node.is_dir,
|
|
@@ -2330,7 +2519,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
2330
2519
|
"fl-row-locked": !!node.lock,
|
|
2331
2520
|
"fl-row-prompting": node.lock?.prompting,
|
|
2332
2521
|
"fl-row-error": node.lock?.prompting && !!node.lock.error
|
|
2333
|
-
}, "fl-row"])}" style="${ssrRenderStyle({ "--fl-depth": node.depth })}"><label class="fl-cell fl-cell-check"><input type="checkbox"${ssrIncludeBooleanAttr(!!node.lock || batchBusy.value) ? " disabled" : ""}${ssrRenderAttr("aria-label", t("selectItem", { name: node.name }))}${ssrIncludeBooleanAttr(isChecked(node)) ? " checked" : ""}${ssrRenderAttr("indeterminate", isIndeterminate(node))}></label><span class="fl-cell fl-cell-name"${ssrRenderAttr("title", node.isRoot ? node.path : void 0)}><span class="fl-indent" aria-hidden="true"></span>`);
|
|
2522
|
+
}, "fl-row"])}" style="${ssrRenderStyle({ "--fl-depth": node.depth })}"${ssrRenderAttr("data-path", node.path)}><label class="fl-cell fl-cell-check"><input type="checkbox"${ssrIncludeBooleanAttr(!!node.lock || batchBusy.value) ? " disabled" : ""}${ssrRenderAttr("aria-label", t("selectItem", { name: node.name }))}${ssrIncludeBooleanAttr(isChecked(node)) ? " checked" : ""}${ssrRenderAttr("indeterminate", isIndeterminate(node))}></label><span class="fl-cell fl-cell-name"${ssrRenderAttr("title", node.isRoot ? node.path : void 0)}><span class="fl-indent" aria-hidden="true"></span>`);
|
|
2334
2523
|
if (node.lock) {
|
|
2335
2524
|
_push(`<button class="fl-arrow fl-lock" type="button"${ssrRenderAttr("aria-label", t("unlockFolder", { name: node.name }))}${ssrRenderAttr("title", t("unlockFolder", { name: node.name }))}>`);
|
|
2336
2525
|
_push(ssrRenderComponent(_sfc_main$2, {
|
|
@@ -2432,12 +2621,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
2432
2621
|
});
|
|
2433
2622
|
_push(`<!--]-->`);
|
|
2434
2623
|
if (deepRow.value) {
|
|
2435
|
-
_push(`<div class="fl-deep" role="status"><
|
|
2624
|
+
_push(`<div class="fl-deep" role="status"><div class="fl-deep-text">`);
|
|
2436
2625
|
if (deepRow.value === "running") _push(`<!--[-->${ssrInterpolate(t("deepRunning", { count: folderCounts.value.checked }))} · <button class="fl-deep-link" type="button">${ssrInterpolate(t("deepStop"))}</button><!--]-->`);
|
|
2437
2626
|
else if (deepRow.value === "idle") _push(`<!--[-->${ssrInterpolate(t("deepHint"))}<button class="fl-deep-link" type="button">${ssrInterpolate(t("deepSearch"))}</button><!--]-->`);
|
|
2438
2627
|
else if (deepRow.value === "capped") _push(`<!--[-->${ssrInterpolate(deepSummary.value)} · <button class="fl-deep-link" type="button">${ssrInterpolate(t("deepContinue"))}</button><!--]-->`);
|
|
2439
2628
|
else _push(`<!--[-->${ssrInterpolate(deepSummary.value)}<!--]-->`);
|
|
2440
|
-
_push(`</
|
|
2629
|
+
_push(`</div>`);
|
|
2441
2630
|
if (deepRow.value === "running") _push(`<span class="fl-deep-bar" aria-hidden="true"></span>`);
|
|
2442
2631
|
else _push(`<!---->`);
|
|
2443
2632
|
_push(`</div>`);
|
|
@@ -2463,7 +2652,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
2463
2652
|
viewer: previewViewer.value,
|
|
2464
2653
|
resolving: previewResolving.value,
|
|
2465
2654
|
error: previewError.value,
|
|
2466
|
-
demo: demo.value,
|
|
2655
|
+
demo: demo.value && !!previewNode.value && !unref(isDemoFile)(previewNode.value.path),
|
|
2467
2656
|
position: previewPosition.value,
|
|
2468
2657
|
total: previewPosition.value ? previewList.value.length : 0,
|
|
2469
2658
|
onClose: closePreview,
|