vitepress-plugin-filelist 0.3.1 → 0.5.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 +33 -5
- package/dist/client/browser/index.d.ts +70 -0
- package/dist/client/browser/index.js +1631 -243
- package/dist/client/ssr/index.d.ts +70 -0
- package/dist/client/ssr/index.js +887 -77
- package/dist/client/style.css +563 -25
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../style.css";
|
|
2
|
-
import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, defineComponent, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, toDisplayString, unref, vModelSelect, vModelText, withDirectives, withKeys } from "vue";
|
|
2
|
+
import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, reactive, ref, renderList, toDisplayString, unref, vModelSelect, vModelText, watch, withDirectives, withKeys, withModifiers } from "vue";
|
|
3
3
|
// src/client/locales.ts
|
|
4
4
|
const fileListLocales = {
|
|
5
5
|
"zh-CN": {
|
|
@@ -11,7 +11,7 @@ const fileListLocales = {
|
|
|
11
11
|
batchCount: "批量下载({count})",
|
|
12
12
|
batching: "打包中 {percent}%",
|
|
13
13
|
passwordPrompt: "该目录受密码保护:",
|
|
14
|
-
passwordPlaceholder: "
|
|
14
|
+
passwordPlaceholder: "输入密码",
|
|
15
15
|
open: "打开",
|
|
16
16
|
columnName: "文件名",
|
|
17
17
|
columnSize: "文件大小",
|
|
@@ -19,7 +19,7 @@ const fileListLocales = {
|
|
|
19
19
|
loading: "加载中…",
|
|
20
20
|
loadFailed: "加载失败:{message}",
|
|
21
21
|
guestHint: "在 OpenList 后台启用 guest,或给组件传一个只读 token。",
|
|
22
|
-
corsHint: "
|
|
22
|
+
corsHint: "可能是跨域或混合内容被拦截:检查 OpenList 的 cors.allow_origins(OpenList-Worker 为 ALLOW_URLS)与页面协议。",
|
|
23
23
|
retry: "重试",
|
|
24
24
|
empty: "没有文件",
|
|
25
25
|
preview: "预览",
|
|
@@ -43,7 +43,27 @@ const fileListLocales = {
|
|
|
43
43
|
selectAll: "全选",
|
|
44
44
|
selectItem: "选择 {name}",
|
|
45
45
|
expand: "展开 {name}",
|
|
46
|
-
collapse: "收起 {name}"
|
|
46
|
+
collapse: "收起 {name}",
|
|
47
|
+
close: "关闭",
|
|
48
|
+
previous: "上一个",
|
|
49
|
+
next: "下一个",
|
|
50
|
+
rotate: "旋转",
|
|
51
|
+
actualSize: "原始大小",
|
|
52
|
+
previewUnsupported: "该类型暂不支持在线预览,可下载后查看",
|
|
53
|
+
previewUnavailable: "该文件无法在页面内预览,可下载后查看",
|
|
54
|
+
previewTooLarge: "文件较大({size}),请下载后查看",
|
|
55
|
+
textTruncated: "仅显示前 {size},完整内容请下载",
|
|
56
|
+
refreshing: "刷新中",
|
|
57
|
+
refreshFailed: "刷新失败:{message}",
|
|
58
|
+
cancel: "取消",
|
|
59
|
+
passwordFor: "{name} 的密码",
|
|
60
|
+
passwordWrong: "密码不正确或无权访问",
|
|
61
|
+
passwordStale: "已保存的密码已失效,请重新输入",
|
|
62
|
+
rememberPassword: "在此标签页中记住(关闭标签页后失效)",
|
|
63
|
+
remembered: "已记住",
|
|
64
|
+
forgetPassword: "忘记",
|
|
65
|
+
unlockFolder: "输入 {name} 的密码",
|
|
66
|
+
networkHint: "可能被跨域拦截:请检查 OpenList 的 cors.allow_origins,或 OpenList-Worker 的 ALLOW_URLS。"
|
|
47
67
|
},
|
|
48
68
|
"en-US": {
|
|
49
69
|
allFiles: "All files",
|
|
@@ -62,7 +82,7 @@ const fileListLocales = {
|
|
|
62
82
|
loading: "Loading…",
|
|
63
83
|
loadFailed: "Failed to load: {message}",
|
|
64
84
|
guestHint: "Enable the guest user in OpenList, or pass a read-only token.",
|
|
65
|
-
corsHint: "Possibly blocked by CORS or mixed content. Check OpenList
|
|
85
|
+
corsHint: "Possibly blocked by CORS or mixed content. Check cors.allow_origins in OpenList (ALLOW_URLS in OpenList-Worker) and the page protocol.",
|
|
66
86
|
retry: "Retry",
|
|
67
87
|
empty: "No files",
|
|
68
88
|
preview: "Preview",
|
|
@@ -86,7 +106,27 @@ const fileListLocales = {
|
|
|
86
106
|
selectAll: "Select all",
|
|
87
107
|
selectItem: "Select {name}",
|
|
88
108
|
expand: "Expand {name}",
|
|
89
|
-
collapse: "Collapse {name}"
|
|
109
|
+
collapse: "Collapse {name}",
|
|
110
|
+
close: "Close",
|
|
111
|
+
previous: "Previous",
|
|
112
|
+
next: "Next",
|
|
113
|
+
rotate: "Rotate",
|
|
114
|
+
actualSize: "Actual size",
|
|
115
|
+
previewUnsupported: "This file type cannot be previewed here. Download it to view.",
|
|
116
|
+
previewUnavailable: "This file cannot be previewed in the page. Download it to view.",
|
|
117
|
+
previewTooLarge: "This file is large ({size}). Download it to view.",
|
|
118
|
+
textTruncated: "Showing the first {size} only. Download for the full file.",
|
|
119
|
+
refreshing: "Refreshing",
|
|
120
|
+
refreshFailed: "Refresh failed: {message}",
|
|
121
|
+
cancel: "Cancel",
|
|
122
|
+
passwordFor: "Password for {name}",
|
|
123
|
+
passwordWrong: "Wrong password or no permission",
|
|
124
|
+
passwordStale: "The saved password no longer works. Enter it again.",
|
|
125
|
+
rememberPassword: "Remember in this tab (cleared when the tab closes)",
|
|
126
|
+
remembered: "Remembered",
|
|
127
|
+
forgetPassword: "Forget",
|
|
128
|
+
unlockFolder: "Enter the password for {name}",
|
|
129
|
+
networkHint: "Possibly blocked by CORS: check cors.allow_origins in OpenList, or ALLOW_URLS in OpenList-Worker."
|
|
90
130
|
}
|
|
91
131
|
};
|
|
92
132
|
function formatLabel(template, values = {}) {
|
|
@@ -268,37 +308,701 @@ function compareNodes(a, b, key, ascending = true) {
|
|
|
268
308
|
else result = a.name.localeCompare(b.name, "zh-Hans-CN", { numeric: true });
|
|
269
309
|
return ascending ? result : -result;
|
|
270
310
|
}
|
|
311
|
+
const PREVIEW_EXTS = {
|
|
312
|
+
image: [
|
|
313
|
+
"png",
|
|
314
|
+
"jpg",
|
|
315
|
+
"jpeg",
|
|
316
|
+
"gif",
|
|
317
|
+
"webp",
|
|
318
|
+
"svg",
|
|
319
|
+
"bmp",
|
|
320
|
+
"ico",
|
|
321
|
+
"avif"
|
|
322
|
+
],
|
|
323
|
+
video: [
|
|
324
|
+
"mp4",
|
|
325
|
+
"webm",
|
|
326
|
+
"m4v",
|
|
327
|
+
"mov",
|
|
328
|
+
"ogv"
|
|
329
|
+
],
|
|
330
|
+
audio: [
|
|
331
|
+
"mp3",
|
|
332
|
+
"wav",
|
|
333
|
+
"ogg",
|
|
334
|
+
"oga",
|
|
335
|
+
"m4a",
|
|
336
|
+
"aac",
|
|
337
|
+
"flac",
|
|
338
|
+
"opus"
|
|
339
|
+
],
|
|
340
|
+
text: [
|
|
341
|
+
"txt",
|
|
342
|
+
"md",
|
|
343
|
+
"markdown",
|
|
344
|
+
"log",
|
|
345
|
+
"json",
|
|
346
|
+
"csv",
|
|
347
|
+
"tsv",
|
|
348
|
+
"xml",
|
|
349
|
+
"yml",
|
|
350
|
+
"yaml",
|
|
351
|
+
"toml",
|
|
352
|
+
"ini",
|
|
353
|
+
"conf",
|
|
354
|
+
"properties",
|
|
355
|
+
"js",
|
|
356
|
+
"mjs",
|
|
357
|
+
"cjs",
|
|
358
|
+
"ts",
|
|
359
|
+
"tsx",
|
|
360
|
+
"jsx",
|
|
361
|
+
"vue",
|
|
362
|
+
"css",
|
|
363
|
+
"scss",
|
|
364
|
+
"less",
|
|
365
|
+
"html",
|
|
366
|
+
"htm",
|
|
367
|
+
"py",
|
|
368
|
+
"java",
|
|
369
|
+
"kt",
|
|
370
|
+
"c",
|
|
371
|
+
"h",
|
|
372
|
+
"cpp",
|
|
373
|
+
"hpp",
|
|
374
|
+
"cs",
|
|
375
|
+
"go",
|
|
376
|
+
"rs",
|
|
377
|
+
"rb",
|
|
378
|
+
"php",
|
|
379
|
+
"sh",
|
|
380
|
+
"bat",
|
|
381
|
+
"ps1",
|
|
382
|
+
"sql",
|
|
383
|
+
"srt",
|
|
384
|
+
"vtt",
|
|
385
|
+
"ass",
|
|
386
|
+
"lrc",
|
|
387
|
+
"gitignore",
|
|
388
|
+
"env"
|
|
389
|
+
],
|
|
390
|
+
pdf: ["pdf"]
|
|
391
|
+
};
|
|
392
|
+
const PREVIEW_KINDS = {};
|
|
393
|
+
for (const [kind, exts] of Object.entries(PREVIEW_EXTS)) for (const ext of exts) PREVIEW_KINDS[ext] = kind;
|
|
394
|
+
function lowerExt(name) {
|
|
395
|
+
const dot = name.lastIndexOf(".");
|
|
396
|
+
return dot >= 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* How a file is shown in the preview dialog. Office documents and anything
|
|
400
|
+
* else the browser cannot render are `none` (info + download).
|
|
401
|
+
*/
|
|
402
|
+
function previewKind(name) {
|
|
403
|
+
return PREVIEW_KINDS[lowerExt(name)] ?? "none";
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Viewers from OpenList's public `iframe_previews` setting that match a file,
|
|
407
|
+
* in the order OpenList lists them. Keys are comma-separated extensions or a
|
|
408
|
+
* `/regex/flags` tested against the extension, as in OpenList's frontend.
|
|
409
|
+
*/
|
|
410
|
+
function iframePreviewsFor(name, setting) {
|
|
411
|
+
if (!setting) return [];
|
|
412
|
+
let parsed;
|
|
413
|
+
try {
|
|
414
|
+
parsed = JSON.parse(setting);
|
|
415
|
+
} catch {
|
|
416
|
+
return [];
|
|
417
|
+
}
|
|
418
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
419
|
+
const ext = lowerExt(name);
|
|
420
|
+
const out = [];
|
|
421
|
+
for (const [key, viewers] of Object.entries(parsed)) {
|
|
422
|
+
let hit = false;
|
|
423
|
+
if (key.startsWith("/")) {
|
|
424
|
+
const end = key.lastIndexOf("/");
|
|
425
|
+
try {
|
|
426
|
+
hit = end > 0 && new RegExp(key.slice(1, end), key.slice(end + 1)).test(ext);
|
|
427
|
+
} catch {
|
|
428
|
+
hit = false;
|
|
429
|
+
}
|
|
430
|
+
} else hit = key.split(",").map((item) => item.trim().toLowerCase()).includes(ext);
|
|
431
|
+
if (!hit || !viewers || typeof viewers !== "object") continue;
|
|
432
|
+
for (const [label, scheme] of Object.entries(viewers)) if (typeof scheme === "string" && /^https?:\/\//i.test(scheme)) out.push({
|
|
433
|
+
label,
|
|
434
|
+
scheme
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
return out;
|
|
438
|
+
}
|
|
439
|
+
function applyUrlOps(placeholder, url) {
|
|
440
|
+
const ops = placeholder.slice(1, placeholder.lastIndexOf("url")).replace(/d$/, "").match(/[eb]/g) ?? [];
|
|
441
|
+
let value = url;
|
|
442
|
+
for (const op of ops.reverse()) value = op === "e" ? encodeURIComponent(value) : btoa(String.fromCharCode(...new TextEncoder().encode(value)));
|
|
443
|
+
return value;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Fill an OpenList preview scheme: `$name`, `$url` (raw URL) and `$durl`
|
|
447
|
+
* (signed download URL), each optionally prefixed with `e` (URI-encode) or
|
|
448
|
+
* `b` (base64), e.g. `$e_url`, `$b_url`, `$e_durl`.
|
|
449
|
+
*/
|
|
450
|
+
function fillPreviewUrl(scheme, args) {
|
|
451
|
+
return scheme.replace(/\$name/g, args.name).replace(/\$[eb_]*durl/g, (placeholder) => applyUrlOps(placeholder, args.downloadUrl)).replace(/\$[eb_]*url/g, (placeholder) => applyUrlOps(placeholder, args.rawUrl));
|
|
452
|
+
}
|
|
453
|
+
// src/client/PreviewDialog.vue
|
|
454
|
+
const _hoisted_1$1 = ["aria-label"];
|
|
455
|
+
const _hoisted_2$1 = {
|
|
456
|
+
key: 0,
|
|
457
|
+
class: "fl-preview-panel"
|
|
458
|
+
};
|
|
459
|
+
const _hoisted_3$1 = { class: "fl-preview-head" };
|
|
460
|
+
const _hoisted_4$1 = ["data-kind"];
|
|
461
|
+
const _hoisted_5$1 = { class: "fl-preview-title" };
|
|
462
|
+
const _hoisted_6$1 = { class: "fl-preview-name" };
|
|
463
|
+
const _hoisted_7$1 = { class: "fl-preview-meta" };
|
|
464
|
+
const _hoisted_8$1 = {
|
|
465
|
+
key: 0,
|
|
466
|
+
class: "fl-preview-tools fl-preview-nav"
|
|
467
|
+
};
|
|
468
|
+
const _hoisted_9$1 = ["aria-label", "title"];
|
|
469
|
+
const _hoisted_10$1 = { class: "fl-preview-count" };
|
|
470
|
+
const _hoisted_11$1 = ["aria-label", "title"];
|
|
471
|
+
const _hoisted_12$1 = ["aria-label", "title"];
|
|
472
|
+
const _hoisted_13$1 = [
|
|
473
|
+
"aria-pressed",
|
|
474
|
+
"aria-label",
|
|
475
|
+
"title"
|
|
476
|
+
];
|
|
477
|
+
const _hoisted_14$1 = {
|
|
478
|
+
viewBox: "0 0 24 24",
|
|
479
|
+
width: "17",
|
|
480
|
+
height: "17",
|
|
481
|
+
fill: "none",
|
|
482
|
+
stroke: "currentColor",
|
|
483
|
+
"stroke-width": "2",
|
|
484
|
+
"stroke-linecap": "round",
|
|
485
|
+
"aria-hidden": "true"
|
|
486
|
+
};
|
|
487
|
+
const _hoisted_15$1 = {
|
|
488
|
+
key: 0,
|
|
489
|
+
d: "M11 8v6M8 11h6"
|
|
490
|
+
};
|
|
491
|
+
const _hoisted_16$1 = {
|
|
492
|
+
key: 1,
|
|
493
|
+
d: "M8 11h6"
|
|
494
|
+
};
|
|
495
|
+
const _hoisted_17$1 = { class: "fl-preview-tools fl-preview-actions" };
|
|
496
|
+
const _hoisted_18$1 = ["aria-label", "title"];
|
|
497
|
+
const _hoisted_19$1 = ["aria-label", "title"];
|
|
498
|
+
const _hoisted_20$1 = ["data-mode"];
|
|
499
|
+
const _hoisted_21$1 = {
|
|
500
|
+
key: 0,
|
|
501
|
+
class: "fl-preview-status",
|
|
502
|
+
role: "status"
|
|
503
|
+
};
|
|
504
|
+
const _hoisted_22$1 = ["src", "alt"];
|
|
505
|
+
const _hoisted_23$1 = ["src"];
|
|
506
|
+
const _hoisted_24$1 = ["src"];
|
|
507
|
+
const _hoisted_25$1 = {
|
|
508
|
+
key: 4,
|
|
509
|
+
class: "fl-preview-text"
|
|
510
|
+
};
|
|
511
|
+
const _hoisted_26$1 = {
|
|
512
|
+
key: 0,
|
|
513
|
+
class: "fl-preview-note"
|
|
514
|
+
};
|
|
515
|
+
const _hoisted_27$1 = ["src", "title"];
|
|
516
|
+
const _hoisted_28$1 = {
|
|
517
|
+
key: 6,
|
|
518
|
+
class: "fl-preview-info"
|
|
519
|
+
};
|
|
520
|
+
const _hoisted_29$1 = ["data-kind"];
|
|
521
|
+
const _hoisted_30$1 = { class: "fl-preview-message" };
|
|
522
|
+
const _sfc_main$1 = /*@__PURE__*/ defineComponent({
|
|
523
|
+
__name: "PreviewDialog",
|
|
524
|
+
props: {
|
|
525
|
+
open: { type: Boolean },
|
|
526
|
+
file: {},
|
|
527
|
+
labels: {},
|
|
528
|
+
src: { default: "" },
|
|
529
|
+
viewer: { default: "" },
|
|
530
|
+
resolving: {
|
|
531
|
+
type: Boolean,
|
|
532
|
+
default: false
|
|
533
|
+
},
|
|
534
|
+
error: { default: "" },
|
|
535
|
+
demo: {
|
|
536
|
+
type: Boolean,
|
|
537
|
+
default: false
|
|
538
|
+
},
|
|
539
|
+
position: { default: 0 },
|
|
540
|
+
total: { default: 0 },
|
|
541
|
+
maxTextBytes: { default: 1048576 },
|
|
542
|
+
maxPdfBytes: { default: 52428800 }
|
|
543
|
+
},
|
|
544
|
+
emits: [
|
|
545
|
+
"close",
|
|
546
|
+
"prev",
|
|
547
|
+
"next",
|
|
548
|
+
"download"
|
|
549
|
+
],
|
|
550
|
+
setup(__props, { emit: __emit }) {
|
|
551
|
+
/**
|
|
552
|
+
* PreviewDialog.vue —— 文件预览弹窗(原生 <dialog>,不跳转页面)
|
|
553
|
+
*
|
|
554
|
+
* image / video / audio:原生标签;text:Range 读取前 maxTextBytes;
|
|
555
|
+
* pdf:OpenList `iframe_previews` 中的查看器,未配置时用浏览器自带阅读器(Blob);
|
|
556
|
+
* 其它类型(含 Office)、失败、过大、演示模式:文件信息 + 下载。
|
|
557
|
+
*/
|
|
558
|
+
const props = __props;
|
|
559
|
+
const emit = __emit;
|
|
560
|
+
function t(key, values) {
|
|
561
|
+
return formatLabel(props.labels[key], values);
|
|
562
|
+
}
|
|
563
|
+
const dialog = ref();
|
|
564
|
+
const kind = computed(() => props.file ? previewKind(props.file.name) : "none");
|
|
565
|
+
const loadingBody = ref(false);
|
|
566
|
+
const bodyFailed = ref(false);
|
|
567
|
+
const textContent = ref("");
|
|
568
|
+
const truncated = ref(false);
|
|
569
|
+
const blobUrl = ref("");
|
|
570
|
+
const rotation = ref(0);
|
|
571
|
+
const actualSize = ref(false);
|
|
572
|
+
let controller;
|
|
573
|
+
let loadSeq = 0;
|
|
574
|
+
const tooLarge = computed(() => kind.value === "pdf" && !props.viewer && (props.file?.size ?? 0) > props.maxPdfBytes);
|
|
575
|
+
const mode = computed(() => {
|
|
576
|
+
if (props.demo) return "demo";
|
|
577
|
+
if (props.error || bodyFailed.value) return "failed";
|
|
578
|
+
if (kind.value === "none") return "unsupported";
|
|
579
|
+
if (tooLarge.value) return "tooLarge";
|
|
580
|
+
return kind.value;
|
|
581
|
+
});
|
|
582
|
+
const busy = computed(() => !props.demo && kind.value !== "none" && !props.error && !bodyFailed.value && (props.resolving || loadingBody.value || !props.src && !tooLarge.value));
|
|
583
|
+
const infoMessage = computed(() => {
|
|
584
|
+
const file = props.file;
|
|
585
|
+
if (!file) return "";
|
|
586
|
+
if (mode.value === "demo") return t("demoPreview", { name: file.name });
|
|
587
|
+
if (mode.value === "tooLarge") return t("previewTooLarge", { size: formatSize(file.size) });
|
|
588
|
+
if (mode.value === "failed") return t("previewUnavailable");
|
|
589
|
+
return t("previewUnsupported");
|
|
590
|
+
});
|
|
591
|
+
function revokeBlob() {
|
|
592
|
+
if (blobUrl.value) URL.revokeObjectURL(blobUrl.value);
|
|
593
|
+
blobUrl.value = "";
|
|
594
|
+
}
|
|
595
|
+
function resetBody() {
|
|
596
|
+
controller?.abort();
|
|
597
|
+
controller = void 0;
|
|
598
|
+
loadSeq += 1;
|
|
599
|
+
revokeBlob();
|
|
600
|
+
loadingBody.value = false;
|
|
601
|
+
bodyFailed.value = false;
|
|
602
|
+
textContent.value = "";
|
|
603
|
+
truncated.value = false;
|
|
604
|
+
rotation.value = 0;
|
|
605
|
+
actualSize.value = false;
|
|
606
|
+
}
|
|
607
|
+
async function readPrefix(response, limit) {
|
|
608
|
+
const reader = response.body?.getReader();
|
|
609
|
+
if (!reader) {
|
|
610
|
+
const all = new Uint8Array(await response.arrayBuffer());
|
|
611
|
+
return {
|
|
612
|
+
bytes: all.slice(0, limit),
|
|
613
|
+
more: all.length > limit
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
const chunks = [];
|
|
617
|
+
let length = 0;
|
|
618
|
+
let more = false;
|
|
619
|
+
while (true) {
|
|
620
|
+
const { done, value } = await reader.read();
|
|
621
|
+
if (done) break;
|
|
622
|
+
chunks.push(value);
|
|
623
|
+
length += value.length;
|
|
624
|
+
if (length >= limit) {
|
|
625
|
+
more = length > limit;
|
|
626
|
+
await reader.cancel().catch(() => {});
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
const bytes = new Uint8Array(Math.min(length, limit));
|
|
631
|
+
let offset = 0;
|
|
632
|
+
for (const chunk of chunks) {
|
|
633
|
+
const part = chunk.subarray(0, bytes.length - offset);
|
|
634
|
+
bytes.set(part, offset);
|
|
635
|
+
offset += part.length;
|
|
636
|
+
if (offset >= bytes.length) break;
|
|
637
|
+
}
|
|
638
|
+
return {
|
|
639
|
+
bytes,
|
|
640
|
+
more
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
async function loadBody() {
|
|
644
|
+
resetBody();
|
|
645
|
+
const file = props.file;
|
|
646
|
+
if (!props.open || !file || props.demo || !props.src || props.error) return;
|
|
647
|
+
if (kind.value !== "text" && !(kind.value === "pdf" && !props.viewer && !tooLarge.value)) return;
|
|
648
|
+
const seq = loadSeq;
|
|
649
|
+
controller = new AbortController();
|
|
650
|
+
loadingBody.value = true;
|
|
651
|
+
try {
|
|
652
|
+
if (kind.value === "text") {
|
|
653
|
+
const limit = props.maxTextBytes;
|
|
654
|
+
const response = await fetch(props.src, {
|
|
655
|
+
headers: { Range: `bytes=0-${limit - 1}` },
|
|
656
|
+
signal: controller.signal
|
|
657
|
+
});
|
|
658
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
659
|
+
const { bytes, more } = await readPrefix(response, limit);
|
|
660
|
+
if (seq !== loadSeq) return;
|
|
661
|
+
textContent.value = new TextDecoder("utf-8").decode(bytes);
|
|
662
|
+
truncated.value = more || file.size > limit;
|
|
663
|
+
} else {
|
|
664
|
+
const response = await fetch(props.src, { signal: controller.signal });
|
|
665
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
666
|
+
const buffer = await response.arrayBuffer();
|
|
667
|
+
if (seq !== loadSeq) return;
|
|
668
|
+
blobUrl.value = URL.createObjectURL(new Blob([buffer], { type: "application/pdf" }));
|
|
669
|
+
}
|
|
670
|
+
} catch {
|
|
671
|
+
if (seq === loadSeq && !controller?.signal.aborted) bodyFailed.value = true;
|
|
672
|
+
} finally {
|
|
673
|
+
if (seq === loadSeq) loadingBody.value = false;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
watch(() => [
|
|
677
|
+
props.open,
|
|
678
|
+
props.file?.name,
|
|
679
|
+
props.src,
|
|
680
|
+
props.viewer,
|
|
681
|
+
props.error,
|
|
682
|
+
props.demo
|
|
683
|
+
], () => void loadBody(), { immediate: true });
|
|
684
|
+
watch(() => props.open, (open) => {
|
|
685
|
+
const el = dialog.value;
|
|
686
|
+
if (!el) return;
|
|
687
|
+
if (open && !el.open) {
|
|
688
|
+
if (typeof el.showModal === "function") el.showModal();
|
|
689
|
+
else el.setAttribute("open", "");
|
|
690
|
+
} else if (!open && el.open) {
|
|
691
|
+
if (typeof el.close === "function") el.close();
|
|
692
|
+
else el.removeAttribute("open");
|
|
693
|
+
}
|
|
694
|
+
}, { flush: "post" });
|
|
695
|
+
onBeforeUnmount(resetBody);
|
|
696
|
+
function onNativeClose() {
|
|
697
|
+
if (props.open) emit("close");
|
|
698
|
+
}
|
|
699
|
+
function onBackdrop(event) {
|
|
700
|
+
if (event.target === dialog.value) emit("close");
|
|
701
|
+
}
|
|
702
|
+
function onKeydown(event) {
|
|
703
|
+
if (props.total < 2) return;
|
|
704
|
+
const target = event.target;
|
|
705
|
+
if (target && /^(INPUT|TEXTAREA|SELECT|VIDEO|AUDIO)$/.test(target.tagName)) return;
|
|
706
|
+
if (event.key === "ArrowLeft") {
|
|
707
|
+
event.preventDefault();
|
|
708
|
+
emit("prev");
|
|
709
|
+
} else if (event.key === "ArrowRight") {
|
|
710
|
+
event.preventDefault();
|
|
711
|
+
emit("next");
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
function markFailed() {
|
|
715
|
+
bodyFailed.value = true;
|
|
716
|
+
}
|
|
717
|
+
return (_ctx, _cache) => {
|
|
718
|
+
return openBlock(), createElementBlock("dialog", {
|
|
719
|
+
ref_key: "dialog",
|
|
720
|
+
ref: dialog,
|
|
721
|
+
class: "fl-preview",
|
|
722
|
+
"aria-label": __props.file?.name,
|
|
723
|
+
onClose: onNativeClose,
|
|
724
|
+
onClick: onBackdrop,
|
|
725
|
+
onKeydown
|
|
726
|
+
}, [__props.open && __props.file ? (openBlock(), createElementBlock("div", _hoisted_2$1, [createElementVNode("header", _hoisted_3$1, [
|
|
727
|
+
createElementVNode("span", {
|
|
728
|
+
class: "fl-type",
|
|
729
|
+
"data-kind": unref(fileKind)(__props.file.name),
|
|
730
|
+
"aria-hidden": "true"
|
|
731
|
+
}, toDisplayString(unref(extOf)(__props.file.name) || "FILE"), 9, _hoisted_4$1),
|
|
732
|
+
createElementVNode("div", _hoisted_5$1, [createElementVNode("span", _hoisted_6$1, toDisplayString(__props.file.name), 1), createElementVNode("span", _hoisted_7$1, toDisplayString(unref(formatSize)(__props.file.size)) + " · " + toDisplayString(unref(formatDate)(__props.file.modified)), 1)]),
|
|
733
|
+
__props.total > 1 || mode.value === "image" && !busy.value ? (openBlock(), createElementBlock("div", _hoisted_8$1, [__props.total > 1 ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
|
|
734
|
+
createElementVNode("button", {
|
|
735
|
+
class: "fl-preview-tool",
|
|
736
|
+
type: "button",
|
|
737
|
+
"aria-label": t("previous"),
|
|
738
|
+
title: t("previous"),
|
|
739
|
+
onClick: _cache[0] || (_cache[0] = ($event) => emit("prev"))
|
|
740
|
+
}, [..._cache[7] || (_cache[7] = [createElementVNode("svg", {
|
|
741
|
+
viewBox: "0 0 24 24",
|
|
742
|
+
width: "18",
|
|
743
|
+
height: "18",
|
|
744
|
+
fill: "none",
|
|
745
|
+
stroke: "currentColor",
|
|
746
|
+
"stroke-width": "2.2",
|
|
747
|
+
"stroke-linecap": "round",
|
|
748
|
+
"stroke-linejoin": "round",
|
|
749
|
+
"aria-hidden": "true"
|
|
750
|
+
}, [createElementVNode("path", { d: "m15 6-6 6 6 6" })], -1)])], 8, _hoisted_9$1),
|
|
751
|
+
createElementVNode("span", _hoisted_10$1, toDisplayString(__props.position) + " / " + toDisplayString(__props.total), 1),
|
|
752
|
+
createElementVNode("button", {
|
|
753
|
+
class: "fl-preview-tool",
|
|
754
|
+
type: "button",
|
|
755
|
+
"aria-label": t("next"),
|
|
756
|
+
title: t("next"),
|
|
757
|
+
onClick: _cache[1] || (_cache[1] = ($event) => emit("next"))
|
|
758
|
+
}, [..._cache[8] || (_cache[8] = [createElementVNode("svg", {
|
|
759
|
+
viewBox: "0 0 24 24",
|
|
760
|
+
width: "18",
|
|
761
|
+
height: "18",
|
|
762
|
+
fill: "none",
|
|
763
|
+
stroke: "currentColor",
|
|
764
|
+
"stroke-width": "2.2",
|
|
765
|
+
"stroke-linecap": "round",
|
|
766
|
+
"stroke-linejoin": "round",
|
|
767
|
+
"aria-hidden": "true"
|
|
768
|
+
}, [createElementVNode("path", { d: "m9 6 6 6-6 6" })], -1)])], 8, _hoisted_11$1)
|
|
769
|
+
], 64)) : createCommentVNode("v-if", true), mode.value === "image" && !busy.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createElementVNode("button", {
|
|
770
|
+
class: "fl-preview-tool fl-preview-rotate",
|
|
771
|
+
type: "button",
|
|
772
|
+
"aria-label": t("rotate"),
|
|
773
|
+
title: t("rotate"),
|
|
774
|
+
onClick: _cache[2] || (_cache[2] = ($event) => rotation.value = (rotation.value + 90) % 360)
|
|
775
|
+
}, [..._cache[9] || (_cache[9] = [createElementVNode("svg", {
|
|
776
|
+
viewBox: "0 0 24 24",
|
|
777
|
+
width: "17",
|
|
778
|
+
height: "17",
|
|
779
|
+
fill: "none",
|
|
780
|
+
stroke: "currentColor",
|
|
781
|
+
"stroke-width": "2",
|
|
782
|
+
"stroke-linecap": "round",
|
|
783
|
+
"stroke-linejoin": "round",
|
|
784
|
+
"aria-hidden": "true"
|
|
785
|
+
}, [createElementVNode("path", { d: "M21 12a9 9 0 1 1-3-6.7" }), createElementVNode("path", { d: "M21 4v5h-5" })], -1)])], 8, _hoisted_12$1), createElementVNode("button", {
|
|
786
|
+
class: "fl-preview-tool fl-preview-zoom",
|
|
787
|
+
type: "button",
|
|
788
|
+
"aria-pressed": actualSize.value,
|
|
789
|
+
"aria-label": t("actualSize"),
|
|
790
|
+
title: t("actualSize"),
|
|
791
|
+
onClick: _cache[3] || (_cache[3] = ($event) => actualSize.value = !actualSize.value)
|
|
792
|
+
}, [(openBlock(), createElementBlock("svg", _hoisted_14$1, [
|
|
793
|
+
_cache[10] || (_cache[10] = createElementVNode("circle", {
|
|
794
|
+
cx: "11",
|
|
795
|
+
cy: "11",
|
|
796
|
+
r: "7"
|
|
797
|
+
}, null, -1)),
|
|
798
|
+
_cache[11] || (_cache[11] = createElementVNode("path", { d: "m20 20-3.5-3.5" }, null, -1)),
|
|
799
|
+
!actualSize.value ? (openBlock(), createElementBlock("path", _hoisted_15$1)) : (openBlock(), createElementBlock("path", _hoisted_16$1))
|
|
800
|
+
]))], 8, _hoisted_13$1)], 64)) : createCommentVNode("v-if", true)])) : createCommentVNode("v-if", true),
|
|
801
|
+
createElementVNode("div", _hoisted_17$1, [!__props.demo ? (openBlock(), createElementBlock("button", {
|
|
802
|
+
key: 0,
|
|
803
|
+
class: "fl-preview-tool fl-preview-download",
|
|
804
|
+
type: "button",
|
|
805
|
+
"aria-label": t("download"),
|
|
806
|
+
title: t("download"),
|
|
807
|
+
onClick: _cache[4] || (_cache[4] = ($event) => emit("download"))
|
|
808
|
+
}, [..._cache[12] || (_cache[12] = [createElementVNode("svg", {
|
|
809
|
+
viewBox: "0 0 24 24",
|
|
810
|
+
width: "17",
|
|
811
|
+
height: "17",
|
|
812
|
+
fill: "none",
|
|
813
|
+
stroke: "currentColor",
|
|
814
|
+
"stroke-width": "2",
|
|
815
|
+
"stroke-linecap": "round",
|
|
816
|
+
"stroke-linejoin": "round",
|
|
817
|
+
"aria-hidden": "true"
|
|
818
|
+
}, [
|
|
819
|
+
createElementVNode("path", { d: "M12 3v12" }),
|
|
820
|
+
createElementVNode("path", { d: "m7 10 5 5 5-5" }),
|
|
821
|
+
createElementVNode("path", { d: "M5 21h14" })
|
|
822
|
+
], -1)])], 8, _hoisted_18$1)) : createCommentVNode("v-if", true), createElementVNode("button", {
|
|
823
|
+
class: "fl-preview-tool fl-preview-close",
|
|
824
|
+
type: "button",
|
|
825
|
+
autofocus: "",
|
|
826
|
+
"aria-label": t("close"),
|
|
827
|
+
title: t("close"),
|
|
828
|
+
onClick: _cache[5] || (_cache[5] = ($event) => emit("close"))
|
|
829
|
+
}, [..._cache[13] || (_cache[13] = [createElementVNode("svg", {
|
|
830
|
+
viewBox: "0 0 24 24",
|
|
831
|
+
width: "18",
|
|
832
|
+
height: "18",
|
|
833
|
+
fill: "none",
|
|
834
|
+
stroke: "currentColor",
|
|
835
|
+
"stroke-width": "2.2",
|
|
836
|
+
"stroke-linecap": "round",
|
|
837
|
+
"aria-hidden": "true"
|
|
838
|
+
}, [createElementVNode("path", { d: "M6 6l12 12" }), createElementVNode("path", { d: "M18 6 6 18" })], -1)])], 8, _hoisted_19$1)])
|
|
839
|
+
]), createElementVNode("div", {
|
|
840
|
+
class: "fl-preview-body",
|
|
841
|
+
"data-mode": busy.value ? "loading" : mode.value
|
|
842
|
+
}, [busy.value ? (openBlock(), createElementBlock("div", _hoisted_21$1, toDisplayString(t("loading")), 1)) : mode.value === "image" ? (openBlock(), createElementBlock("img", {
|
|
843
|
+
key: 1,
|
|
844
|
+
class: normalizeClass(["fl-preview-image", { "fl-preview-actual": actualSize.value }]),
|
|
845
|
+
src: __props.src,
|
|
846
|
+
alt: __props.file.name,
|
|
847
|
+
style: normalizeStyle({ transform: rotation.value ? `rotate(${rotation.value}deg)` : void 0 }),
|
|
848
|
+
onError: markFailed
|
|
849
|
+
}, null, 46, _hoisted_22$1)) : mode.value === "video" ? (openBlock(), createElementBlock("video", {
|
|
850
|
+
key: 2,
|
|
851
|
+
class: "fl-preview-video",
|
|
852
|
+
src: __props.src,
|
|
853
|
+
controls: "",
|
|
854
|
+
playsinline: "",
|
|
855
|
+
preload: "metadata",
|
|
856
|
+
onError: markFailed
|
|
857
|
+
}, null, 40, _hoisted_23$1)) : mode.value === "audio" ? (openBlock(), createElementBlock("audio", {
|
|
858
|
+
key: 3,
|
|
859
|
+
class: "fl-preview-audio",
|
|
860
|
+
src: __props.src,
|
|
861
|
+
controls: "",
|
|
862
|
+
preload: "metadata",
|
|
863
|
+
onError: markFailed
|
|
864
|
+
}, null, 40, _hoisted_24$1)) : mode.value === "text" ? (openBlock(), createElementBlock("div", _hoisted_25$1, [truncated.value ? (openBlock(), createElementBlock("p", _hoisted_26$1, toDisplayString(t("textTruncated", { size: unref(formatSize)(__props.maxTextBytes) })), 1)) : createCommentVNode("v-if", true), createElementVNode("pre", null, toDisplayString(textContent.value), 1)])) : mode.value === "pdf" ? (openBlock(), createElementBlock("iframe", {
|
|
865
|
+
key: 5,
|
|
866
|
+
class: "fl-preview-frame",
|
|
867
|
+
src: __props.viewer || blobUrl.value,
|
|
868
|
+
title: __props.file.name
|
|
869
|
+
}, null, 8, _hoisted_27$1)) : (openBlock(), createElementBlock("div", _hoisted_28$1, [
|
|
870
|
+
createElementVNode("span", {
|
|
871
|
+
class: "fl-type fl-preview-badge",
|
|
872
|
+
"data-kind": unref(fileKind)(__props.file.name),
|
|
873
|
+
"aria-hidden": "true"
|
|
874
|
+
}, toDisplayString(unref(extOf)(__props.file.name) || "FILE"), 9, _hoisted_29$1),
|
|
875
|
+
createElementVNode("p", _hoisted_30$1, toDisplayString(infoMessage.value), 1),
|
|
876
|
+
!__props.demo ? (openBlock(), createElementBlock("button", {
|
|
877
|
+
key: 0,
|
|
878
|
+
class: "fl-btn fl-btn-primary",
|
|
879
|
+
type: "button",
|
|
880
|
+
onClick: _cache[6] || (_cache[6] = ($event) => emit("download"))
|
|
881
|
+
}, [_cache[14] || (_cache[14] = createElementVNode("svg", {
|
|
882
|
+
viewBox: "0 0 24 24",
|
|
883
|
+
width: "15",
|
|
884
|
+
height: "15",
|
|
885
|
+
fill: "none",
|
|
886
|
+
stroke: "currentColor",
|
|
887
|
+
"stroke-width": "2.2",
|
|
888
|
+
"stroke-linecap": "round",
|
|
889
|
+
"stroke-linejoin": "round",
|
|
890
|
+
"aria-hidden": "true"
|
|
891
|
+
}, [
|
|
892
|
+
createElementVNode("path", { d: "M12 3v12" }),
|
|
893
|
+
createElementVNode("path", { d: "m7 10 5 5 5-5" }),
|
|
894
|
+
createElementVNode("path", { d: "M5 21h14" })
|
|
895
|
+
], -1)), createTextVNode(" " + toDisplayString(t("download")), 1)])) : createCommentVNode("v-if", true)
|
|
896
|
+
]))], 8, _hoisted_20$1)])) : createCommentVNode("v-if", true)], 40, _hoisted_1$1);
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
// src/client/passwords.ts
|
|
901
|
+
/**
|
|
902
|
+
* Folder passwords remembered for the current browser tab.
|
|
903
|
+
*
|
|
904
|
+
* Stored in `sessionStorage`, so they live only in this tab and are gone when
|
|
905
|
+
* it closes. Keyed by OpenList base URL, then by folder path; a remembered
|
|
906
|
+
* folder also covers its sub-folders. Every access is guarded: private mode
|
|
907
|
+
* or blocked storage simply means nothing is remembered.
|
|
908
|
+
*/
|
|
909
|
+
const PREFIX = "vitepress-plugin-filelist:passwords:";
|
|
910
|
+
function store() {
|
|
911
|
+
try {
|
|
912
|
+
return typeof sessionStorage === "undefined" ? null : sessionStorage;
|
|
913
|
+
} catch {
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function read(base) {
|
|
918
|
+
try {
|
|
919
|
+
const raw = store()?.getItem(PREFIX + base);
|
|
920
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
921
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
922
|
+
} catch {
|
|
923
|
+
return {};
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
function write(base, map) {
|
|
927
|
+
try {
|
|
928
|
+
const target = store();
|
|
929
|
+
if (!target) return;
|
|
930
|
+
if (Object.keys(map).length) target.setItem(PREFIX + base, JSON.stringify(map));
|
|
931
|
+
else target.removeItem(PREFIX + base);
|
|
932
|
+
} catch {}
|
|
933
|
+
}
|
|
934
|
+
function normalise(path) {
|
|
935
|
+
return path.replace(/\/+$/, "") || "/";
|
|
936
|
+
}
|
|
937
|
+
/** Whether `path` is `folder` itself or inside it. */
|
|
938
|
+
function isWithin(path, folder) {
|
|
939
|
+
const p = normalise(path);
|
|
940
|
+
const f = normalise(folder);
|
|
941
|
+
return f === "/" || p === f || p.startsWith(`${f}/`);
|
|
942
|
+
}
|
|
943
|
+
/** The closest remembered folder at or above `path`, if any. */
|
|
944
|
+
function findRememberedPassword(base, path) {
|
|
945
|
+
let best = null;
|
|
946
|
+
for (const [folder, password] of Object.entries(read(base))) {
|
|
947
|
+
if (typeof password !== "string" || !isWithin(path, folder)) continue;
|
|
948
|
+
if (!best || folder.length > best.path.length) best = {
|
|
949
|
+
path: folder,
|
|
950
|
+
password
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
return best;
|
|
954
|
+
}
|
|
955
|
+
function rememberFolderPassword(base, path, password) {
|
|
956
|
+
write(base, {
|
|
957
|
+
...read(base),
|
|
958
|
+
[normalise(path)]: password
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
function forgetFolderPassword(base, path) {
|
|
962
|
+
const map = read(base);
|
|
963
|
+
delete map[normalise(path)];
|
|
964
|
+
write(base, map);
|
|
965
|
+
}
|
|
966
|
+
/** Folders with a remembered password, for showing the "remembered" tag. */
|
|
967
|
+
function rememberedFolders(base) {
|
|
968
|
+
return Object.keys(read(base));
|
|
969
|
+
}
|
|
271
970
|
// src/client/FileList.vue
|
|
272
|
-
const _hoisted_1 =
|
|
273
|
-
const _hoisted_2 = {
|
|
971
|
+
const _hoisted_1 = ["aria-busy"];
|
|
972
|
+
const _hoisted_2 = { class: "fl-bar" };
|
|
973
|
+
const _hoisted_3 = {
|
|
274
974
|
key: 0,
|
|
275
975
|
class: "fl-selection",
|
|
276
976
|
role: "status"
|
|
277
977
|
};
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
const
|
|
978
|
+
const _hoisted_4 = { class: "fl-selection-count" };
|
|
979
|
+
const _hoisted_5 = { class: "fl-selection-size" };
|
|
980
|
+
const _hoisted_6 = {
|
|
281
981
|
key: 1,
|
|
282
982
|
class: "fl-title"
|
|
283
983
|
};
|
|
284
|
-
const
|
|
285
|
-
const
|
|
984
|
+
const _hoisted_7 = { class: "fl-title-text" };
|
|
985
|
+
const _hoisted_8 = {
|
|
286
986
|
key: 0,
|
|
287
987
|
class: "fl-demo-badge"
|
|
288
988
|
};
|
|
289
|
-
const
|
|
290
|
-
const
|
|
291
|
-
const
|
|
292
|
-
const
|
|
293
|
-
const
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
989
|
+
const _hoisted_9 = ["aria-label", "title"];
|
|
990
|
+
const _hoisted_10 = { class: "fl-clear-text" };
|
|
991
|
+
const _hoisted_11 = { class: "fl-tools" };
|
|
992
|
+
const _hoisted_12 = { class: "fl-search" };
|
|
993
|
+
const _hoisted_13 = ["placeholder", "aria-label"];
|
|
994
|
+
const _hoisted_14 = [
|
|
995
|
+
"disabled",
|
|
996
|
+
"aria-label",
|
|
997
|
+
"title"
|
|
998
|
+
];
|
|
999
|
+
const _hoisted_15 = ["disabled"];
|
|
1000
|
+
const _hoisted_16 = { class: "fl-row fl-head" };
|
|
1001
|
+
const _hoisted_17 = {
|
|
297
1002
|
key: 0,
|
|
298
|
-
class: "fl-
|
|
1003
|
+
class: "fl-progress",
|
|
1004
|
+
"aria-hidden": "true"
|
|
299
1005
|
};
|
|
300
|
-
const _hoisted_16 = ["placeholder", "aria-label"];
|
|
301
|
-
const _hoisted_17 = { class: "fl-row fl-head" };
|
|
302
1006
|
const _hoisted_18 = { class: "fl-cell fl-cell-check" };
|
|
303
1007
|
const _hoisted_19 = [
|
|
304
1008
|
"aria-label",
|
|
@@ -330,84 +1034,139 @@ const _hoisted_29 = {
|
|
|
330
1034
|
const _hoisted_30 = ["aria-label"];
|
|
331
1035
|
const _hoisted_31 = ["value"];
|
|
332
1036
|
const _hoisted_32 = {
|
|
333
|
-
key:
|
|
1037
|
+
key: 0,
|
|
334
1038
|
class: "fl-status"
|
|
335
1039
|
};
|
|
336
|
-
const _hoisted_33 =
|
|
1040
|
+
const _hoisted_33 = ["for"];
|
|
1041
|
+
const _hoisted_34 = {
|
|
1042
|
+
key: 0,
|
|
1043
|
+
id: "fl-root-unlock-error",
|
|
1044
|
+
class: "fl-unlock-message",
|
|
1045
|
+
role: "alert"
|
|
1046
|
+
};
|
|
1047
|
+
const _hoisted_35 = { class: "fl-unlock" };
|
|
1048
|
+
const _hoisted_36 = [
|
|
1049
|
+
"id",
|
|
1050
|
+
"data-unlock",
|
|
1051
|
+
"placeholder",
|
|
1052
|
+
"aria-invalid",
|
|
1053
|
+
"aria-describedby"
|
|
1054
|
+
];
|
|
1055
|
+
const _hoisted_37 = [
|
|
1056
|
+
"aria-pressed",
|
|
1057
|
+
"aria-label",
|
|
1058
|
+
"title"
|
|
1059
|
+
];
|
|
1060
|
+
const _hoisted_38 = ["disabled"];
|
|
1061
|
+
const _hoisted_39 = {
|
|
337
1062
|
key: 2,
|
|
338
1063
|
class: "fl-status fl-error"
|
|
339
1064
|
};
|
|
340
|
-
const
|
|
1065
|
+
const _hoisted_40 = {
|
|
341
1066
|
key: 0,
|
|
342
1067
|
class: "fl-hint"
|
|
343
1068
|
};
|
|
344
|
-
const
|
|
1069
|
+
const _hoisted_41 = {
|
|
345
1070
|
key: 1,
|
|
346
1071
|
class: "fl-hint"
|
|
347
1072
|
};
|
|
348
|
-
const
|
|
1073
|
+
const _hoisted_42 = {
|
|
349
1074
|
key: 3,
|
|
350
1075
|
class: "fl-status"
|
|
351
1076
|
};
|
|
352
|
-
const
|
|
353
|
-
const
|
|
1077
|
+
const _hoisted_43 = { class: "fl-cell fl-cell-check" };
|
|
1078
|
+
const _hoisted_44 = [
|
|
1079
|
+
"disabled",
|
|
354
1080
|
"aria-label",
|
|
355
1081
|
"checked",
|
|
356
1082
|
".indeterminate",
|
|
357
1083
|
"onChange"
|
|
358
1084
|
];
|
|
359
|
-
const
|
|
360
|
-
const
|
|
1085
|
+
const _hoisted_45 = ["title"];
|
|
1086
|
+
const _hoisted_46 = [
|
|
1087
|
+
"aria-label",
|
|
1088
|
+
"title",
|
|
1089
|
+
"onClick"
|
|
1090
|
+
];
|
|
1091
|
+
const _hoisted_47 = [
|
|
361
1092
|
"aria-label",
|
|
362
1093
|
"aria-expanded",
|
|
363
1094
|
"onClick"
|
|
364
1095
|
];
|
|
365
|
-
const
|
|
366
|
-
key:
|
|
1096
|
+
const _hoisted_48 = {
|
|
1097
|
+
key: 2,
|
|
367
1098
|
class: "fl-arrow-placeholder",
|
|
368
1099
|
"aria-hidden": "true"
|
|
369
1100
|
};
|
|
370
|
-
const
|
|
371
|
-
key:
|
|
1101
|
+
const _hoisted_49 = {
|
|
1102
|
+
key: 3,
|
|
372
1103
|
class: "fl-folder-icon",
|
|
373
1104
|
viewBox: "0 0 24 24",
|
|
374
1105
|
width: "18",
|
|
375
1106
|
height: "18",
|
|
376
1107
|
"aria-hidden": "true"
|
|
377
1108
|
};
|
|
378
|
-
const
|
|
379
|
-
const
|
|
380
|
-
const
|
|
381
|
-
const
|
|
382
|
-
const
|
|
383
|
-
key:
|
|
1109
|
+
const _hoisted_50 = ["data-kind"];
|
|
1110
|
+
const _hoisted_51 = { class: "fl-label" };
|
|
1111
|
+
const _hoisted_52 = ["onClick"];
|
|
1112
|
+
const _hoisted_53 = { class: "fl-meta" };
|
|
1113
|
+
const _hoisted_54 = {
|
|
1114
|
+
key: 5,
|
|
384
1115
|
class: "fl-count"
|
|
385
1116
|
};
|
|
386
|
-
const
|
|
387
|
-
|
|
1117
|
+
const _hoisted_55 = ["id"];
|
|
1118
|
+
const _hoisted_56 = [
|
|
1119
|
+
"aria-label",
|
|
1120
|
+
"title",
|
|
1121
|
+
"onClick"
|
|
1122
|
+
];
|
|
1123
|
+
const _hoisted_57 = {
|
|
1124
|
+
key: 8,
|
|
388
1125
|
class: "fl-loading",
|
|
389
1126
|
"aria-hidden": "true"
|
|
390
1127
|
};
|
|
391
|
-
const
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
1128
|
+
const _hoisted_58 = ["onSubmit", "onKeydown"];
|
|
1129
|
+
const _hoisted_59 = [
|
|
1130
|
+
"onUpdate:modelValue",
|
|
1131
|
+
"data-unlock",
|
|
1132
|
+
"placeholder",
|
|
1133
|
+
"aria-label",
|
|
1134
|
+
"aria-invalid",
|
|
1135
|
+
"aria-describedby"
|
|
1136
|
+
];
|
|
1137
|
+
const _hoisted_60 = [
|
|
1138
|
+
"aria-pressed",
|
|
1139
|
+
"aria-label",
|
|
1140
|
+
"title",
|
|
1141
|
+
"onClick"
|
|
1142
|
+
];
|
|
1143
|
+
const _hoisted_61 = ["disabled"];
|
|
1144
|
+
const _hoisted_62 = [
|
|
1145
|
+
"aria-label",
|
|
1146
|
+
"title",
|
|
1147
|
+
"onClick"
|
|
1148
|
+
];
|
|
1149
|
+
const _hoisted_63 = { class: "fl-cell fl-cell-size" };
|
|
1150
|
+
const _hoisted_64 = { class: "fl-cell fl-cell-date" };
|
|
1151
|
+
const _hoisted_65 = { class: "fl-cell fl-cell-op" };
|
|
1152
|
+
const _hoisted_66 = ["aria-label", "onClick"];
|
|
1153
|
+
const _hoisted_67 = [
|
|
395
1154
|
"aria-label",
|
|
396
1155
|
"aria-expanded",
|
|
397
1156
|
"onClick"
|
|
398
1157
|
];
|
|
399
|
-
const
|
|
1158
|
+
const _hoisted_68 = [
|
|
400
1159
|
"aria-label",
|
|
401
1160
|
"title",
|
|
402
1161
|
"onClick"
|
|
403
1162
|
];
|
|
404
|
-
const
|
|
1163
|
+
const _hoisted_69 = [
|
|
405
1164
|
"aria-label",
|
|
406
1165
|
"title",
|
|
407
1166
|
"onClick"
|
|
408
1167
|
];
|
|
409
|
-
const
|
|
410
|
-
const
|
|
1168
|
+
const _hoisted_70 = { class: "fl-foot" };
|
|
1169
|
+
const _hoisted_71 = {
|
|
411
1170
|
key: 4,
|
|
412
1171
|
class: "fl-toast",
|
|
413
1172
|
role: "status"
|
|
@@ -425,6 +1184,10 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
425
1184
|
token: { default: "" },
|
|
426
1185
|
password: { default: "" },
|
|
427
1186
|
title: { default: "" },
|
|
1187
|
+
rememberPassword: {
|
|
1188
|
+
type: Boolean,
|
|
1189
|
+
default: true
|
|
1190
|
+
},
|
|
428
1191
|
locale: { default: "zh-CN" },
|
|
429
1192
|
labels: {}
|
|
430
1193
|
},
|
|
@@ -443,18 +1206,30 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
443
1206
|
}
|
|
444
1207
|
const demo = computed(() => !props.base);
|
|
445
1208
|
const baseUrl = computed(() => props.base.replace(/\/+$/, ""));
|
|
446
|
-
const
|
|
1209
|
+
const rootEl = ref();
|
|
447
1210
|
const roots = ref([]);
|
|
448
1211
|
const loadingRoot = ref(false);
|
|
449
1212
|
const errorMsg = ref("");
|
|
450
|
-
const
|
|
451
|
-
|
|
1213
|
+
const refreshing = ref(false);
|
|
1214
|
+
let refreshSeq = 0;
|
|
1215
|
+
/** Single-path mode: the listed folder itself needs a password. */
|
|
1216
|
+
const rootLock = reactive({
|
|
1217
|
+
active: false,
|
|
1218
|
+
input: "",
|
|
1219
|
+
error: "",
|
|
1220
|
+
remember: false,
|
|
1221
|
+
busy: false
|
|
1222
|
+
});
|
|
1223
|
+
/** Password the visitor typed for the listed folder (single-path mode). */
|
|
1224
|
+
const rootPassword = ref("");
|
|
452
1225
|
const keyword = ref("");
|
|
453
1226
|
const selected = ref(/* @__PURE__ */ new Set());
|
|
454
1227
|
const toast = ref("");
|
|
455
1228
|
const sortKey = ref("name");
|
|
456
1229
|
const sortAsc = ref(true);
|
|
457
1230
|
const siteTitle = ref("");
|
|
1231
|
+
/** OpenList's public `iframe_previews` setting (JSON text). */
|
|
1232
|
+
const iframePreviews = ref("");
|
|
458
1233
|
const batchBusy = ref(false);
|
|
459
1234
|
const batchProgress = ref(0);
|
|
460
1235
|
const downloadCache = /* @__PURE__ */ new Map();
|
|
@@ -470,6 +1245,90 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
470
1245
|
function isPasswordError(error) {
|
|
471
1246
|
return /password|密码/i.test(errorText(error));
|
|
472
1247
|
}
|
|
1248
|
+
function isNetworkError(error) {
|
|
1249
|
+
return /failed to fetch|networkerror|load failed|network request failed/i.test(errorText(error));
|
|
1250
|
+
}
|
|
1251
|
+
/** A failure toast; browser network errors are usually CORS, so say where to look. */
|
|
1252
|
+
function failureMessage(key, error) {
|
|
1253
|
+
const message = t(key, { message: errorText(error) });
|
|
1254
|
+
return isNetworkError(error) ? `${message} ${t("networkHint")}` : message;
|
|
1255
|
+
}
|
|
1256
|
+
/** Total size for display: an empty total reads `0 B`, not `-`. */
|
|
1257
|
+
function formatTotal(bytes) {
|
|
1258
|
+
return bytes > 0 ? formatSize(bytes) : "0 B";
|
|
1259
|
+
}
|
|
1260
|
+
const canRemember = computed(() => props.rememberPassword !== false);
|
|
1261
|
+
const passwordScope = computed(() => baseUrl.value || "demo");
|
|
1262
|
+
/** Passwords typed on this page (not necessarily remembered), so a refresh keeps folders open. */
|
|
1263
|
+
const typedPasswords = /* @__PURE__ */ new Map();
|
|
1264
|
+
function typedPasswordFor(path) {
|
|
1265
|
+
let best = "";
|
|
1266
|
+
let bestPath = "";
|
|
1267
|
+
for (const [folder, password] of typedPasswords) if (isWithin(path, folder) && folder.length > bestPath.length) {
|
|
1268
|
+
best = password;
|
|
1269
|
+
bestPath = folder;
|
|
1270
|
+
}
|
|
1271
|
+
return best;
|
|
1272
|
+
}
|
|
1273
|
+
/** Folders with a remembered password (mirrors sessionStorage so the template reacts). */
|
|
1274
|
+
const rememberedPaths = ref(/* @__PURE__ */ new Set());
|
|
1275
|
+
function syncRemembered() {
|
|
1276
|
+
rememberedPaths.value = new Set(canRemember.value ? rememberedFolders(passwordScope.value) : []);
|
|
1277
|
+
}
|
|
1278
|
+
function storedPassword(path) {
|
|
1279
|
+
return canRemember.value ? findRememberedPassword(passwordScope.value, path) : null;
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Password for an item: its own or inherited one first (site-configured or
|
|
1283
|
+
* typed), then one remembered in this tab, then the component's `password`.
|
|
1284
|
+
*/
|
|
1285
|
+
function passwordFor(node) {
|
|
1286
|
+
if (node.password) return { password: node.password };
|
|
1287
|
+
const typed = typedPasswordFor(node.path);
|
|
1288
|
+
if (typed) return { password: typed };
|
|
1289
|
+
const stored = storedPassword(node.path);
|
|
1290
|
+
if (stored) return {
|
|
1291
|
+
password: stored.password,
|
|
1292
|
+
rememberedAt: stored.path
|
|
1293
|
+
};
|
|
1294
|
+
return { password: props.password };
|
|
1295
|
+
}
|
|
1296
|
+
function rootPasswordFor() {
|
|
1297
|
+
if (rootPassword.value) return { password: rootPassword.value };
|
|
1298
|
+
const stored = storedPassword(props.path);
|
|
1299
|
+
if (stored) return {
|
|
1300
|
+
password: stored.password,
|
|
1301
|
+
rememberedAt: stored.path
|
|
1302
|
+
};
|
|
1303
|
+
return { password: props.password };
|
|
1304
|
+
}
|
|
1305
|
+
/** A remembered password stopped working: drop it so we do not keep retrying it. */
|
|
1306
|
+
function dropStale(path) {
|
|
1307
|
+
if (!path) return false;
|
|
1308
|
+
forgetFolderPassword(passwordScope.value, path);
|
|
1309
|
+
syncRemembered();
|
|
1310
|
+
return true;
|
|
1311
|
+
}
|
|
1312
|
+
function focusUnlock(path) {
|
|
1313
|
+
nextTick(() => {
|
|
1314
|
+
(rootEl.value?.querySelector(`input[data-unlock="${CSS.escape(path)}"]`))?.focus();
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
/** Show the same-row password input; `focus` only for user actions, never on page load. */
|
|
1318
|
+
function startPrompt(node, focus) {
|
|
1319
|
+
node.prompting = true;
|
|
1320
|
+
if (focus) focusUnlock(node.path);
|
|
1321
|
+
}
|
|
1322
|
+
function cancelUnlock(node) {
|
|
1323
|
+
node.prompting = false;
|
|
1324
|
+
node.unlockInput = "";
|
|
1325
|
+
node.unlockError = "";
|
|
1326
|
+
}
|
|
1327
|
+
function unlockErrorText(error) {
|
|
1328
|
+
if (error === "wrong") return t("passwordWrong");
|
|
1329
|
+
if (error === "stale") return t("passwordStale");
|
|
1330
|
+
return "";
|
|
1331
|
+
}
|
|
473
1332
|
const DEMO_TREE = {
|
|
474
1333
|
"/": [
|
|
475
1334
|
{
|
|
@@ -501,6 +1360,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
501
1360
|
size: 0,
|
|
502
1361
|
is_dir: true,
|
|
503
1362
|
modified: "2026-09-01T08:00:00+08:00"
|
|
1363
|
+
},
|
|
1364
|
+
{
|
|
1365
|
+
name: "加密示例(密码 1234)",
|
|
1366
|
+
size: 0,
|
|
1367
|
+
is_dir: true,
|
|
1368
|
+
modified: "2026-10-20T18:00:00+08:00"
|
|
504
1369
|
}
|
|
505
1370
|
],
|
|
506
1371
|
"/assignments": [{
|
|
@@ -591,6 +1456,17 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
591
1456
|
is_dir: false,
|
|
592
1457
|
modified: "2026-01-05T19:02:00+08:00"
|
|
593
1458
|
}],
|
|
1459
|
+
"/加密示例(密码 1234)": [{
|
|
1460
|
+
name: "期末答案(教师版).pdf",
|
|
1461
|
+
size: 2621440,
|
|
1462
|
+
is_dir: false,
|
|
1463
|
+
modified: "2026-10-20T18:00:00+08:00"
|
|
1464
|
+
}, {
|
|
1465
|
+
name: "成绩登记表.xlsx",
|
|
1466
|
+
size: 48128,
|
|
1467
|
+
is_dir: false,
|
|
1468
|
+
modified: "2026-10-21T09:12:00+08:00"
|
|
1469
|
+
}],
|
|
594
1470
|
"/slides": [{
|
|
595
1471
|
name: "第一章 行列式.pptx",
|
|
596
1472
|
size: 3145728,
|
|
@@ -603,6 +1479,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
603
1479
|
modified: "2026-09-15T13:30:00+08:00"
|
|
604
1480
|
}]
|
|
605
1481
|
};
|
|
1482
|
+
/** Demo-only folder passwords, so the demo can show the password flow. */
|
|
1483
|
+
const DEMO_PASSWORDS = { "/加密示例(密码 1234)": "1234" };
|
|
606
1484
|
class ApiError extends Error {
|
|
607
1485
|
code;
|
|
608
1486
|
constructor(message, code) {
|
|
@@ -610,16 +1488,17 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
610
1488
|
this.code = code;
|
|
611
1489
|
}
|
|
612
1490
|
}
|
|
613
|
-
|
|
1491
|
+
/** JSON content type only when there is a body: a bare GET then needs no CORS preflight. */
|
|
1492
|
+
function authHeaders(json) {
|
|
614
1493
|
return {
|
|
615
|
-
"Content-Type": "application/json",
|
|
1494
|
+
...json ? { "Content-Type": "application/json" } : {},
|
|
616
1495
|
...props.token ? { Authorization: props.token } : {}
|
|
617
1496
|
};
|
|
618
1497
|
}
|
|
619
1498
|
async function request(apiPath, body) {
|
|
620
1499
|
const response = await fetch(`${baseUrl.value}${apiPath}`, {
|
|
621
1500
|
method: body === void 0 ? "GET" : "POST",
|
|
622
|
-
headers: authHeaders(),
|
|
1501
|
+
headers: authHeaders(body !== void 0),
|
|
623
1502
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
624
1503
|
});
|
|
625
1504
|
const payload = await response.json().catch(() => null);
|
|
@@ -627,9 +1506,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
627
1506
|
if (payload.code !== 200) throw new ApiError(payload.message || `HTTP ${response.status}`, payload.code ?? response.status);
|
|
628
1507
|
return payload.data;
|
|
629
1508
|
}
|
|
630
|
-
async function listDir(path, password
|
|
1509
|
+
async function listDir(path, password) {
|
|
631
1510
|
if (demo.value) {
|
|
632
1511
|
await new Promise((resolve) => setTimeout(resolve, 220));
|
|
1512
|
+
const guard = Object.entries(DEMO_PASSWORDS).find(([folder]) => isWithin(path, folder));
|
|
1513
|
+
if (guard && guard[1] !== password) throw new ApiError("password is incorrect or you have no permission", 403);
|
|
633
1514
|
return DEMO_TREE[path] ?? [];
|
|
634
1515
|
}
|
|
635
1516
|
return (await request("/api/fs/list", {
|
|
@@ -659,25 +1540,28 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
659
1540
|
try {
|
|
660
1541
|
url = absoluteUrl((await request("/api/fs/get", {
|
|
661
1542
|
path: node.path,
|
|
662
|
-
password: node.password
|
|
1543
|
+
password: passwordFor(node).password
|
|
663
1544
|
}))?.raw_url ?? "");
|
|
664
1545
|
} catch {
|
|
665
1546
|
url = "";
|
|
666
1547
|
}
|
|
667
|
-
if (!url)
|
|
668
|
-
const encoded = node.path.split("/").map(encodeURIComponent).join("/");
|
|
669
|
-
url = `${baseUrl.value}/d${encoded}${node.sign ? `?sign=${encodeURIComponent(node.sign)}` : ""}`;
|
|
670
|
-
}
|
|
1548
|
+
if (!url) url = directUrl(node);
|
|
671
1549
|
downloadCache.set(node.path, url);
|
|
672
1550
|
return url;
|
|
673
1551
|
}
|
|
1552
|
+
/** OpenList's signed `/d/` download link for a file. */
|
|
1553
|
+
function directUrl(node) {
|
|
1554
|
+
return `${baseUrl.value}/d${encodePath(node.path)}${node.sign ? `?sign=${encodeURIComponent(node.sign)}` : ""}`;
|
|
1555
|
+
}
|
|
674
1556
|
async function loadSiteTitle() {
|
|
675
1557
|
if (demo.value) return;
|
|
676
1558
|
try {
|
|
677
1559
|
const data = await request("/api/public/settings");
|
|
678
1560
|
siteTitle.value = data?.site_title || data?.title || "";
|
|
1561
|
+
iframePreviews.value = data?.iframe_previews || "";
|
|
679
1562
|
} catch {
|
|
680
1563
|
siteTitle.value = "";
|
|
1564
|
+
iframePreviews.value = "";
|
|
681
1565
|
}
|
|
682
1566
|
}
|
|
683
1567
|
function sortTree(nodes) {
|
|
@@ -714,95 +1598,297 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
714
1598
|
const parts = path.split("/").filter(Boolean);
|
|
715
1599
|
return parts[parts.length - 1] || path;
|
|
716
1600
|
}
|
|
717
|
-
|
|
1601
|
+
/**
|
|
1602
|
+
* Load a folder's children. A password error marks the folder locked instead of
|
|
1603
|
+
* toasting; `prompt` also shows its same-row input (and focuses it).
|
|
1604
|
+
*/
|
|
1605
|
+
async function loadChildren(node, prompt = false) {
|
|
718
1606
|
if (node.loaded) return true;
|
|
1607
|
+
if (node.locked) {
|
|
1608
|
+
if (prompt) startPrompt(node, prompt === "focus");
|
|
1609
|
+
return false;
|
|
1610
|
+
}
|
|
719
1611
|
node.loading = true;
|
|
1612
|
+
const { password, rememberedAt } = passwordFor(node);
|
|
720
1613
|
try {
|
|
721
|
-
node.children = toNodes(await listDir(node.path,
|
|
1614
|
+
node.children = toNodes(await listDir(node.path, password), node.path, node.depth + 1, node.password, node.zipPath);
|
|
722
1615
|
sortTree(node.children);
|
|
723
1616
|
node.loaded = true;
|
|
724
1617
|
return true;
|
|
725
1618
|
} catch (error) {
|
|
726
|
-
if (isPasswordError(error))
|
|
727
|
-
|
|
1619
|
+
if (isPasswordError(error)) {
|
|
1620
|
+
node.locked = true;
|
|
1621
|
+
node.expanded = false;
|
|
1622
|
+
node.unlockError = dropStale(rememberedAt) ? "stale" : "";
|
|
1623
|
+
if (prompt) startPrompt(node, prompt === "focus");
|
|
1624
|
+
} else showToast(failureMessage("folderFailed", error));
|
|
728
1625
|
return false;
|
|
729
1626
|
} finally {
|
|
730
1627
|
node.loading = false;
|
|
731
1628
|
}
|
|
732
1629
|
}
|
|
733
|
-
|
|
1630
|
+
/** Submit the same-row password for a locked folder (or a locked root of unknown type). */
|
|
1631
|
+
async function submitUnlock(node) {
|
|
1632
|
+
const password = node.unlockInput ?? "";
|
|
1633
|
+
if (!password || node.unlockBusy) return;
|
|
1634
|
+
node.unlockBusy = true;
|
|
1635
|
+
try {
|
|
1636
|
+
if (node.unresolved) {
|
|
1637
|
+
const data = await request("/api/fs/get", {
|
|
1638
|
+
path: node.path,
|
|
1639
|
+
password
|
|
1640
|
+
});
|
|
1641
|
+
const name = node.name;
|
|
1642
|
+
Object.assign(node, data, { name });
|
|
1643
|
+
if (node.is_dir) node.zipPath = [name];
|
|
1644
|
+
else node.zipPath = [data.name || node.zipPath[0]];
|
|
1645
|
+
node.unresolved = false;
|
|
1646
|
+
}
|
|
1647
|
+
if (node.is_dir) {
|
|
1648
|
+
node.children = toNodes(await listDir(node.path, password), node.path, node.depth + 1, password, node.zipPath);
|
|
1649
|
+
sortTree(node.children);
|
|
1650
|
+
node.loaded = true;
|
|
1651
|
+
node.expanded = true;
|
|
1652
|
+
}
|
|
1653
|
+
node.password = password;
|
|
1654
|
+
typedPasswords.set(node.path, password);
|
|
1655
|
+
if (node.unlockRemember && canRemember.value) {
|
|
1656
|
+
rememberFolderPassword(passwordScope.value, node.path, password);
|
|
1657
|
+
syncRemembered();
|
|
1658
|
+
}
|
|
1659
|
+
node.locked = false;
|
|
1660
|
+
node.prompting = false;
|
|
1661
|
+
node.unlockError = "";
|
|
1662
|
+
node.unlockInput = "";
|
|
1663
|
+
node.unlockRemember = false;
|
|
1664
|
+
} catch (error) {
|
|
1665
|
+
if (isPasswordError(error)) {
|
|
1666
|
+
node.unlockError = "wrong";
|
|
1667
|
+
node.unlockInput = "";
|
|
1668
|
+
focusUnlock(node.path);
|
|
1669
|
+
} else showToast(failureMessage("folderFailed", error));
|
|
1670
|
+
} finally {
|
|
1671
|
+
node.unlockBusy = false;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
/** Forget a remembered password: the folder folds up and asks again next time. */
|
|
1675
|
+
function forgetPassword(node) {
|
|
1676
|
+
forgetFolderPassword(passwordScope.value, node.path);
|
|
1677
|
+
typedPasswords.delete(node.path);
|
|
1678
|
+
syncRemembered();
|
|
1679
|
+
node.password = node.configuredPassword || "";
|
|
1680
|
+
node.expanded = false;
|
|
1681
|
+
node.loaded = false;
|
|
1682
|
+
node.children = [];
|
|
1683
|
+
node.locked = false;
|
|
1684
|
+
selected.value = new Set([...selected.value].filter((path) => !isWithin(path, node.path)));
|
|
1685
|
+
for (const key of [...downloadCache.keys()]) if (isWithin(key, node.path)) downloadCache.delete(key);
|
|
1686
|
+
}
|
|
1687
|
+
/** When OpenList cannot tell us, guess from the name: a known file extension means a file. */
|
|
1688
|
+
function looksLikeFile(path) {
|
|
1689
|
+
const name = sourceName(path);
|
|
1690
|
+
return fileKind(name) !== "other" || previewKind(name) !== "none";
|
|
1691
|
+
}
|
|
1692
|
+
async function loadSourceRoot(source, expanded) {
|
|
734
1693
|
const path = source.path;
|
|
735
|
-
const
|
|
1694
|
+
const configured = source.password ?? "";
|
|
736
1695
|
let item = {
|
|
737
1696
|
name: sourceName(path),
|
|
738
1697
|
size: 0,
|
|
739
|
-
is_dir:
|
|
1698
|
+
is_dir: !looksLikeFile(path),
|
|
740
1699
|
modified: ""
|
|
741
1700
|
};
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1701
|
+
let locked = false;
|
|
1702
|
+
let unlockError = "";
|
|
1703
|
+
if (!demo.value) {
|
|
1704
|
+
const { password, rememberedAt } = configured ? {
|
|
1705
|
+
password: configured,
|
|
1706
|
+
rememberedAt: void 0
|
|
1707
|
+
} : (() => {
|
|
1708
|
+
const stored = storedPassword(path);
|
|
1709
|
+
return stored ? {
|
|
1710
|
+
password: stored.password,
|
|
1711
|
+
rememberedAt: stored.path
|
|
1712
|
+
} : {
|
|
1713
|
+
password: props.password,
|
|
1714
|
+
rememberedAt: void 0
|
|
1715
|
+
};
|
|
1716
|
+
})();
|
|
1717
|
+
try {
|
|
1718
|
+
const data = await request("/api/fs/get", {
|
|
1719
|
+
path,
|
|
1720
|
+
password
|
|
1721
|
+
});
|
|
1722
|
+
item = {
|
|
1723
|
+
...item,
|
|
1724
|
+
...data
|
|
1725
|
+
};
|
|
1726
|
+
} catch (error) {
|
|
1727
|
+
if (isPasswordError(error)) {
|
|
1728
|
+
locked = true;
|
|
1729
|
+
unlockError = dropStale(rememberedAt) ? "stale" : "";
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
758
1733
|
const name = source.title || item.name || sourceName(path);
|
|
759
1734
|
return {
|
|
760
1735
|
...item,
|
|
761
1736
|
name,
|
|
762
1737
|
path,
|
|
763
1738
|
depth: 0,
|
|
764
|
-
expanded
|
|
1739
|
+
expanded,
|
|
765
1740
|
loaded: false,
|
|
766
1741
|
loading: false,
|
|
767
1742
|
children: [],
|
|
768
|
-
password,
|
|
1743
|
+
password: configured,
|
|
1744
|
+
configuredPassword: configured,
|
|
769
1745
|
isRoot: true,
|
|
1746
|
+
locked,
|
|
1747
|
+
unresolved: locked,
|
|
1748
|
+
unlockError,
|
|
1749
|
+
prompting: locked && expanded,
|
|
770
1750
|
zipPath: [item.is_dir ? name : item.name || sourceName(path)]
|
|
771
1751
|
};
|
|
772
1752
|
}
|
|
1753
|
+
class RootLockedError extends Error {
|
|
1754
|
+
rememberedAt;
|
|
1755
|
+
constructor(rememberedAt) {
|
|
1756
|
+
super("root locked");
|
|
1757
|
+
this.rememberedAt = rememberedAt;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Build a fresh tree. `expandedPaths` re-opens those folders (refresh keeps
|
|
1762
|
+
* what was open); without it the configured `expanded` defaults apply.
|
|
1763
|
+
*/
|
|
1764
|
+
async function buildTree(expandedPaths) {
|
|
1765
|
+
let nodes;
|
|
1766
|
+
if (props.sources.length) nodes = await Promise.all(props.sources.map((source) => loadSourceRoot(source, expandedPaths ? expandedPaths.has(source.path) : source.expanded ?? props.expanded)));
|
|
1767
|
+
else {
|
|
1768
|
+
const { password, rememberedAt } = rootPasswordFor();
|
|
1769
|
+
let items;
|
|
1770
|
+
try {
|
|
1771
|
+
items = await listDir(props.path, password);
|
|
1772
|
+
} catch (error) {
|
|
1773
|
+
if (isPasswordError(error)) throw new RootLockedError(rememberedAt);
|
|
1774
|
+
throw error;
|
|
1775
|
+
}
|
|
1776
|
+
nodes = toNodes(items, props.path, 0, rootPassword.value);
|
|
1777
|
+
sortTree(nodes);
|
|
1778
|
+
}
|
|
1779
|
+
const open = async (list) => {
|
|
1780
|
+
for (const node of list) {
|
|
1781
|
+
if (!node.is_dir || node.locked || !node.expanded && !expandedPaths?.has(node.path)) continue;
|
|
1782
|
+
node.expanded = await loadChildren(node, "show");
|
|
1783
|
+
if (node.expanded && expandedPaths) await open(node.children);
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
await open(nodes);
|
|
1787
|
+
return nodes;
|
|
1788
|
+
}
|
|
1789
|
+
/** First load, or a retry after an error: shows the loading state. */
|
|
773
1790
|
async function loadRoot() {
|
|
1791
|
+
refreshSeq += 1;
|
|
1792
|
+
refreshing.value = false;
|
|
774
1793
|
loadingRoot.value = true;
|
|
775
1794
|
errorMsg.value = "";
|
|
776
|
-
needsPassword.value = false;
|
|
777
1795
|
try {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1796
|
+
roots.value = await buildTree();
|
|
1797
|
+
rootLock.active = false;
|
|
1798
|
+
selected.value = /* @__PURE__ */ new Set();
|
|
1799
|
+
downloadCache.clear();
|
|
1800
|
+
} catch (error) {
|
|
1801
|
+
if (error instanceof RootLockedError) {
|
|
1802
|
+
roots.value = [];
|
|
1803
|
+
rootLock.active = true;
|
|
1804
|
+
rootLock.error = dropStale(error.rememberedAt) ? "stale" : "";
|
|
1805
|
+
} else errorMsg.value = errorText(error);
|
|
1806
|
+
} finally {
|
|
1807
|
+
loadingRoot.value = false;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
function expandedPathsOf(nodes, out = /* @__PURE__ */ new Set()) {
|
|
1811
|
+
for (const node of nodes) if (node.is_dir && node.expanded) {
|
|
1812
|
+
out.add(node.path);
|
|
1813
|
+
expandedPathsOf(node.children, out);
|
|
1814
|
+
}
|
|
1815
|
+
return out;
|
|
1816
|
+
}
|
|
1817
|
+
/**
|
|
1818
|
+
* Refresh keeps the old rows in place (dimmed) until the new tree is ready, then
|
|
1819
|
+
* swaps it in. Open folders stay open; selected files that still exist stay selected.
|
|
1820
|
+
*/
|
|
1821
|
+
async function refresh() {
|
|
1822
|
+
if (refreshing.value || loadingRoot.value) return;
|
|
1823
|
+
if (!roots.value.length || errorMsg.value || rootLock.active) {
|
|
1824
|
+
await loadRoot();
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
const seq = ++refreshSeq;
|
|
1828
|
+
refreshing.value = true;
|
|
1829
|
+
try {
|
|
1830
|
+
const fresh = await buildTree(expandedPathsOf(roots.value));
|
|
1831
|
+
if (seq !== refreshSeq) return;
|
|
1832
|
+
roots.value = fresh;
|
|
1833
|
+
const existing = new Set(fresh.flatMap(descendantFiles).map((file) => file.path));
|
|
1834
|
+
selected.value = new Set([...selected.value].filter((path) => existing.has(path)));
|
|
1835
|
+
downloadCache.clear();
|
|
1836
|
+
} catch (error) {
|
|
1837
|
+
if (seq !== refreshSeq) return;
|
|
1838
|
+
if (error instanceof RootLockedError) {
|
|
1839
|
+
roots.value = [];
|
|
1840
|
+
rootLock.active = true;
|
|
1841
|
+
rootLock.error = dropStale(error.rememberedAt) ? "stale" : "wrong";
|
|
782
1842
|
selected.value = /* @__PURE__ */ new Set();
|
|
783
|
-
|
|
784
|
-
|
|
1843
|
+
} else showToast(failureMessage("refreshFailed", error));
|
|
1844
|
+
} finally {
|
|
1845
|
+
if (seq === refreshSeq) refreshing.value = false;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
/** Single-path mode: unlock the listed folder itself. */
|
|
1849
|
+
async function submitRootUnlock() {
|
|
1850
|
+
const password = rootLock.input;
|
|
1851
|
+
if (!password || rootLock.busy) return;
|
|
1852
|
+
rootLock.busy = true;
|
|
1853
|
+
try {
|
|
1854
|
+
const items = await listDir(props.path, password);
|
|
1855
|
+
rootPassword.value = password;
|
|
1856
|
+
if (rootLock.remember && canRemember.value) {
|
|
1857
|
+
rememberFolderPassword(passwordScope.value, props.path, password);
|
|
1858
|
+
syncRemembered();
|
|
785
1859
|
}
|
|
786
|
-
const
|
|
787
|
-
|
|
788
|
-
|
|
1860
|
+
const nodes = toNodes(items, props.path, 0, password);
|
|
1861
|
+
sortTree(nodes);
|
|
1862
|
+
roots.value = nodes;
|
|
1863
|
+
Object.assign(rootLock, {
|
|
1864
|
+
active: false,
|
|
1865
|
+
input: "",
|
|
1866
|
+
error: "",
|
|
1867
|
+
remember: false
|
|
1868
|
+
});
|
|
789
1869
|
selected.value = /* @__PURE__ */ new Set();
|
|
790
1870
|
downloadCache.clear();
|
|
791
1871
|
} catch (error) {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
1872
|
+
if (isPasswordError(error)) {
|
|
1873
|
+
rootLock.error = "wrong";
|
|
1874
|
+
rootLock.input = "";
|
|
1875
|
+
focusUnlock(props.path);
|
|
1876
|
+
} else showToast(failureMessage("loadFailed", error));
|
|
795
1877
|
} finally {
|
|
796
|
-
|
|
1878
|
+
rootLock.busy = false;
|
|
797
1879
|
}
|
|
798
1880
|
}
|
|
799
1881
|
async function toggleExpand(node) {
|
|
1882
|
+
if (node.locked) {
|
|
1883
|
+
startPrompt(node, true);
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
800
1886
|
if (!node.is_dir) return;
|
|
801
1887
|
if (node.expanded) {
|
|
802
1888
|
node.expanded = false;
|
|
803
1889
|
return;
|
|
804
1890
|
}
|
|
805
|
-
if (!await loadChildren(node)) return;
|
|
1891
|
+
if (!await loadChildren(node, "focus")) return;
|
|
806
1892
|
node.expanded = true;
|
|
807
1893
|
}
|
|
808
1894
|
const visible = computed(() => {
|
|
@@ -832,18 +1918,33 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
832
1918
|
const hit = files.filter((file) => selected.value.has(file.path)).length;
|
|
833
1919
|
return hit > 0 && hit < files.length;
|
|
834
1920
|
}
|
|
835
|
-
/**
|
|
1921
|
+
/**
|
|
1922
|
+
* Load a directory and all of its sub-directories. Stops at the first folder
|
|
1923
|
+
* that needs a password and returns it, so nothing is silently left out.
|
|
1924
|
+
*/
|
|
836
1925
|
async function loadTree(node) {
|
|
837
|
-
if (!node.is_dir) return true;
|
|
838
|
-
if (!await loadChildren(node)) return
|
|
839
|
-
|
|
840
|
-
|
|
1926
|
+
if (!node.is_dir) return { ok: true };
|
|
1927
|
+
if (!await loadChildren(node)) return node.locked ? {
|
|
1928
|
+
ok: false,
|
|
1929
|
+
locked: node
|
|
1930
|
+
} : { ok: false };
|
|
1931
|
+
for (const child of node.children) {
|
|
1932
|
+
if (!child.is_dir) continue;
|
|
1933
|
+
const result = await loadTree(child);
|
|
1934
|
+
if (!result.ok) {
|
|
1935
|
+
node.expanded = true;
|
|
1936
|
+
return result;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
return { ok: true };
|
|
841
1940
|
}
|
|
842
1941
|
async function toggleCheck(node, event) {
|
|
843
1942
|
const input = event?.target;
|
|
844
1943
|
if (node.is_dir && !isChecked(node)) {
|
|
845
|
-
|
|
1944
|
+
const result = await loadTree(node);
|
|
1945
|
+
if (result.ok) node.expanded = true;
|
|
846
1946
|
else {
|
|
1947
|
+
if (result.locked) startPrompt(result.locked, true);
|
|
847
1948
|
if (input) input.checked = isChecked(node);
|
|
848
1949
|
return;
|
|
849
1950
|
}
|
|
@@ -928,21 +2029,55 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
928
2029
|
showToast(t("downloadFailed", { message: errorText(error) }));
|
|
929
2030
|
}
|
|
930
2031
|
}
|
|
931
|
-
|
|
2032
|
+
/** Only types the dialog can actually render get a preview button. */
|
|
932
2033
|
function canPreview(node) {
|
|
933
|
-
return !node.is_dir &&
|
|
2034
|
+
return !node.is_dir && previewKind(node.name) !== "none";
|
|
934
2035
|
}
|
|
2036
|
+
const previewNode = ref(null);
|
|
2037
|
+
const previewSrc = ref("");
|
|
2038
|
+
const previewViewer = ref("");
|
|
2039
|
+
const previewResolving = ref(false);
|
|
2040
|
+
const previewError = ref("");
|
|
2041
|
+
let previewSeq = 0;
|
|
2042
|
+
/** Previewable files in the order they are listed, for ← / →. */
|
|
2043
|
+
const previewList = computed(() => visible.value.filter(canPreview));
|
|
2044
|
+
const previewPosition = computed(() => previewNode.value ? previewList.value.indexOf(previewNode.value) + 1 : 0);
|
|
935
2045
|
async function preview(node) {
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
2046
|
+
const seq = ++previewSeq;
|
|
2047
|
+
previewNode.value = node;
|
|
2048
|
+
previewSrc.value = "";
|
|
2049
|
+
previewViewer.value = "";
|
|
2050
|
+
previewError.value = "";
|
|
2051
|
+
previewResolving.value = false;
|
|
2052
|
+
if (demo.value || !canPreview(node)) return;
|
|
2053
|
+
previewResolving.value = true;
|
|
940
2054
|
try {
|
|
941
|
-
|
|
2055
|
+
const url = await resolveUrl(node);
|
|
2056
|
+
if (seq !== previewSeq) return;
|
|
2057
|
+
previewSrc.value = url;
|
|
2058
|
+
if (previewKind(node.name) === "pdf") {
|
|
2059
|
+
const viewer = iframePreviewsFor(node.name, iframePreviews.value)[0];
|
|
2060
|
+
if (viewer) previewViewer.value = fillPreviewUrl(viewer.scheme, {
|
|
2061
|
+
name: node.name,
|
|
2062
|
+
rawUrl: url,
|
|
2063
|
+
downloadUrl: directUrl(node)
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
942
2066
|
} catch (error) {
|
|
943
|
-
|
|
2067
|
+
if (seq === previewSeq) previewError.value = errorText(error);
|
|
2068
|
+
} finally {
|
|
2069
|
+
if (seq === previewSeq) previewResolving.value = false;
|
|
944
2070
|
}
|
|
945
2071
|
}
|
|
2072
|
+
function stepPreview(delta) {
|
|
2073
|
+
const list = previewList.value;
|
|
2074
|
+
if (!list.length) return;
|
|
2075
|
+
preview(list[((previewNode.value ? list.indexOf(previewNode.value) : -1) + delta + list.length) % list.length]);
|
|
2076
|
+
}
|
|
2077
|
+
function closePreview() {
|
|
2078
|
+
previewSeq += 1;
|
|
2079
|
+
previewNode.value = null;
|
|
2080
|
+
}
|
|
946
2081
|
async function batchDownload() {
|
|
947
2082
|
const files = allFiles.value.filter((file) => selected.value.has(file.path));
|
|
948
2083
|
if (!files.length) return;
|
|
@@ -982,11 +2117,6 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
982
2117
|
batchBusy.value = false;
|
|
983
2118
|
}
|
|
984
2119
|
}
|
|
985
|
-
function submitPassword() {
|
|
986
|
-
activePassword.value = passwordInput.value;
|
|
987
|
-
downloadCache.clear();
|
|
988
|
-
loadRoot();
|
|
989
|
-
}
|
|
990
2120
|
const displayTitle = computed(() => props.title || siteTitle.value || (props.path === "/" ? t("allFiles") : props.path));
|
|
991
2121
|
const stickyTop = ref(0);
|
|
992
2122
|
function measureStickyTop() {
|
|
@@ -1000,6 +2130,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1000
2130
|
stickyTop.value = top;
|
|
1001
2131
|
}
|
|
1002
2132
|
onMounted(() => {
|
|
2133
|
+
syncRemembered();
|
|
1003
2134
|
loadRoot();
|
|
1004
2135
|
loadSiteTitle();
|
|
1005
2136
|
measureStickyTop();
|
|
@@ -1011,11 +2142,17 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1011
2142
|
});
|
|
1012
2143
|
return (_ctx, _cache) => {
|
|
1013
2144
|
return openBlock(), createElementBlock("div", {
|
|
1014
|
-
|
|
1015
|
-
|
|
2145
|
+
ref_key: "rootEl",
|
|
2146
|
+
ref: rootEl,
|
|
2147
|
+
class: normalizeClass(["fl-root", {
|
|
2148
|
+
"fl-selecting": selected.value.size > 0,
|
|
2149
|
+
"fl-refreshing": refreshing.value
|
|
2150
|
+
}]),
|
|
2151
|
+
style: normalizeStyle({ "--fl-sticky-top-auto": `${stickyTop.value}px` }),
|
|
2152
|
+
"aria-busy": refreshing.value || loadingRoot.value
|
|
1016
2153
|
}, [
|
|
1017
|
-
createElementVNode("div",
|
|
1018
|
-
selected.value.size ? (openBlock(), createElementBlock("div",
|
|
2154
|
+
createElementVNode("div", _hoisted_2, [
|
|
2155
|
+
selected.value.size ? (openBlock(), createElementBlock("div", _hoisted_3, [createElementVNode("span", _hoisted_4, toDisplayString(t("selected", { count: selected.value.size })), 1), createElementVNode("span", _hoisted_5, toDisplayString(t("totalSize", { size: formatTotal(selectedBytes.value) })), 1)])) : (openBlock(), createElementBlock("div", _hoisted_6, [createElementVNode("span", _hoisted_7, toDisplayString(displayTitle.value), 1), demo.value ? (openBlock(), createElementBlock("span", _hoisted_8, toDisplayString(t("demoBadge")), 1)) : createCommentVNode("v-if", true)])),
|
|
1019
2156
|
selected.value.size ? (openBlock(), createElementBlock("button", {
|
|
1020
2157
|
key: 2,
|
|
1021
2158
|
class: "fl-clear",
|
|
@@ -1023,7 +2160,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1023
2160
|
"aria-label": t("clearSelection"),
|
|
1024
2161
|
title: t("clearSelection"),
|
|
1025
2162
|
onClick: clearSelection
|
|
1026
|
-
}, [_cache[
|
|
2163
|
+
}, [_cache[10] || (_cache[10] = createElementVNode("svg", {
|
|
1027
2164
|
class: "fl-clear-icon",
|
|
1028
2165
|
viewBox: "0 0 24 24",
|
|
1029
2166
|
width: "18",
|
|
@@ -1033,8 +2170,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1033
2170
|
"stroke-width": "2.2",
|
|
1034
2171
|
"stroke-linecap": "round",
|
|
1035
2172
|
"aria-hidden": "true"
|
|
1036
|
-
}, [createElementVNode("path", { d: "M6 6l12 12" }), createElementVNode("path", { d: "M18 6 6 18" })], -1)), createElementVNode("span",
|
|
1037
|
-
createElementVNode("div",
|
|
2173
|
+
}, [createElementVNode("path", { d: "M6 6l12 12" }), createElementVNode("path", { d: "M18 6 6 18" })], -1)), createElementVNode("span", _hoisted_10, toDisplayString(t("clear")), 1)], 8, _hoisted_9)) : createCommentVNode("v-if", true),
|
|
2174
|
+
createElementVNode("div", _hoisted_11, [createElementVNode("label", _hoisted_12, [_cache[11] || (_cache[11] = createElementVNode("svg", {
|
|
1038
2175
|
viewBox: "0 0 24 24",
|
|
1039
2176
|
width: "15",
|
|
1040
2177
|
height: "15",
|
|
@@ -1052,13 +2189,14 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1052
2189
|
type: "text",
|
|
1053
2190
|
placeholder: t("filter"),
|
|
1054
2191
|
"aria-label": t("filter")
|
|
1055
|
-
}, null, 8,
|
|
1056
|
-
class: "fl-icon-btn",
|
|
2192
|
+
}, null, 8, _hoisted_13), [[vModelText, keyword.value]])]), createElementVNode("button", {
|
|
2193
|
+
class: normalizeClass(["fl-icon-btn fl-refresh", { "fl-spinning": refreshing.value }]),
|
|
1057
2194
|
type: "button",
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
2195
|
+
disabled: refreshing.value || loadingRoot.value,
|
|
2196
|
+
"aria-label": refreshing.value ? t("refreshing") : t("refresh"),
|
|
2197
|
+
title: refreshing.value ? t("refreshing") : t("refresh"),
|
|
2198
|
+
onClick: refresh
|
|
2199
|
+
}, [..._cache[12] || (_cache[12] = [createElementVNode("svg", {
|
|
1062
2200
|
viewBox: "0 0 24 24",
|
|
1063
2201
|
width: "15",
|
|
1064
2202
|
height: "15",
|
|
@@ -1068,14 +2206,14 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1068
2206
|
"stroke-linecap": "round",
|
|
1069
2207
|
"stroke-linejoin": "round",
|
|
1070
2208
|
"aria-hidden": "true"
|
|
1071
|
-
}, [createElementVNode("path", { d: "M21 12a9 9 0 1 1-3-6.7" }), createElementVNode("path", { d: "M21 4v5h-5" })], -1)])],
|
|
2209
|
+
}, [createElementVNode("path", { d: "M21 12a9 9 0 1 1-3-6.7" }), createElementVNode("path", { d: "M21 4v5h-5" })], -1)])], 10, _hoisted_14)]),
|
|
1072
2210
|
selected.value.size ? (openBlock(), createElementBlock("button", {
|
|
1073
2211
|
key: 3,
|
|
1074
2212
|
class: "fl-btn fl-btn-primary",
|
|
1075
2213
|
type: "button",
|
|
1076
2214
|
disabled: batchBusy.value,
|
|
1077
2215
|
onClick: batchDownload
|
|
1078
|
-
}, [_cache[
|
|
2216
|
+
}, [_cache[13] || (_cache[13] = createElementVNode("svg", {
|
|
1079
2217
|
viewBox: "0 0 24 24",
|
|
1080
2218
|
width: "15",
|
|
1081
2219
|
height: "15",
|
|
@@ -1089,24 +2227,10 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1089
2227
|
createElementVNode("path", { d: "M12 3v12" }),
|
|
1090
2228
|
createElementVNode("path", { d: "m7 10 5 5 5-5" }),
|
|
1091
2229
|
createElementVNode("path", { d: "M5 21h14" })
|
|
1092
|
-
], -1)), createTextVNode(" " + toDisplayString(batchBusy.value ? t("batching", { percent: batchProgress.value }) : t("batch")), 1)], 8,
|
|
2230
|
+
], -1)), createTextVNode(" " + toDisplayString(batchBusy.value ? t("batching", { percent: batchProgress.value }) : t("batch")), 1)], 8, _hoisted_15)) : createCommentVNode("v-if", true)
|
|
1093
2231
|
]),
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
withDirectives(createElementVNode("input", {
|
|
1097
|
-
"onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => passwordInput.value = $event),
|
|
1098
|
-
type: "password",
|
|
1099
|
-
placeholder: t("passwordPlaceholder"),
|
|
1100
|
-
"aria-label": t("passwordPlaceholder"),
|
|
1101
|
-
onKeyup: withKeys(submitPassword, ["enter"])
|
|
1102
|
-
}, null, 40, _hoisted_16), [[vModelText, passwordInput.value]]),
|
|
1103
|
-
createElementVNode("button", {
|
|
1104
|
-
class: "fl-btn fl-btn-primary",
|
|
1105
|
-
type: "button",
|
|
1106
|
-
onClick: submitPassword
|
|
1107
|
-
}, toDisplayString(t("open")), 1)
|
|
1108
|
-
])) : createCommentVNode("v-if", true),
|
|
1109
|
-
createElementVNode("div", _hoisted_17, [
|
|
2232
|
+
createElementVNode("div", _hoisted_16, [
|
|
2233
|
+
refreshing.value ? (openBlock(), createElementBlock("span", _hoisted_17)) : createCommentVNode("v-if", true),
|
|
1110
2234
|
createElementVNode("label", _hoisted_18, [createElementVNode("input", {
|
|
1111
2235
|
type: "checkbox",
|
|
1112
2236
|
"aria-label": t("selectAll"),
|
|
@@ -1118,23 +2242,23 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1118
2242
|
class: "fl-sort-btn",
|
|
1119
2243
|
type: "button",
|
|
1120
2244
|
"aria-sort": sortKey.value === "name" ? sortAsc.value ? "ascending" : "descending" : void 0,
|
|
1121
|
-
onClick: _cache[
|
|
2245
|
+
onClick: _cache[1] || (_cache[1] = ($event) => setSort("name"))
|
|
1122
2246
|
}, [createTextVNode(toDisplayString(t("columnName")), 1), createElementVNode("span", _hoisted_22, toDisplayString(sortKey.value === "name" ? sortAsc.value ? "▲" : "▼" : ""), 1)], 8, _hoisted_21)]),
|
|
1123
2247
|
createElementVNode("span", _hoisted_23, [createElementVNode("button", {
|
|
1124
2248
|
class: "fl-sort-btn",
|
|
1125
2249
|
type: "button",
|
|
1126
|
-
onClick: _cache[
|
|
2250
|
+
onClick: _cache[2] || (_cache[2] = ($event) => setSort("size"))
|
|
1127
2251
|
}, [createTextVNode(toDisplayString(t("columnSize")), 1), createElementVNode("span", _hoisted_24, toDisplayString(sortKey.value === "size" ? sortAsc.value ? "▲" : "▼" : ""), 1)])]),
|
|
1128
2252
|
createElementVNode("span", _hoisted_25, [createElementVNode("button", {
|
|
1129
2253
|
class: "fl-sort-btn",
|
|
1130
2254
|
type: "button",
|
|
1131
|
-
onClick: _cache[
|
|
2255
|
+
onClick: _cache[3] || (_cache[3] = ($event) => setSort("modified"))
|
|
1132
2256
|
}, [createTextVNode(toDisplayString(t("columnDate")), 1), createElementVNode("span", _hoisted_26, toDisplayString(sortKey.value === "modified" ? sortAsc.value ? "▲" : "▼" : ""), 1)])]),
|
|
1133
2257
|
createElementVNode("span", _hoisted_27, [createElementVNode("label", _hoisted_28, [
|
|
1134
2258
|
createElementVNode("span", null, toDisplayString(t("sortBy")), 1),
|
|
1135
2259
|
createElementVNode("span", _hoisted_29, toDisplayString(sortOptions.value.find((option) => option.value === sortValue.value)?.label), 1),
|
|
1136
2260
|
withDirectives(createElementVNode("select", {
|
|
1137
|
-
"onUpdate:modelValue": _cache[
|
|
2261
|
+
"onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => sortValue.value = $event),
|
|
1138
2262
|
"aria-label": t("sortBy")
|
|
1139
2263
|
}, [(openBlock(true), createElementBlock(Fragment, null, renderList(sortOptions.value, (option) => {
|
|
1140
2264
|
return openBlock(), createElementBlock("option", {
|
|
@@ -1144,49 +2268,148 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1144
2268
|
}), 128))], 8, _hoisted_30), [[vModelSelect, sortValue.value]])
|
|
1145
2269
|
])])
|
|
1146
2270
|
]),
|
|
1147
|
-
loadingRoot.value ? (openBlock(), createElementBlock("div", _hoisted_32, toDisplayString(t("loading")), 1)) :
|
|
2271
|
+
loadingRoot.value ? (openBlock(), createElementBlock("div", _hoisted_32, toDisplayString(t("loading")), 1)) : rootLock.active ? (openBlock(), createElementBlock("form", {
|
|
2272
|
+
key: 1,
|
|
2273
|
+
class: normalizeClass(["fl-unlock-root", { "fl-unlock-error": rootLock.error }]),
|
|
2274
|
+
onSubmit: withModifiers(submitRootUnlock, ["prevent"])
|
|
2275
|
+
}, [
|
|
2276
|
+
_cache[15] || (_cache[15] = createElementVNode("svg", {
|
|
2277
|
+
class: "fl-unlock-lock",
|
|
2278
|
+
viewBox: "0 0 24 24",
|
|
2279
|
+
width: "16",
|
|
2280
|
+
height: "16",
|
|
2281
|
+
fill: "none",
|
|
2282
|
+
stroke: "currentColor",
|
|
2283
|
+
"stroke-width": "2.2",
|
|
2284
|
+
"stroke-linecap": "round",
|
|
2285
|
+
"stroke-linejoin": "round",
|
|
2286
|
+
"aria-hidden": "true"
|
|
2287
|
+
}, [createElementVNode("rect", {
|
|
2288
|
+
x: "5",
|
|
2289
|
+
y: "11",
|
|
2290
|
+
width: "14",
|
|
2291
|
+
height: "10",
|
|
2292
|
+
rx: "2"
|
|
2293
|
+
}), createElementVNode("path", { d: "M8 11V8a4 4 0 0 1 8 0v3" })], -1)),
|
|
2294
|
+
createElementVNode("label", {
|
|
2295
|
+
class: "fl-unlock-label",
|
|
2296
|
+
for: `fl-root-unlock-${__props.path}`
|
|
2297
|
+
}, toDisplayString(t("passwordPrompt")), 9, _hoisted_33),
|
|
2298
|
+
rootLock.error ? (openBlock(), createElementBlock("span", _hoisted_34, toDisplayString(unlockErrorText(rootLock.error)), 1)) : createCommentVNode("v-if", true),
|
|
2299
|
+
createElementVNode("span", _hoisted_35, [
|
|
2300
|
+
withDirectives(createElementVNode("input", {
|
|
2301
|
+
id: `fl-root-unlock-${__props.path}`,
|
|
2302
|
+
"onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => rootLock.input = $event),
|
|
2303
|
+
type: "password",
|
|
2304
|
+
autocomplete: "off",
|
|
2305
|
+
"data-unlock": __props.path,
|
|
2306
|
+
placeholder: t("passwordPlaceholder"),
|
|
2307
|
+
"aria-invalid": !!rootLock.error,
|
|
2308
|
+
"aria-describedby": rootLock.error ? "fl-root-unlock-error" : void 0
|
|
2309
|
+
}, null, 8, _hoisted_36), [[vModelText, rootLock.input]]),
|
|
2310
|
+
canRemember.value ? (openBlock(), createElementBlock("button", {
|
|
2311
|
+
key: 0,
|
|
2312
|
+
class: "fl-unlock-remember",
|
|
2313
|
+
type: "button",
|
|
2314
|
+
"aria-pressed": rootLock.remember,
|
|
2315
|
+
"aria-label": t("rememberPassword"),
|
|
2316
|
+
title: t("rememberPassword"),
|
|
2317
|
+
onClick: _cache[6] || (_cache[6] = ($event) => rootLock.remember = !rootLock.remember)
|
|
2318
|
+
}, [..._cache[14] || (_cache[14] = [createElementVNode("svg", {
|
|
2319
|
+
viewBox: "0 0 24 24",
|
|
2320
|
+
width: "16",
|
|
2321
|
+
height: "16",
|
|
2322
|
+
fill: "none",
|
|
2323
|
+
stroke: "currentColor",
|
|
2324
|
+
"stroke-width": "2",
|
|
2325
|
+
"stroke-linecap": "round",
|
|
2326
|
+
"stroke-linejoin": "round",
|
|
2327
|
+
"aria-hidden": "true"
|
|
2328
|
+
}, [
|
|
2329
|
+
createElementVNode("circle", {
|
|
2330
|
+
cx: "8",
|
|
2331
|
+
cy: "15",
|
|
2332
|
+
r: "4"
|
|
2333
|
+
}),
|
|
2334
|
+
createElementVNode("path", { d: "m11 12 9-9" }),
|
|
2335
|
+
createElementVNode("path", { d: "m17 6 3 3" })
|
|
2336
|
+
], -1)])], 8, _hoisted_37)) : createCommentVNode("v-if", true),
|
|
2337
|
+
createElementVNode("button", {
|
|
2338
|
+
class: "fl-unlock-submit",
|
|
2339
|
+
type: "submit",
|
|
2340
|
+
disabled: rootLock.busy || !rootLock.input
|
|
2341
|
+
}, toDisplayString(t("open")), 9, _hoisted_38)
|
|
2342
|
+
])
|
|
2343
|
+
], 34)) : errorMsg.value ? (openBlock(), createElementBlock("div", _hoisted_39, [
|
|
1148
2344
|
createTextVNode(toDisplayString(t("loadFailed", { message: errorMsg.value })) + " ", 1),
|
|
1149
|
-
/guest/i.test(errorMsg.value) ? (openBlock(), createElementBlock("p",
|
|
1150
|
-
/failed to fetch|networkerror/i.test(errorMsg.value) ? (openBlock(), createElementBlock("p",
|
|
2345
|
+
/guest/i.test(errorMsg.value) ? (openBlock(), createElementBlock("p", _hoisted_40, toDisplayString(t("guestHint")), 1)) : createCommentVNode("v-if", true),
|
|
2346
|
+
/failed to fetch|networkerror/i.test(errorMsg.value) ? (openBlock(), createElementBlock("p", _hoisted_41, toDisplayString(t("corsHint")), 1)) : createCommentVNode("v-if", true),
|
|
1151
2347
|
createElementVNode("button", {
|
|
1152
2348
|
class: "fl-btn",
|
|
1153
2349
|
type: "button",
|
|
1154
2350
|
onClick: loadRoot
|
|
1155
2351
|
}, toDisplayString(t("retry")), 1)
|
|
1156
|
-
])) : visible.value.length === 0 ? (openBlock(), createElementBlock("div",
|
|
2352
|
+
])) : visible.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_42, toDisplayString(t("empty")), 1)) : createCommentVNode("v-if", true),
|
|
1157
2353
|
(openBlock(true), createElementBlock(Fragment, null, renderList(visible.value, (node) => {
|
|
1158
2354
|
return openBlock(), createElementBlock("div", {
|
|
1159
2355
|
key: node.path,
|
|
1160
2356
|
class: normalizeClass(["fl-row", {
|
|
1161
2357
|
"fl-row-dir": node.is_dir,
|
|
1162
2358
|
"fl-row-root": node.isRoot,
|
|
1163
|
-
"fl-row-selected": !node.is_dir && selected.value.has(node.path)
|
|
2359
|
+
"fl-row-selected": !node.is_dir && selected.value.has(node.path),
|
|
2360
|
+
"fl-row-locked": node.locked,
|
|
2361
|
+
"fl-row-prompting": node.locked && node.prompting,
|
|
2362
|
+
"fl-row-error": node.locked && node.prompting && !!node.unlockError
|
|
1164
2363
|
}]),
|
|
1165
2364
|
style: normalizeStyle({ "--fl-depth": node.depth })
|
|
1166
2365
|
}, [
|
|
1167
|
-
createElementVNode("label",
|
|
2366
|
+
createElementVNode("label", _hoisted_43, [createElementVNode("input", {
|
|
1168
2367
|
type: "checkbox",
|
|
2368
|
+
disabled: node.locked,
|
|
1169
2369
|
"aria-label": t("selectItem", { name: node.name }),
|
|
1170
2370
|
checked: isChecked(node),
|
|
1171
2371
|
".indeterminate": isIndeterminate(node),
|
|
1172
2372
|
onChange: ($event) => toggleCheck(node, $event)
|
|
1173
|
-
}, null, 40,
|
|
2373
|
+
}, null, 40, _hoisted_44)]),
|
|
1174
2374
|
createElementVNode("span", {
|
|
1175
2375
|
class: "fl-cell fl-cell-name",
|
|
1176
2376
|
title: node.isRoot ? node.path : void 0
|
|
1177
2377
|
}, [
|
|
1178
|
-
_cache[
|
|
2378
|
+
_cache[20] || (_cache[20] = createElementVNode("span", {
|
|
1179
2379
|
class: "fl-indent",
|
|
1180
2380
|
"aria-hidden": "true"
|
|
1181
2381
|
}, null, -1)),
|
|
1182
|
-
node.
|
|
2382
|
+
node.locked ? (openBlock(), createElementBlock("button", {
|
|
1183
2383
|
key: 0,
|
|
2384
|
+
class: "fl-arrow fl-lock",
|
|
2385
|
+
type: "button",
|
|
2386
|
+
"aria-label": t("unlockFolder", { name: node.name }),
|
|
2387
|
+
title: t("unlockFolder", { name: node.name }),
|
|
2388
|
+
onClick: ($event) => toggleExpand(node)
|
|
2389
|
+
}, [..._cache[16] || (_cache[16] = [createElementVNode("svg", {
|
|
2390
|
+
viewBox: "0 0 24 24",
|
|
2391
|
+
width: "14",
|
|
2392
|
+
height: "14",
|
|
2393
|
+
fill: "none",
|
|
2394
|
+
stroke: "currentColor",
|
|
2395
|
+
"stroke-width": "2.2",
|
|
2396
|
+
"stroke-linecap": "round",
|
|
2397
|
+
"stroke-linejoin": "round",
|
|
2398
|
+
"aria-hidden": "true"
|
|
2399
|
+
}, [createElementVNode("rect", {
|
|
2400
|
+
x: "5",
|
|
2401
|
+
y: "11",
|
|
2402
|
+
width: "14",
|
|
2403
|
+
height: "10",
|
|
2404
|
+
rx: "2"
|
|
2405
|
+
}), createElementVNode("path", { d: "M8 11V8a4 4 0 0 1 8 0v3" })], -1)])], 8, _hoisted_46)) : node.is_dir ? (openBlock(), createElementBlock("button", {
|
|
2406
|
+
key: 1,
|
|
1184
2407
|
class: normalizeClass(["fl-arrow", { "fl-arrow-open": node.expanded }]),
|
|
1185
2408
|
type: "button",
|
|
1186
2409
|
"aria-label": t(node.expanded ? "collapse" : "expand", { name: node.name }),
|
|
1187
2410
|
"aria-expanded": node.expanded,
|
|
1188
2411
|
onClick: ($event) => toggleExpand(node)
|
|
1189
|
-
}, [..._cache[
|
|
2412
|
+
}, [..._cache[17] || (_cache[17] = [createElementVNode("svg", {
|
|
1190
2413
|
viewBox: "0 0 24 24",
|
|
1191
2414
|
width: "14",
|
|
1192
2415
|
height: "14",
|
|
@@ -1196,86 +2419,251 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1196
2419
|
"stroke-linecap": "round",
|
|
1197
2420
|
"stroke-linejoin": "round",
|
|
1198
2421
|
"aria-hidden": "true"
|
|
1199
|
-
}, [createElementVNode("path", { d: "m9 6 6 6-6 6" })], -1)])], 10,
|
|
1200
|
-
node.is_dir ? (openBlock(), createElementBlock("svg",
|
|
1201
|
-
key:
|
|
2422
|
+
}, [createElementVNode("path", { d: "m9 6 6 6-6 6" })], -1)])], 10, _hoisted_47)) : (openBlock(), createElementBlock("span", _hoisted_48)),
|
|
2423
|
+
node.is_dir ? (openBlock(), createElementBlock("svg", _hoisted_49, [..._cache[18] || (_cache[18] = [createElementVNode("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" }, null, -1)])])) : (openBlock(), createElementBlock("span", {
|
|
2424
|
+
key: 4,
|
|
1202
2425
|
class: "fl-type",
|
|
1203
2426
|
"data-kind": unref(fileKind)(node.name),
|
|
1204
2427
|
"aria-hidden": "true"
|
|
1205
|
-
}, toDisplayString(unref(extOf)(node.name) || "FILE"), 9,
|
|
1206
|
-
createElementVNode("span",
|
|
2428
|
+
}, toDisplayString(unref(extOf)(node.name) || "FILE"), 9, _hoisted_50)),
|
|
2429
|
+
createElementVNode("span", _hoisted_51, [createElementVNode("span", {
|
|
1207
2430
|
class: normalizeClass(["fl-name", { "fl-name-dir": node.is_dir }]),
|
|
1208
|
-
onClick: ($event) => node.is_dir ? toggleExpand(node) : download(node)
|
|
1209
|
-
}, toDisplayString(node.name), 11,
|
|
1210
|
-
node.is_dir && node.loaded ? (openBlock(), createElementBlock("span",
|
|
1211
|
-
node.
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
2431
|
+
onClick: ($event) => node.locked || node.is_dir ? toggleExpand(node) : download(node)
|
|
2432
|
+
}, toDisplayString(node.name), 11, _hoisted_52), createElementVNode("span", _hoisted_53, [node.is_dir ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [node.loaded ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(toDisplayString(t("folderItems", { count: folderFiles(node).length })) + " · " + toDisplayString(formatTotal(unref(totalSize)(folderFiles(node)))), 1)], 64)) : createCommentVNode("v-if", true)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(unref(formatSize)(node.size)) + " · " + toDisplayString(unref(formatDate)(node.modified)), 1)], 64))])]),
|
|
2433
|
+
node.is_dir && node.loaded ? (openBlock(), createElementBlock("span", _hoisted_54, toDisplayString(t("folderItems", { count: folderFiles(node).length })), 1)) : createCommentVNode("v-if", true),
|
|
2434
|
+
node.locked && node.prompting && node.unlockError ? (openBlock(), createElementBlock("span", {
|
|
2435
|
+
key: 6,
|
|
2436
|
+
id: `fl-unlock-error-${node.path}`,
|
|
2437
|
+
class: "fl-unlock-message",
|
|
2438
|
+
role: "alert"
|
|
2439
|
+
}, toDisplayString(unlockErrorText(node.unlockError)), 9, _hoisted_55)) : createCommentVNode("v-if", true),
|
|
2440
|
+
!node.locked && rememberedPaths.value.has(node.path) ? (openBlock(), createElementBlock("button", {
|
|
2441
|
+
key: 7,
|
|
2442
|
+
class: "fl-remembered",
|
|
2443
|
+
type: "button",
|
|
2444
|
+
"aria-label": `${t("forgetPassword")} ${node.name}`,
|
|
2445
|
+
title: t("forgetPassword"),
|
|
2446
|
+
onClick: ($event) => forgetPassword(node)
|
|
2447
|
+
}, [_cache[19] || (_cache[19] = createElementVNode("svg", {
|
|
2448
|
+
viewBox: "0 0 24 24",
|
|
2449
|
+
width: "11",
|
|
2450
|
+
height: "11",
|
|
2451
|
+
fill: "none",
|
|
2452
|
+
stroke: "currentColor",
|
|
2453
|
+
"stroke-width": "2.2",
|
|
2454
|
+
"stroke-linecap": "round",
|
|
2455
|
+
"stroke-linejoin": "round",
|
|
2456
|
+
"aria-hidden": "true"
|
|
2457
|
+
}, [
|
|
2458
|
+
createElementVNode("circle", {
|
|
2459
|
+
cx: "8",
|
|
2460
|
+
cy: "15",
|
|
2461
|
+
r: "4"
|
|
2462
|
+
}),
|
|
2463
|
+
createElementVNode("path", { d: "m11 12 9-9" }),
|
|
2464
|
+
createElementVNode("path", { d: "m17 6 3 3" })
|
|
2465
|
+
], -1)), createTextVNode(" " + toDisplayString(t("remembered")) + " · " + toDisplayString(t("forgetPassword")), 1)], 8, _hoisted_56)) : createCommentVNode("v-if", true),
|
|
2466
|
+
node.loading ? (openBlock(), createElementBlock("span", _hoisted_57, "…")) : createCommentVNode("v-if", true)
|
|
2467
|
+
], 8, _hoisted_45),
|
|
2468
|
+
node.locked && node.prompting ? (openBlock(), createElementBlock("form", {
|
|
1233
2469
|
key: 0,
|
|
1234
|
-
class: "fl-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
title: t("preview"),
|
|
1238
|
-
onClick: ($event) => preview(node)
|
|
1239
|
-
}, [..._cache[14] || (_cache[14] = [createElementVNode("svg", {
|
|
1240
|
-
viewBox: "0 0 24 24",
|
|
1241
|
-
width: "16",
|
|
1242
|
-
height: "16",
|
|
1243
|
-
fill: "none",
|
|
1244
|
-
stroke: "currentColor",
|
|
1245
|
-
"stroke-width": "2",
|
|
1246
|
-
"stroke-linecap": "round",
|
|
1247
|
-
"stroke-linejoin": "round",
|
|
1248
|
-
"aria-hidden": "true"
|
|
1249
|
-
}, [createElementVNode("path", { d: "M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z" }), createElementVNode("circle", {
|
|
1250
|
-
cx: "12",
|
|
1251
|
-
cy: "12",
|
|
1252
|
-
r: "3"
|
|
1253
|
-
})], -1)])], 8, _hoisted_53)) : createCommentVNode("v-if", true), createElementVNode("button", {
|
|
1254
|
-
class: "fl-action fl-download",
|
|
1255
|
-
type: "button",
|
|
1256
|
-
"aria-label": `${t("download")} ${node.name}`,
|
|
1257
|
-
title: t("download"),
|
|
1258
|
-
onClick: ($event) => download(node)
|
|
1259
|
-
}, [..._cache[15] || (_cache[15] = [createElementVNode("svg", {
|
|
1260
|
-
viewBox: "0 0 24 24",
|
|
1261
|
-
width: "16",
|
|
1262
|
-
height: "16",
|
|
1263
|
-
fill: "none",
|
|
1264
|
-
stroke: "currentColor",
|
|
1265
|
-
"stroke-width": "2",
|
|
1266
|
-
"stroke-linecap": "round",
|
|
1267
|
-
"stroke-linejoin": "round",
|
|
1268
|
-
"aria-hidden": "true"
|
|
2470
|
+
class: "fl-unlock fl-unlock-row",
|
|
2471
|
+
onSubmit: withModifiers(($event) => submitUnlock(node), ["prevent"]),
|
|
2472
|
+
onKeydown: withKeys(withModifiers(($event) => cancelUnlock(node), ["prevent"]), ["esc"])
|
|
1269
2473
|
}, [
|
|
1270
|
-
createElementVNode("
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
2474
|
+
_cache[23] || (_cache[23] = createElementVNode("svg", {
|
|
2475
|
+
class: "fl-unlock-lock",
|
|
2476
|
+
viewBox: "0 0 24 24",
|
|
2477
|
+
width: "18",
|
|
2478
|
+
height: "18",
|
|
2479
|
+
fill: "none",
|
|
2480
|
+
stroke: "currentColor",
|
|
2481
|
+
"stroke-width": "2.2",
|
|
2482
|
+
"stroke-linecap": "round",
|
|
2483
|
+
"stroke-linejoin": "round",
|
|
2484
|
+
"aria-hidden": "true"
|
|
2485
|
+
}, [createElementVNode("rect", {
|
|
2486
|
+
x: "5",
|
|
2487
|
+
y: "11",
|
|
2488
|
+
width: "14",
|
|
2489
|
+
height: "10",
|
|
2490
|
+
rx: "2"
|
|
2491
|
+
}), createElementVNode("path", { d: "M8 11V8a4 4 0 0 1 8 0v3" })], -1)),
|
|
2492
|
+
withDirectives(createElementVNode("input", {
|
|
2493
|
+
"onUpdate:modelValue": ($event) => node.unlockInput = $event,
|
|
2494
|
+
type: "password",
|
|
2495
|
+
autocomplete: "off",
|
|
2496
|
+
"data-unlock": node.path,
|
|
2497
|
+
placeholder: node.unlockError ? unlockErrorText(node.unlockError) : t("passwordPlaceholder"),
|
|
2498
|
+
"aria-label": t("passwordFor", { name: node.name }),
|
|
2499
|
+
"aria-invalid": !!node.unlockError,
|
|
2500
|
+
"aria-describedby": node.unlockError ? `fl-unlock-error-${node.path}` : void 0
|
|
2501
|
+
}, null, 8, _hoisted_59), [[vModelText, node.unlockInput]]),
|
|
2502
|
+
canRemember.value ? (openBlock(), createElementBlock("button", {
|
|
2503
|
+
key: 0,
|
|
2504
|
+
class: "fl-unlock-remember",
|
|
2505
|
+
type: "button",
|
|
2506
|
+
"aria-pressed": !!node.unlockRemember,
|
|
2507
|
+
"aria-label": t("rememberPassword"),
|
|
2508
|
+
title: t("rememberPassword"),
|
|
2509
|
+
onClick: ($event) => node.unlockRemember = !node.unlockRemember
|
|
2510
|
+
}, [..._cache[21] || (_cache[21] = [createElementVNode("svg", {
|
|
2511
|
+
viewBox: "0 0 24 24",
|
|
2512
|
+
width: "16",
|
|
2513
|
+
height: "16",
|
|
2514
|
+
fill: "none",
|
|
2515
|
+
stroke: "currentColor",
|
|
2516
|
+
"stroke-width": "2",
|
|
2517
|
+
"stroke-linecap": "round",
|
|
2518
|
+
"stroke-linejoin": "round",
|
|
2519
|
+
"aria-hidden": "true"
|
|
2520
|
+
}, [
|
|
2521
|
+
createElementVNode("circle", {
|
|
2522
|
+
cx: "8",
|
|
2523
|
+
cy: "15",
|
|
2524
|
+
r: "4"
|
|
2525
|
+
}),
|
|
2526
|
+
createElementVNode("path", { d: "m11 12 9-9" }),
|
|
2527
|
+
createElementVNode("path", { d: "m17 6 3 3" })
|
|
2528
|
+
], -1)])], 8, _hoisted_60)) : createCommentVNode("v-if", true),
|
|
2529
|
+
createElementVNode("button", {
|
|
2530
|
+
class: "fl-unlock-submit",
|
|
2531
|
+
type: "submit",
|
|
2532
|
+
disabled: node.unlockBusy || !node.unlockInput
|
|
2533
|
+
}, toDisplayString(t("open")), 9, _hoisted_61),
|
|
2534
|
+
createElementVNode("button", {
|
|
2535
|
+
class: "fl-unlock-cancel",
|
|
2536
|
+
type: "button",
|
|
2537
|
+
"aria-label": t("cancel"),
|
|
2538
|
+
title: t("cancel"),
|
|
2539
|
+
onClick: ($event) => cancelUnlock(node)
|
|
2540
|
+
}, [..._cache[22] || (_cache[22] = [createElementVNode("svg", {
|
|
2541
|
+
viewBox: "0 0 24 24",
|
|
2542
|
+
width: "14",
|
|
2543
|
+
height: "14",
|
|
2544
|
+
fill: "none",
|
|
2545
|
+
stroke: "currentColor",
|
|
2546
|
+
"stroke-width": "2.2",
|
|
2547
|
+
"stroke-linecap": "round",
|
|
2548
|
+
"aria-hidden": "true"
|
|
2549
|
+
}, [createElementVNode("path", { d: "M6 6l12 12" }), createElementVNode("path", { d: "M18 6 6 18" })], -1)])], 8, _hoisted_62)
|
|
2550
|
+
], 40, _hoisted_58)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
|
|
2551
|
+
createElementVNode("span", _hoisted_63, toDisplayString(node.is_dir || node.locked ? "—" : unref(formatSize)(node.size)), 1),
|
|
2552
|
+
createElementVNode("span", _hoisted_64, toDisplayString(unref(formatDate)(node.modified)), 1),
|
|
2553
|
+
createElementVNode("span", _hoisted_65, [node.locked ? (openBlock(), createElementBlock("button", {
|
|
2554
|
+
key: 0,
|
|
2555
|
+
class: "fl-action fl-arrow-end fl-lock",
|
|
2556
|
+
type: "button",
|
|
2557
|
+
"aria-label": t("unlockFolder", { name: node.name }),
|
|
2558
|
+
onClick: ($event) => toggleExpand(node)
|
|
2559
|
+
}, [..._cache[24] || (_cache[24] = [createElementVNode("svg", {
|
|
2560
|
+
viewBox: "0 0 24 24",
|
|
2561
|
+
width: "18",
|
|
2562
|
+
height: "18",
|
|
2563
|
+
fill: "none",
|
|
2564
|
+
stroke: "currentColor",
|
|
2565
|
+
"stroke-width": "2.2",
|
|
2566
|
+
"stroke-linecap": "round",
|
|
2567
|
+
"stroke-linejoin": "round",
|
|
2568
|
+
"aria-hidden": "true"
|
|
2569
|
+
}, [createElementVNode("rect", {
|
|
2570
|
+
x: "5",
|
|
2571
|
+
y: "11",
|
|
2572
|
+
width: "14",
|
|
2573
|
+
height: "10",
|
|
2574
|
+
rx: "2"
|
|
2575
|
+
}), createElementVNode("path", { d: "M8 11V8a4 4 0 0 1 8 0v3" })], -1)])], 8, _hoisted_66)) : node.is_dir ? (openBlock(), createElementBlock("button", {
|
|
2576
|
+
key: 1,
|
|
2577
|
+
class: normalizeClass(["fl-action fl-arrow-end", { "fl-arrow-open": node.expanded }]),
|
|
2578
|
+
type: "button",
|
|
2579
|
+
"aria-label": t(node.expanded ? "collapse" : "expand", { name: node.name }),
|
|
2580
|
+
"aria-expanded": node.expanded,
|
|
2581
|
+
onClick: ($event) => toggleExpand(node)
|
|
2582
|
+
}, [..._cache[25] || (_cache[25] = [createElementVNode("svg", {
|
|
2583
|
+
viewBox: "0 0 24 24",
|
|
2584
|
+
width: "18",
|
|
2585
|
+
height: "18",
|
|
2586
|
+
fill: "none",
|
|
2587
|
+
stroke: "currentColor",
|
|
2588
|
+
"stroke-width": "2.2",
|
|
2589
|
+
"stroke-linecap": "round",
|
|
2590
|
+
"stroke-linejoin": "round",
|
|
2591
|
+
"aria-hidden": "true"
|
|
2592
|
+
}, [createElementVNode("path", { d: "m9 6 6 6-6 6" })], -1)])], 10, _hoisted_67)) : !node.locked ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [canPreview(node) ? (openBlock(), createElementBlock("button", {
|
|
2593
|
+
key: 0,
|
|
2594
|
+
class: "fl-action fl-download",
|
|
2595
|
+
type: "button",
|
|
2596
|
+
"aria-label": `${t("preview")} ${node.name}`,
|
|
2597
|
+
title: t("preview"),
|
|
2598
|
+
onClick: ($event) => preview(node)
|
|
2599
|
+
}, [..._cache[26] || (_cache[26] = [createElementVNode("svg", {
|
|
2600
|
+
viewBox: "0 0 24 24",
|
|
2601
|
+
width: "16",
|
|
2602
|
+
height: "16",
|
|
2603
|
+
fill: "none",
|
|
2604
|
+
stroke: "currentColor",
|
|
2605
|
+
"stroke-width": "2",
|
|
2606
|
+
"stroke-linecap": "round",
|
|
2607
|
+
"stroke-linejoin": "round",
|
|
2608
|
+
"aria-hidden": "true"
|
|
2609
|
+
}, [createElementVNode("path", { d: "M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z" }), createElementVNode("circle", {
|
|
2610
|
+
cx: "12",
|
|
2611
|
+
cy: "12",
|
|
2612
|
+
r: "3"
|
|
2613
|
+
})], -1)])], 8, _hoisted_68)) : createCommentVNode("v-if", true), createElementVNode("button", {
|
|
2614
|
+
class: "fl-action fl-download",
|
|
2615
|
+
type: "button",
|
|
2616
|
+
"aria-label": `${t("download")} ${node.name}`,
|
|
2617
|
+
title: t("download"),
|
|
2618
|
+
onClick: ($event) => download(node)
|
|
2619
|
+
}, [..._cache[27] || (_cache[27] = [createElementVNode("svg", {
|
|
2620
|
+
viewBox: "0 0 24 24",
|
|
2621
|
+
width: "16",
|
|
2622
|
+
height: "16",
|
|
2623
|
+
fill: "none",
|
|
2624
|
+
stroke: "currentColor",
|
|
2625
|
+
"stroke-width": "2",
|
|
2626
|
+
"stroke-linecap": "round",
|
|
2627
|
+
"stroke-linejoin": "round",
|
|
2628
|
+
"aria-hidden": "true"
|
|
2629
|
+
}, [
|
|
2630
|
+
createElementVNode("path", { d: "M12 3v12" }),
|
|
2631
|
+
createElementVNode("path", { d: "m7 10 5 5 5-5" }),
|
|
2632
|
+
createElementVNode("path", { d: "M5 21h14" })
|
|
2633
|
+
], -1)])], 8, _hoisted_69)], 64)) : createCommentVNode("v-if", true)])
|
|
2634
|
+
], 64))
|
|
1274
2635
|
], 6);
|
|
1275
2636
|
}), 128)),
|
|
1276
|
-
createElementVNode("div",
|
|
1277
|
-
toast.value ? (openBlock(), createElementBlock("div",
|
|
1278
|
-
|
|
2637
|
+
createElementVNode("div", _hoisted_70, [createElementVNode("span", null, toDisplayString(t("loaded", { count: allFiles.value.length })) + " · " + toDisplayString(t("totalSize", { size: formatTotal(loadedBytes.value) })), 1)]),
|
|
2638
|
+
toast.value ? (openBlock(), createElementBlock("div", _hoisted_71, toDisplayString(toast.value), 1)) : createCommentVNode("v-if", true),
|
|
2639
|
+
createVNode(_sfc_main$1, {
|
|
2640
|
+
open: !!previewNode.value,
|
|
2641
|
+
file: previewNode.value,
|
|
2642
|
+
labels: text.value,
|
|
2643
|
+
src: previewSrc.value,
|
|
2644
|
+
viewer: previewViewer.value,
|
|
2645
|
+
resolving: previewResolving.value,
|
|
2646
|
+
error: previewError.value,
|
|
2647
|
+
demo: demo.value,
|
|
2648
|
+
position: previewPosition.value,
|
|
2649
|
+
total: previewPosition.value ? previewList.value.length : 0,
|
|
2650
|
+
onClose: closePreview,
|
|
2651
|
+
onPrev: _cache[7] || (_cache[7] = ($event) => stepPreview(-1)),
|
|
2652
|
+
onNext: _cache[8] || (_cache[8] = ($event) => stepPreview(1)),
|
|
2653
|
+
onDownload: _cache[9] || (_cache[9] = ($event) => previewNode.value && download(previewNode.value))
|
|
2654
|
+
}, null, 8, [
|
|
2655
|
+
"open",
|
|
2656
|
+
"file",
|
|
2657
|
+
"labels",
|
|
2658
|
+
"src",
|
|
2659
|
+
"viewer",
|
|
2660
|
+
"resolving",
|
|
2661
|
+
"error",
|
|
2662
|
+
"demo",
|
|
2663
|
+
"position",
|
|
2664
|
+
"total"
|
|
2665
|
+
])
|
|
2666
|
+
], 14, _hoisted_1);
|
|
1279
2667
|
};
|
|
1280
2668
|
}
|
|
1281
2669
|
});
|
|
@@ -1300,4 +2688,4 @@ function enhanceAppWithFileList({ app }) {
|
|
|
1300
2688
|
if (!app._context.components.FileList) app.component("FileList", _sfc_main);
|
|
1301
2689
|
if (!app._context.components.VPFileList) app.component("VPFileList", _sfc_main);
|
|
1302
2690
|
}
|
|
1303
|
-
export { _sfc_main as FileList, _sfc_main as VPFileList, compareNodes, encodePath, enhanceAppWithFileList, extOf, fileKind, fileListLocales, formatDate, formatLabel, formatSize, joinPath, resolveLabels, totalSize, uniqueEntryName, zipEntryName, zipSegment };
|
|
2691
|
+
export { _sfc_main as FileList, _sfc_main as VPFileList, compareNodes, encodePath, enhanceAppWithFileList, extOf, fileKind, fileListLocales, fillPreviewUrl, formatDate, formatLabel, formatSize, iframePreviewsFor, joinPath, previewKind, resolveLabels, totalSize, uniqueEntryName, zipEntryName, zipSegment };
|