vitepress-plugin-filelist 0.4.0 → 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 +13 -2
- package/dist/client/browser/index.d.ts +26 -0
- package/dist/client/browser/index.js +948 -233
- package/dist/client/ssr/index.d.ts +26 -0
- package/dist/client/ssr/index.js +378 -74
- package/dist/client/style.css +310 -25
- package/package.json +1 -1
package/dist/client/ssr/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computed, defineComponent, mergeProps, onBeforeUnmount, onMounted, ref, unref, useSSRContext, watch } from "vue";
|
|
1
|
+
import { computed, defineComponent, mergeProps, nextTick, onBeforeUnmount, onMounted, reactive, ref, unref, useSSRContext, watch } from "vue";
|
|
2
2
|
import { ssrIncludeBooleanAttr, ssrInterpolate, ssrLooseContain, ssrLooseEqual, ssrRenderAttr, ssrRenderAttrs, ssrRenderClass, ssrRenderComponent, ssrRenderList, ssrRenderStyle } from "vue/server-renderer";
|
|
3
3
|
// src/client/locales.ts
|
|
4
4
|
const fileListLocales = {
|
|
@@ -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: "预览",
|
|
@@ -52,7 +52,18 @@ const fileListLocales = {
|
|
|
52
52
|
previewUnsupported: "该类型暂不支持在线预览,可下载后查看",
|
|
53
53
|
previewUnavailable: "该文件无法在页面内预览,可下载后查看",
|
|
54
54
|
previewTooLarge: "文件较大({size}),请下载后查看",
|
|
55
|
-
textTruncated: "仅显示前 {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。"
|
|
56
67
|
},
|
|
57
68
|
"en-US": {
|
|
58
69
|
allFiles: "All files",
|
|
@@ -71,7 +82,7 @@ const fileListLocales = {
|
|
|
71
82
|
loading: "Loading…",
|
|
72
83
|
loadFailed: "Failed to load: {message}",
|
|
73
84
|
guestHint: "Enable the guest user in OpenList, or pass a read-only token.",
|
|
74
|
-
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.",
|
|
75
86
|
retry: "Retry",
|
|
76
87
|
empty: "No files",
|
|
77
88
|
preview: "Preview",
|
|
@@ -104,7 +115,18 @@ const fileListLocales = {
|
|
|
104
115
|
previewUnsupported: "This file type cannot be previewed here. Download it to view.",
|
|
105
116
|
previewUnavailable: "This file cannot be previewed in the page. Download it to view.",
|
|
106
117
|
previewTooLarge: "This file is large ({size}). Download it to view.",
|
|
107
|
-
textTruncated: "Showing the first {size} only. Download for the full file."
|
|
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."
|
|
108
130
|
}
|
|
109
131
|
};
|
|
110
132
|
function formatLabel(template, values = {}) {
|
|
@@ -656,6 +678,70 @@ _sfc_main$1.setup = (props, ctx) => {
|
|
|
656
678
|
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/client/PreviewDialog.vue");
|
|
657
679
|
return _sfc_setup$1 ? _sfc_setup$1(props, ctx) : void 0;
|
|
658
680
|
};
|
|
681
|
+
// src/client/passwords.ts
|
|
682
|
+
/**
|
|
683
|
+
* Folder passwords remembered for the current browser tab.
|
|
684
|
+
*
|
|
685
|
+
* Stored in `sessionStorage`, so they live only in this tab and are gone when
|
|
686
|
+
* it closes. Keyed by OpenList base URL, then by folder path; a remembered
|
|
687
|
+
* folder also covers its sub-folders. Every access is guarded: private mode
|
|
688
|
+
* or blocked storage simply means nothing is remembered.
|
|
689
|
+
*/
|
|
690
|
+
const PREFIX = "vitepress-plugin-filelist:passwords:";
|
|
691
|
+
function store() {
|
|
692
|
+
try {
|
|
693
|
+
return typeof sessionStorage === "undefined" ? null : sessionStorage;
|
|
694
|
+
} catch {
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
function read(base) {
|
|
699
|
+
try {
|
|
700
|
+
const raw = store()?.getItem(PREFIX + base);
|
|
701
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
702
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
703
|
+
} catch {
|
|
704
|
+
return {};
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function write(base, map) {
|
|
708
|
+
try {
|
|
709
|
+
const target = store();
|
|
710
|
+
if (!target) return;
|
|
711
|
+
if (Object.keys(map).length) target.setItem(PREFIX + base, JSON.stringify(map));
|
|
712
|
+
else target.removeItem(PREFIX + base);
|
|
713
|
+
} catch {}
|
|
714
|
+
}
|
|
715
|
+
function normalise(path) {
|
|
716
|
+
return path.replace(/\/+$/, "") || "/";
|
|
717
|
+
}
|
|
718
|
+
/** Whether `path` is `folder` itself or inside it. */
|
|
719
|
+
function isWithin(path, folder) {
|
|
720
|
+
const p = normalise(path);
|
|
721
|
+
const f = normalise(folder);
|
|
722
|
+
return f === "/" || p === f || p.startsWith(`${f}/`);
|
|
723
|
+
}
|
|
724
|
+
/** The closest remembered folder at or above `path`, if any. */
|
|
725
|
+
function findRememberedPassword(base, path) {
|
|
726
|
+
let best = null;
|
|
727
|
+
for (const [folder, password] of Object.entries(read(base))) {
|
|
728
|
+
if (typeof password !== "string" || !isWithin(path, folder)) continue;
|
|
729
|
+
if (!best || folder.length > best.path.length) best = {
|
|
730
|
+
path: folder,
|
|
731
|
+
password
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
return best;
|
|
735
|
+
}
|
|
736
|
+
function forgetFolderPassword(base, path) {
|
|
737
|
+
const map = read(base);
|
|
738
|
+
delete map[normalise(path)];
|
|
739
|
+
write(base, map);
|
|
740
|
+
}
|
|
741
|
+
/** Folders with a remembered password, for showing the "remembered" tag. */
|
|
742
|
+
function rememberedFolders(base) {
|
|
743
|
+
return Object.keys(read(base));
|
|
744
|
+
}
|
|
659
745
|
// src/client/FileList.vue
|
|
660
746
|
const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
661
747
|
__name: "FileList",
|
|
@@ -671,6 +757,10 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
671
757
|
token: { default: "" },
|
|
672
758
|
password: { default: "" },
|
|
673
759
|
title: { default: "" },
|
|
760
|
+
rememberPassword: {
|
|
761
|
+
type: Boolean,
|
|
762
|
+
default: true
|
|
763
|
+
},
|
|
674
764
|
locale: { default: "zh-CN" },
|
|
675
765
|
labels: {}
|
|
676
766
|
},
|
|
@@ -689,12 +779,22 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
689
779
|
}
|
|
690
780
|
const demo = computed(() => !props.base);
|
|
691
781
|
const baseUrl = computed(() => props.base.replace(/\/+$/, ""));
|
|
692
|
-
const
|
|
782
|
+
const rootEl = ref();
|
|
693
783
|
const roots = ref([]);
|
|
694
784
|
const loadingRoot = ref(false);
|
|
695
785
|
const errorMsg = ref("");
|
|
696
|
-
const
|
|
697
|
-
|
|
786
|
+
const refreshing = ref(false);
|
|
787
|
+
let refreshSeq = 0;
|
|
788
|
+
/** Single-path mode: the listed folder itself needs a password. */
|
|
789
|
+
const rootLock = reactive({
|
|
790
|
+
active: false,
|
|
791
|
+
input: "",
|
|
792
|
+
error: "",
|
|
793
|
+
remember: false,
|
|
794
|
+
busy: false
|
|
795
|
+
});
|
|
796
|
+
/** Password the visitor typed for the listed folder (single-path mode). */
|
|
797
|
+
const rootPassword = ref("");
|
|
698
798
|
const keyword = ref("");
|
|
699
799
|
const selected = ref(/* @__PURE__ */ new Set());
|
|
700
800
|
const toast = ref("");
|
|
@@ -718,6 +818,85 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
718
818
|
function isPasswordError(error) {
|
|
719
819
|
return /password|密码/i.test(errorText(error));
|
|
720
820
|
}
|
|
821
|
+
function isNetworkError(error) {
|
|
822
|
+
return /failed to fetch|networkerror|load failed|network request failed/i.test(errorText(error));
|
|
823
|
+
}
|
|
824
|
+
/** A failure toast; browser network errors are usually CORS, so say where to look. */
|
|
825
|
+
function failureMessage(key, error) {
|
|
826
|
+
const message = t(key, { message: errorText(error) });
|
|
827
|
+
return isNetworkError(error) ? `${message} ${t("networkHint")}` : message;
|
|
828
|
+
}
|
|
829
|
+
/** Total size for display: an empty total reads `0 B`, not `-`. */
|
|
830
|
+
function formatTotal(bytes) {
|
|
831
|
+
return bytes > 0 ? formatSize(bytes) : "0 B";
|
|
832
|
+
}
|
|
833
|
+
const canRemember = computed(() => props.rememberPassword !== false);
|
|
834
|
+
const passwordScope = computed(() => baseUrl.value || "demo");
|
|
835
|
+
/** Passwords typed on this page (not necessarily remembered), so a refresh keeps folders open. */
|
|
836
|
+
const typedPasswords = /* @__PURE__ */ new Map();
|
|
837
|
+
function typedPasswordFor(path) {
|
|
838
|
+
let best = "";
|
|
839
|
+
let bestPath = "";
|
|
840
|
+
for (const [folder, password] of typedPasswords) if (isWithin(path, folder) && folder.length > bestPath.length) {
|
|
841
|
+
best = password;
|
|
842
|
+
bestPath = folder;
|
|
843
|
+
}
|
|
844
|
+
return best;
|
|
845
|
+
}
|
|
846
|
+
/** Folders with a remembered password (mirrors sessionStorage so the template reacts). */
|
|
847
|
+
const rememberedPaths = ref(/* @__PURE__ */ new Set());
|
|
848
|
+
function syncRemembered() {
|
|
849
|
+
rememberedPaths.value = new Set(canRemember.value ? rememberedFolders(passwordScope.value) : []);
|
|
850
|
+
}
|
|
851
|
+
function storedPassword(path) {
|
|
852
|
+
return canRemember.value ? findRememberedPassword(passwordScope.value, path) : null;
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Password for an item: its own or inherited one first (site-configured or
|
|
856
|
+
* typed), then one remembered in this tab, then the component's `password`.
|
|
857
|
+
*/
|
|
858
|
+
function passwordFor(node) {
|
|
859
|
+
if (node.password) return { password: node.password };
|
|
860
|
+
const typed = typedPasswordFor(node.path);
|
|
861
|
+
if (typed) return { password: typed };
|
|
862
|
+
const stored = storedPassword(node.path);
|
|
863
|
+
if (stored) return {
|
|
864
|
+
password: stored.password,
|
|
865
|
+
rememberedAt: stored.path
|
|
866
|
+
};
|
|
867
|
+
return { password: props.password };
|
|
868
|
+
}
|
|
869
|
+
function rootPasswordFor() {
|
|
870
|
+
if (rootPassword.value) return { password: rootPassword.value };
|
|
871
|
+
const stored = storedPassword(props.path);
|
|
872
|
+
if (stored) return {
|
|
873
|
+
password: stored.password,
|
|
874
|
+
rememberedAt: stored.path
|
|
875
|
+
};
|
|
876
|
+
return { password: props.password };
|
|
877
|
+
}
|
|
878
|
+
/** A remembered password stopped working: drop it so we do not keep retrying it. */
|
|
879
|
+
function dropStale(path) {
|
|
880
|
+
if (!path) return false;
|
|
881
|
+
forgetFolderPassword(passwordScope.value, path);
|
|
882
|
+
syncRemembered();
|
|
883
|
+
return true;
|
|
884
|
+
}
|
|
885
|
+
function focusUnlock(path) {
|
|
886
|
+
nextTick(() => {
|
|
887
|
+
(rootEl.value?.querySelector(`input[data-unlock="${CSS.escape(path)}"]`))?.focus();
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
/** Show the same-row password input; `focus` only for user actions, never on page load. */
|
|
891
|
+
function startPrompt(node, focus) {
|
|
892
|
+
node.prompting = true;
|
|
893
|
+
if (focus) focusUnlock(node.path);
|
|
894
|
+
}
|
|
895
|
+
function unlockErrorText(error) {
|
|
896
|
+
if (error === "wrong") return t("passwordWrong");
|
|
897
|
+
if (error === "stale") return t("passwordStale");
|
|
898
|
+
return "";
|
|
899
|
+
}
|
|
721
900
|
const DEMO_TREE = {
|
|
722
901
|
"/": [
|
|
723
902
|
{
|
|
@@ -749,6 +928,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
749
928
|
size: 0,
|
|
750
929
|
is_dir: true,
|
|
751
930
|
modified: "2026-09-01T08:00:00+08:00"
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
name: "加密示例(密码 1234)",
|
|
934
|
+
size: 0,
|
|
935
|
+
is_dir: true,
|
|
936
|
+
modified: "2026-10-20T18:00:00+08:00"
|
|
752
937
|
}
|
|
753
938
|
],
|
|
754
939
|
"/assignments": [{
|
|
@@ -839,6 +1024,17 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
839
1024
|
is_dir: false,
|
|
840
1025
|
modified: "2026-01-05T19:02:00+08:00"
|
|
841
1026
|
}],
|
|
1027
|
+
"/加密示例(密码 1234)": [{
|
|
1028
|
+
name: "期末答案(教师版).pdf",
|
|
1029
|
+
size: 2621440,
|
|
1030
|
+
is_dir: false,
|
|
1031
|
+
modified: "2026-10-20T18:00:00+08:00"
|
|
1032
|
+
}, {
|
|
1033
|
+
name: "成绩登记表.xlsx",
|
|
1034
|
+
size: 48128,
|
|
1035
|
+
is_dir: false,
|
|
1036
|
+
modified: "2026-10-21T09:12:00+08:00"
|
|
1037
|
+
}],
|
|
842
1038
|
"/slides": [{
|
|
843
1039
|
name: "第一章 行列式.pptx",
|
|
844
1040
|
size: 3145728,
|
|
@@ -851,6 +1047,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
851
1047
|
modified: "2026-09-15T13:30:00+08:00"
|
|
852
1048
|
}]
|
|
853
1049
|
};
|
|
1050
|
+
/** Demo-only folder passwords, so the demo can show the password flow. */
|
|
1051
|
+
const DEMO_PASSWORDS = { "/加密示例(密码 1234)": "1234" };
|
|
854
1052
|
class ApiError extends Error {
|
|
855
1053
|
code;
|
|
856
1054
|
constructor(message, code) {
|
|
@@ -858,16 +1056,17 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
858
1056
|
this.code = code;
|
|
859
1057
|
}
|
|
860
1058
|
}
|
|
861
|
-
|
|
1059
|
+
/** JSON content type only when there is a body: a bare GET then needs no CORS preflight. */
|
|
1060
|
+
function authHeaders(json) {
|
|
862
1061
|
return {
|
|
863
|
-
"Content-Type": "application/json",
|
|
1062
|
+
...json ? { "Content-Type": "application/json" } : {},
|
|
864
1063
|
...props.token ? { Authorization: props.token } : {}
|
|
865
1064
|
};
|
|
866
1065
|
}
|
|
867
1066
|
async function request(apiPath, body) {
|
|
868
1067
|
const response = await fetch(`${baseUrl.value}${apiPath}`, {
|
|
869
1068
|
method: body === void 0 ? "GET" : "POST",
|
|
870
|
-
headers: authHeaders(),
|
|
1069
|
+
headers: authHeaders(body !== void 0),
|
|
871
1070
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
872
1071
|
});
|
|
873
1072
|
const payload = await response.json().catch(() => null);
|
|
@@ -875,9 +1074,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
875
1074
|
if (payload.code !== 200) throw new ApiError(payload.message || `HTTP ${response.status}`, payload.code ?? response.status);
|
|
876
1075
|
return payload.data;
|
|
877
1076
|
}
|
|
878
|
-
async function listDir(path, password
|
|
1077
|
+
async function listDir(path, password) {
|
|
879
1078
|
if (demo.value) {
|
|
880
1079
|
await new Promise((resolve) => setTimeout(resolve, 220));
|
|
1080
|
+
const guard = Object.entries(DEMO_PASSWORDS).find(([folder]) => isWithin(path, folder));
|
|
1081
|
+
if (guard && guard[1] !== password) throw new ApiError("password is incorrect or you have no permission", 403);
|
|
881
1082
|
return DEMO_TREE[path] ?? [];
|
|
882
1083
|
}
|
|
883
1084
|
return (await request("/api/fs/list", {
|
|
@@ -907,7 +1108,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
907
1108
|
try {
|
|
908
1109
|
url = absoluteUrl((await request("/api/fs/get", {
|
|
909
1110
|
path: node.path,
|
|
910
|
-
password: node.password
|
|
1111
|
+
password: passwordFor(node).password
|
|
911
1112
|
}))?.raw_url ?? "");
|
|
912
1113
|
} catch {
|
|
913
1114
|
url = "";
|
|
@@ -957,84 +1158,154 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
957
1158
|
const parts = path.split("/").filter(Boolean);
|
|
958
1159
|
return parts[parts.length - 1] || path;
|
|
959
1160
|
}
|
|
960
|
-
|
|
1161
|
+
/**
|
|
1162
|
+
* Load a folder's children. A password error marks the folder locked instead of
|
|
1163
|
+
* toasting; `prompt` also shows its same-row input (and focuses it).
|
|
1164
|
+
*/
|
|
1165
|
+
async function loadChildren(node, prompt = false) {
|
|
961
1166
|
if (node.loaded) return true;
|
|
1167
|
+
if (node.locked) {
|
|
1168
|
+
if (prompt) startPrompt(node, prompt === "focus");
|
|
1169
|
+
return false;
|
|
1170
|
+
}
|
|
962
1171
|
node.loading = true;
|
|
1172
|
+
const { password, rememberedAt } = passwordFor(node);
|
|
963
1173
|
try {
|
|
964
|
-
node.children = toNodes(await listDir(node.path,
|
|
1174
|
+
node.children = toNodes(await listDir(node.path, password), node.path, node.depth + 1, node.password, node.zipPath);
|
|
965
1175
|
sortTree(node.children);
|
|
966
1176
|
node.loaded = true;
|
|
967
1177
|
return true;
|
|
968
1178
|
} catch (error) {
|
|
969
|
-
if (isPasswordError(error))
|
|
970
|
-
|
|
1179
|
+
if (isPasswordError(error)) {
|
|
1180
|
+
node.locked = true;
|
|
1181
|
+
node.expanded = false;
|
|
1182
|
+
node.unlockError = dropStale(rememberedAt) ? "stale" : "";
|
|
1183
|
+
if (prompt) startPrompt(node, prompt === "focus");
|
|
1184
|
+
} else showToast(failureMessage("folderFailed", error));
|
|
971
1185
|
return false;
|
|
972
1186
|
} finally {
|
|
973
1187
|
node.loading = false;
|
|
974
1188
|
}
|
|
975
1189
|
}
|
|
976
|
-
|
|
1190
|
+
/** When OpenList cannot tell us, guess from the name: a known file extension means a file. */
|
|
1191
|
+
function looksLikeFile(path) {
|
|
1192
|
+
const name = sourceName(path);
|
|
1193
|
+
return fileKind(name) !== "other" || previewKind(name) !== "none";
|
|
1194
|
+
}
|
|
1195
|
+
async function loadSourceRoot(source, expanded) {
|
|
977
1196
|
const path = source.path;
|
|
978
|
-
const
|
|
1197
|
+
const configured = source.password ?? "";
|
|
979
1198
|
let item = {
|
|
980
1199
|
name: sourceName(path),
|
|
981
1200
|
size: 0,
|
|
982
|
-
is_dir:
|
|
1201
|
+
is_dir: !looksLikeFile(path),
|
|
983
1202
|
modified: ""
|
|
984
1203
|
};
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1204
|
+
let locked = false;
|
|
1205
|
+
let unlockError = "";
|
|
1206
|
+
if (!demo.value) {
|
|
1207
|
+
const { password, rememberedAt } = configured ? {
|
|
1208
|
+
password: configured,
|
|
1209
|
+
rememberedAt: void 0
|
|
1210
|
+
} : (() => {
|
|
1211
|
+
const stored = storedPassword(path);
|
|
1212
|
+
return stored ? {
|
|
1213
|
+
password: stored.password,
|
|
1214
|
+
rememberedAt: stored.path
|
|
1215
|
+
} : {
|
|
1216
|
+
password: props.password,
|
|
1217
|
+
rememberedAt: void 0
|
|
1218
|
+
};
|
|
1219
|
+
})();
|
|
1220
|
+
try {
|
|
1221
|
+
const data = await request("/api/fs/get", {
|
|
1222
|
+
path,
|
|
1223
|
+
password
|
|
1224
|
+
});
|
|
1225
|
+
item = {
|
|
1226
|
+
...item,
|
|
1227
|
+
...data
|
|
1228
|
+
};
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
if (isPasswordError(error)) {
|
|
1231
|
+
locked = true;
|
|
1232
|
+
unlockError = dropStale(rememberedAt) ? "stale" : "";
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1001
1236
|
const name = source.title || item.name || sourceName(path);
|
|
1002
1237
|
return {
|
|
1003
1238
|
...item,
|
|
1004
1239
|
name,
|
|
1005
1240
|
path,
|
|
1006
1241
|
depth: 0,
|
|
1007
|
-
expanded
|
|
1242
|
+
expanded,
|
|
1008
1243
|
loaded: false,
|
|
1009
1244
|
loading: false,
|
|
1010
1245
|
children: [],
|
|
1011
|
-
password,
|
|
1246
|
+
password: configured,
|
|
1247
|
+
configuredPassword: configured,
|
|
1012
1248
|
isRoot: true,
|
|
1249
|
+
locked,
|
|
1250
|
+
unresolved: locked,
|
|
1251
|
+
unlockError,
|
|
1252
|
+
prompting: locked && expanded,
|
|
1013
1253
|
zipPath: [item.is_dir ? name : item.name || sourceName(path)]
|
|
1014
1254
|
};
|
|
1015
1255
|
}
|
|
1256
|
+
class RootLockedError extends Error {
|
|
1257
|
+
rememberedAt;
|
|
1258
|
+
constructor(rememberedAt) {
|
|
1259
|
+
super("root locked");
|
|
1260
|
+
this.rememberedAt = rememberedAt;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Build a fresh tree. `expandedPaths` re-opens those folders (refresh keeps
|
|
1265
|
+
* what was open); without it the configured `expanded` defaults apply.
|
|
1266
|
+
*/
|
|
1267
|
+
async function buildTree(expandedPaths) {
|
|
1268
|
+
let nodes;
|
|
1269
|
+
if (props.sources.length) nodes = await Promise.all(props.sources.map((source) => loadSourceRoot(source, expandedPaths ? expandedPaths.has(source.path) : source.expanded ?? props.expanded)));
|
|
1270
|
+
else {
|
|
1271
|
+
const { password, rememberedAt } = rootPasswordFor();
|
|
1272
|
+
let items;
|
|
1273
|
+
try {
|
|
1274
|
+
items = await listDir(props.path, password);
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
if (isPasswordError(error)) throw new RootLockedError(rememberedAt);
|
|
1277
|
+
throw error;
|
|
1278
|
+
}
|
|
1279
|
+
nodes = toNodes(items, props.path, 0, rootPassword.value);
|
|
1280
|
+
sortTree(nodes);
|
|
1281
|
+
}
|
|
1282
|
+
const open = async (list) => {
|
|
1283
|
+
for (const node of list) {
|
|
1284
|
+
if (!node.is_dir || node.locked || !node.expanded && !expandedPaths?.has(node.path)) continue;
|
|
1285
|
+
node.expanded = await loadChildren(node, "show");
|
|
1286
|
+
if (node.expanded && expandedPaths) await open(node.children);
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
await open(nodes);
|
|
1290
|
+
return nodes;
|
|
1291
|
+
}
|
|
1292
|
+
/** First load, or a retry after an error: shows the loading state. */
|
|
1016
1293
|
async function loadRoot() {
|
|
1294
|
+
refreshSeq += 1;
|
|
1295
|
+
refreshing.value = false;
|
|
1017
1296
|
loadingRoot.value = true;
|
|
1018
1297
|
errorMsg.value = "";
|
|
1019
|
-
needsPassword.value = false;
|
|
1020
1298
|
try {
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
roots.value = nodes;
|
|
1024
|
-
for (const node of roots.value) if (node.is_dir && node.expanded) node.expanded = await loadChildren(node);
|
|
1025
|
-
selected.value = /* @__PURE__ */ new Set();
|
|
1026
|
-
downloadCache.clear();
|
|
1027
|
-
return;
|
|
1028
|
-
}
|
|
1029
|
-
const items = await listDir(props.path);
|
|
1030
|
-
roots.value = toNodes(items, props.path, 0, activePassword.value);
|
|
1031
|
-
sortTree(roots.value);
|
|
1299
|
+
roots.value = await buildTree();
|
|
1300
|
+
rootLock.active = false;
|
|
1032
1301
|
selected.value = /* @__PURE__ */ new Set();
|
|
1033
1302
|
downloadCache.clear();
|
|
1034
1303
|
} catch (error) {
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1304
|
+
if (error instanceof RootLockedError) {
|
|
1305
|
+
roots.value = [];
|
|
1306
|
+
rootLock.active = true;
|
|
1307
|
+
rootLock.error = dropStale(error.rememberedAt) ? "stale" : "";
|
|
1308
|
+
} else errorMsg.value = errorText(error);
|
|
1038
1309
|
} finally {
|
|
1039
1310
|
loadingRoot.value = false;
|
|
1040
1311
|
}
|
|
@@ -1182,6 +1453,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1182
1453
|
stickyTop.value = top;
|
|
1183
1454
|
}
|
|
1184
1455
|
onMounted(() => {
|
|
1456
|
+
syncRemembered();
|
|
1185
1457
|
loadRoot();
|
|
1186
1458
|
loadSiteTitle();
|
|
1187
1459
|
measureStickyTop();
|
|
@@ -1193,10 +1465,16 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1193
1465
|
});
|
|
1194
1466
|
return (_ctx, _push, _parent, _attrs) => {
|
|
1195
1467
|
_push(`<div${ssrRenderAttrs(mergeProps({
|
|
1196
|
-
|
|
1197
|
-
|
|
1468
|
+
ref_key: "rootEl",
|
|
1469
|
+
ref: rootEl,
|
|
1470
|
+
class: ["fl-root", {
|
|
1471
|
+
"fl-selecting": selected.value.size > 0,
|
|
1472
|
+
"fl-refreshing": refreshing.value
|
|
1473
|
+
}],
|
|
1474
|
+
style: { "--fl-sticky-top-auto": `${stickyTop.value}px` },
|
|
1475
|
+
"aria-busy": refreshing.value || loadingRoot.value
|
|
1198
1476
|
}, _attrs))}><div class="fl-bar">`);
|
|
1199
|
-
if (selected.value.size) _push(`<div class="fl-selection" role="status"><span class="fl-selection-count">${ssrInterpolate(t("selected", { count: selected.value.size }))}</span><span class="fl-selection-size">${ssrInterpolate(t("totalSize", { size:
|
|
1477
|
+
if (selected.value.size) _push(`<div class="fl-selection" role="status"><span class="fl-selection-count">${ssrInterpolate(t("selected", { count: selected.value.size }))}</span><span class="fl-selection-size">${ssrInterpolate(t("totalSize", { size: formatTotal(selectedBytes.value) }))}</span></div>`);
|
|
1200
1478
|
else {
|
|
1201
1479
|
_push(`<div class="fl-title"><span class="fl-title-text">${ssrInterpolate(displayTitle.value)}</span>`);
|
|
1202
1480
|
if (demo.value) _push(`<span class="fl-demo-badge">${ssrInterpolate(t("demoBadge"))}</span>`);
|
|
@@ -1205,19 +1483,27 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1205
1483
|
}
|
|
1206
1484
|
if (selected.value.size) _push(`<button class="fl-clear" type="button"${ssrRenderAttr("aria-label", t("clearSelection"))}${ssrRenderAttr("title", t("clearSelection"))}><svg class="fl-clear-icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12"></path><path d="M18 6 6 18"></path></svg><span class="fl-clear-text">${ssrInterpolate(t("clear"))}</span></button>`);
|
|
1207
1485
|
else _push(`<!---->`);
|
|
1208
|
-
_push(`<div class="fl-tools"><label class="fl-search"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"></circle><path d="m20 20-3.5-3.5"></path></svg><input${ssrRenderAttr("value", keyword.value)} type="text"${ssrRenderAttr("placeholder", t("filter"))}${ssrRenderAttr("aria-label", t("filter"))}></label><button class="fl-icon-btn" type="button"${ssrRenderAttr("aria-label", t("refresh"))}${ssrRenderAttr("title", t("refresh"))}><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-3-6.7"></path><path d="M21 4v5h-5"></path></svg></button></div>`);
|
|
1486
|
+
_push(`<div class="fl-tools"><label class="fl-search"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"></circle><path d="m20 20-3.5-3.5"></path></svg><input${ssrRenderAttr("value", keyword.value)} type="text"${ssrRenderAttr("placeholder", t("filter"))}${ssrRenderAttr("aria-label", t("filter"))}></label><button class="${ssrRenderClass([{ "fl-spinning": refreshing.value }, "fl-icon-btn fl-refresh"])}" type="button"${ssrIncludeBooleanAttr(refreshing.value || loadingRoot.value) ? " disabled" : ""}${ssrRenderAttr("aria-label", refreshing.value ? t("refreshing") : t("refresh"))}${ssrRenderAttr("title", refreshing.value ? t("refreshing") : t("refresh"))}><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-3-6.7"></path><path d="M21 4v5h-5"></path></svg></button></div>`);
|
|
1209
1487
|
if (selected.value.size) _push(`<button class="fl-btn fl-btn-primary" type="button"${ssrIncludeBooleanAttr(batchBusy.value) ? " disabled" : ""}><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v12"></path><path d="m7 10 5 5 5-5"></path><path d="M5 21h14"></path></svg> ${ssrInterpolate(batchBusy.value ? t("batching", { percent: batchProgress.value }) : t("batch"))}</button>`);
|
|
1210
1488
|
else _push(`<!---->`);
|
|
1211
|
-
_push(`</div>`);
|
|
1212
|
-
if (
|
|
1489
|
+
_push(`</div><div class="fl-row fl-head">`);
|
|
1490
|
+
if (refreshing.value) _push(`<span class="fl-progress" aria-hidden="true"></span>`);
|
|
1213
1491
|
else _push(`<!---->`);
|
|
1214
|
-
_push(`<
|
|
1492
|
+
_push(`<label class="fl-cell fl-cell-check"><input type="checkbox"${ssrRenderAttr("aria-label", t("selectAll"))}${ssrIncludeBooleanAttr(allChecked.value) ? " checked" : ""}${ssrRenderAttr("indeterminate", someChecked.value)}></label><span class="fl-cell fl-cell-name"><button class="fl-sort-btn" type="button"${ssrRenderAttr("aria-sort", sortKey.value === "name" ? sortAsc.value ? "ascending" : "descending" : void 0)}>${ssrInterpolate(t("columnName"))}<span class="fl-sort" aria-hidden="true">${ssrInterpolate(sortKey.value === "name" ? sortAsc.value ? "▲" : "▼" : "")}</span></button></span><span class="fl-cell fl-cell-size"><button class="fl-sort-btn" type="button">${ssrInterpolate(t("columnSize"))}<span class="fl-sort" aria-hidden="true">${ssrInterpolate(sortKey.value === "size" ? sortAsc.value ? "▲" : "▼" : "")}</span></button></span><span class="fl-cell fl-cell-date"><button class="fl-sort-btn" type="button">${ssrInterpolate(t("columnDate"))}<span class="fl-sort" aria-hidden="true">${ssrInterpolate(sortKey.value === "modified" ? sortAsc.value ? "▲" : "▼" : "")}</span></button></span><span class="fl-cell fl-cell-op"><label class="fl-sort-select"><span>${ssrInterpolate(t("sortBy"))}</span><span class="fl-sort-current" aria-hidden="true">${ssrInterpolate(sortOptions.value.find((option) => option.value === sortValue.value)?.label)}</span><select${ssrRenderAttr("aria-label", t("sortBy"))}><!--[-->`);
|
|
1215
1493
|
ssrRenderList(sortOptions.value, (option) => {
|
|
1216
1494
|
_push(`<option${ssrRenderAttr("value", option.value)}${ssrIncludeBooleanAttr(Array.isArray(sortValue.value) ? ssrLooseContain(sortValue.value, option.value) : ssrLooseEqual(sortValue.value, option.value)) ? " selected" : ""}>${ssrInterpolate(option.label)}</option>`);
|
|
1217
1495
|
});
|
|
1218
1496
|
_push(`<!--]--></select></label></span></div>`);
|
|
1219
1497
|
if (loadingRoot.value) _push(`<div class="fl-status">${ssrInterpolate(t("loading"))}</div>`);
|
|
1220
|
-
else if (
|
|
1498
|
+
else if (rootLock.active) {
|
|
1499
|
+
_push(`<form class="${ssrRenderClass([{ "fl-unlock-error": rootLock.error }, "fl-unlock-root"])}"><svg class="fl-unlock-lock" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="11" width="14" height="10" rx="2"></rect><path d="M8 11V8a4 4 0 0 1 8 0v3"></path></svg><label class="fl-unlock-label"${ssrRenderAttr("for", `fl-root-unlock-${__props.path}`)}>${ssrInterpolate(t("passwordPrompt"))}</label>`);
|
|
1500
|
+
if (rootLock.error) _push(`<span id="fl-root-unlock-error" class="fl-unlock-message" role="alert">${ssrInterpolate(unlockErrorText(rootLock.error))}</span>`);
|
|
1501
|
+
else _push(`<!---->`);
|
|
1502
|
+
_push(`<span class="fl-unlock"><input${ssrRenderAttr("id", `fl-root-unlock-${__props.path}`)}${ssrRenderAttr("value", rootLock.input)} type="password" autocomplete="off"${ssrRenderAttr("data-unlock", __props.path)}${ssrRenderAttr("placeholder", t("passwordPlaceholder"))}${ssrRenderAttr("aria-invalid", !!rootLock.error)}${ssrRenderAttr("aria-describedby", rootLock.error ? "fl-root-unlock-error" : void 0)}>`);
|
|
1503
|
+
if (canRemember.value) _push(`<button class="fl-unlock-remember" type="button"${ssrRenderAttr("aria-pressed", rootLock.remember)}${ssrRenderAttr("aria-label", t("rememberPassword"))}${ssrRenderAttr("title", t("rememberPassword"))}><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="15" r="4"></circle><path d="m11 12 9-9"></path><path d="m17 6 3 3"></path></svg></button>`);
|
|
1504
|
+
else _push(`<!---->`);
|
|
1505
|
+
_push(`<button class="fl-unlock-submit" type="submit"${ssrIncludeBooleanAttr(rootLock.busy || !rootLock.input) ? " disabled" : ""}>${ssrInterpolate(t("open"))}</button></span></form>`);
|
|
1506
|
+
} else if (errorMsg.value) {
|
|
1221
1507
|
_push(`<div class="fl-status fl-error">${ssrInterpolate(t("loadFailed", { message: errorMsg.value }))} `);
|
|
1222
1508
|
if (/guest/i.test(errorMsg.value)) _push(`<p class="fl-hint">${ssrInterpolate(t("guestHint"))}</p>`);
|
|
1223
1509
|
else _push(`<!---->`);
|
|
@@ -1231,35 +1517,53 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
1231
1517
|
_push(`<div class="${ssrRenderClass([{
|
|
1232
1518
|
"fl-row-dir": node.is_dir,
|
|
1233
1519
|
"fl-row-root": node.isRoot,
|
|
1234
|
-
"fl-row-selected": !node.is_dir && selected.value.has(node.path)
|
|
1235
|
-
|
|
1236
|
-
|
|
1520
|
+
"fl-row-selected": !node.is_dir && selected.value.has(node.path),
|
|
1521
|
+
"fl-row-locked": node.locked,
|
|
1522
|
+
"fl-row-prompting": node.locked && node.prompting,
|
|
1523
|
+
"fl-row-error": node.locked && node.prompting && !!node.unlockError
|
|
1524
|
+
}, "fl-row"])}" style="${ssrRenderStyle({ "--fl-depth": node.depth })}"><label class="fl-cell fl-cell-check"><input type="checkbox"${ssrIncludeBooleanAttr(node.locked) ? " disabled" : ""}${ssrRenderAttr("aria-label", t("selectItem", { name: node.name }))}${ssrIncludeBooleanAttr(isChecked(node)) ? " checked" : ""}${ssrRenderAttr("indeterminate", isIndeterminate(node))}></label><span class="fl-cell fl-cell-name"${ssrRenderAttr("title", node.isRoot ? node.path : void 0)}><span class="fl-indent" aria-hidden="true"></span>`);
|
|
1525
|
+
if (node.locked) _push(`<button class="fl-arrow fl-lock" type="button"${ssrRenderAttr("aria-label", t("unlockFolder", { name: node.name }))}${ssrRenderAttr("title", t("unlockFolder", { name: node.name }))}><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="11" width="14" height="10" rx="2"></rect><path d="M8 11V8a4 4 0 0 1 8 0v3"></path></svg></button>`);
|
|
1526
|
+
else if (node.is_dir) _push(`<button type="button" class="${ssrRenderClass([{ "fl-arrow-open": node.expanded }, "fl-arrow"])}"${ssrRenderAttr("aria-label", t(node.expanded ? "collapse" : "expand", { name: node.name }))}${ssrRenderAttr("aria-expanded", node.expanded)}><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 6 6 6-6 6"></path></svg></button>`);
|
|
1237
1527
|
else _push(`<span class="fl-arrow-placeholder" aria-hidden="true"></span>`);
|
|
1238
1528
|
if (node.is_dir) _push(`<svg class="fl-folder-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path></svg>`);
|
|
1239
1529
|
else _push(`<span class="fl-type"${ssrRenderAttr("data-kind", unref(fileKind)(node.name))} aria-hidden="true">${ssrInterpolate(unref(extOf)(node.name) || "FILE")}</span>`);
|
|
1240
1530
|
_push(`<span class="fl-label"><span class="${ssrRenderClass([{ "fl-name-dir": node.is_dir }, "fl-name"])}">${ssrInterpolate(node.name)}</span><span class="fl-meta">`);
|
|
1241
1531
|
if (node.is_dir) {
|
|
1242
1532
|
_push(`<!--[-->`);
|
|
1243
|
-
if (node.loaded) _push(`<!--[-->${ssrInterpolate(t("folderItems", { count: folderFiles(node).length }))} · ${ssrInterpolate(
|
|
1533
|
+
if (node.loaded) _push(`<!--[-->${ssrInterpolate(t("folderItems", { count: folderFiles(node).length }))} · ${ssrInterpolate(formatTotal(unref(totalSize)(folderFiles(node))))}<!--]-->`);
|
|
1244
1534
|
else _push(`<!---->`);
|
|
1245
1535
|
_push(`<!--]-->`);
|
|
1246
1536
|
} else _push(`<!--[-->${ssrInterpolate(unref(formatSize)(node.size))} · ${ssrInterpolate(unref(formatDate)(node.modified))}<!--]-->`);
|
|
1247
1537
|
_push(`</span></span>`);
|
|
1248
1538
|
if (node.is_dir && node.loaded) _push(`<span class="fl-count">${ssrInterpolate(t("folderItems", { count: folderFiles(node).length }))}</span>`);
|
|
1249
1539
|
else _push(`<!---->`);
|
|
1540
|
+
if (node.locked && node.prompting && node.unlockError) _push(`<span${ssrRenderAttr("id", `fl-unlock-error-${node.path}`)} class="fl-unlock-message" role="alert">${ssrInterpolate(unlockErrorText(node.unlockError))}</span>`);
|
|
1541
|
+
else _push(`<!---->`);
|
|
1542
|
+
if (!node.locked && rememberedPaths.value.has(node.path)) _push(`<button class="fl-remembered" type="button"${ssrRenderAttr("aria-label", `${t("forgetPassword")} ${node.name}`)}${ssrRenderAttr("title", t("forgetPassword"))}><svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="15" r="4"></circle><path d="m11 12 9-9"></path><path d="m17 6 3 3"></path></svg> ${ssrInterpolate(t("remembered"))} · ${ssrInterpolate(t("forgetPassword"))}</button>`);
|
|
1543
|
+
else _push(`<!---->`);
|
|
1250
1544
|
if (node.loading) _push(`<span class="fl-loading" aria-hidden="true">…</span>`);
|
|
1251
1545
|
else _push(`<!---->`);
|
|
1252
|
-
_push(`</span
|
|
1253
|
-
if (node.
|
|
1254
|
-
|
|
1255
|
-
_push(
|
|
1256
|
-
if (canPreview(node)) _push(`<button class="fl-action fl-download" type="button"${ssrRenderAttr("aria-label", `${t("preview")} ${node.name}`)}${ssrRenderAttr("title", t("preview"))}><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"></path><circle cx="12" cy="12" r="3"></circle></svg></button>`);
|
|
1546
|
+
_push(`</span>`);
|
|
1547
|
+
if (node.locked && node.prompting) {
|
|
1548
|
+
_push(`<form class="fl-unlock fl-unlock-row"><svg class="fl-unlock-lock" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="11" width="14" height="10" rx="2"></rect><path d="M8 11V8a4 4 0 0 1 8 0v3"></path></svg><input${ssrRenderAttr("value", node.unlockInput)} type="password" autocomplete="off"${ssrRenderAttr("data-unlock", node.path)}${ssrRenderAttr("placeholder", node.unlockError ? unlockErrorText(node.unlockError) : t("passwordPlaceholder"))}${ssrRenderAttr("aria-label", t("passwordFor", { name: node.name }))}${ssrRenderAttr("aria-invalid", !!node.unlockError)}${ssrRenderAttr("aria-describedby", node.unlockError ? `fl-unlock-error-${node.path}` : void 0)}>`);
|
|
1549
|
+
if (canRemember.value) _push(`<button class="fl-unlock-remember" type="button"${ssrRenderAttr("aria-pressed", !!node.unlockRemember)}${ssrRenderAttr("aria-label", t("rememberPassword"))}${ssrRenderAttr("title", t("rememberPassword"))}><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="15" r="4"></circle><path d="m11 12 9-9"></path><path d="m17 6 3 3"></path></svg></button>`);
|
|
1257
1550
|
else _push(`<!---->`);
|
|
1258
|
-
_push(`<button class="fl-
|
|
1551
|
+
_push(`<button class="fl-unlock-submit" type="submit"${ssrIncludeBooleanAttr(node.unlockBusy || !node.unlockInput) ? " disabled" : ""}>${ssrInterpolate(t("open"))}</button><button class="fl-unlock-cancel" type="button"${ssrRenderAttr("aria-label", t("cancel"))}${ssrRenderAttr("title", t("cancel"))}><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12"></path><path d="M18 6 6 18"></path></svg></button></form>`);
|
|
1552
|
+
} else {
|
|
1553
|
+
_push(`<!--[--><span class="fl-cell fl-cell-size">${ssrInterpolate(node.is_dir || node.locked ? "—" : unref(formatSize)(node.size))}</span><span class="fl-cell fl-cell-date">${ssrInterpolate(unref(formatDate)(node.modified))}</span><span class="fl-cell fl-cell-op">`);
|
|
1554
|
+
if (node.locked) _push(`<button class="fl-action fl-arrow-end fl-lock" type="button"${ssrRenderAttr("aria-label", t("unlockFolder", { name: node.name }))}><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="11" width="14" height="10" rx="2"></rect><path d="M8 11V8a4 4 0 0 1 8 0v3"></path></svg></button>`);
|
|
1555
|
+
else if (node.is_dir) _push(`<button type="button" class="${ssrRenderClass([{ "fl-arrow-open": node.expanded }, "fl-action fl-arrow-end"])}"${ssrRenderAttr("aria-label", t(node.expanded ? "collapse" : "expand", { name: node.name }))}${ssrRenderAttr("aria-expanded", node.expanded)}><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 6 6 6-6 6"></path></svg></button>`);
|
|
1556
|
+
else if (!node.locked) {
|
|
1557
|
+
_push(`<!--[-->`);
|
|
1558
|
+
if (canPreview(node)) _push(`<button class="fl-action fl-download" type="button"${ssrRenderAttr("aria-label", `${t("preview")} ${node.name}`)}${ssrRenderAttr("title", t("preview"))}><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"></path><circle cx="12" cy="12" r="3"></circle></svg></button>`);
|
|
1559
|
+
else _push(`<!---->`);
|
|
1560
|
+
_push(`<button class="fl-action fl-download" type="button"${ssrRenderAttr("aria-label", `${t("download")} ${node.name}`)}${ssrRenderAttr("title", t("download"))}><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v12"></path><path d="m7 10 5 5 5-5"></path><path d="M5 21h14"></path></svg></button><!--]-->`);
|
|
1561
|
+
} else _push(`<!---->`);
|
|
1562
|
+
_push(`</span><!--]-->`);
|
|
1259
1563
|
}
|
|
1260
|
-
_push(`</
|
|
1564
|
+
_push(`</div>`);
|
|
1261
1565
|
});
|
|
1262
|
-
_push(`<!--]--><div class="fl-foot"><span>${ssrInterpolate(t("loaded", { count: allFiles.value.length }))} · ${ssrInterpolate(t("totalSize", { size:
|
|
1566
|
+
_push(`<!--]--><div class="fl-foot"><span>${ssrInterpolate(t("loaded", { count: allFiles.value.length }))} · ${ssrInterpolate(t("totalSize", { size: formatTotal(loadedBytes.value) }))}</span></div>`);
|
|
1263
1567
|
if (toast.value) _push(`<div class="fl-toast" role="status">${ssrInterpolate(toast.value)}</div>`);
|
|
1264
1568
|
else _push(`<!---->`);
|
|
1265
1569
|
_push(ssrRenderComponent(_sfc_main$1, {
|