@ystemsrx/cfshare 0.1.1 → 0.1.3
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 -6
- package/README.zh.md +33 -6
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +368 -0
- package/dist/src/manager.d.ts +142 -0
- package/dist/src/manager.d.ts.map +1 -0
- package/dist/src/manager.js +2251 -0
- package/dist/src/policy.d.ts +15 -0
- package/dist/src/policy.d.ts.map +1 -0
- package/dist/src/policy.js +130 -0
- package/dist/src/schemas.d.ts +97 -0
- package/dist/src/schemas.d.ts.map +1 -0
- package/dist/src/schemas.js +149 -0
- package/dist/src/templates/fileExplorerTemplate.d.ts +12 -0
- package/dist/src/templates/fileExplorerTemplate.d.ts.map +1 -0
- package/dist/src/templates/fileExplorerTemplate.js +1340 -0
- package/dist/src/templates/markdownPreviewTemplate.d.ts +5 -0
- package/dist/src/templates/markdownPreviewTemplate.d.ts.map +1 -0
- package/dist/src/templates/markdownPreviewTemplate.js +243 -0
- package/dist/src/tools.d.ts +3 -0
- package/dist/src/tools.d.ts.map +1 -0
- package/dist/src/tools.js +138 -0
- package/dist/src/types.d.ts +100 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1 -0
- package/package.json +10 -1
- package/skills/cfshare/SKILL.md +1 -1
- package/src/cli.ts +484 -0
- package/src/manager.ts +45 -9
- package/src/shims.d.ts +55 -0
- package/src/tools.ts +11 -2
|
@@ -0,0 +1,1340 @@
|
|
|
1
|
+
function escapeHtml(input) {
|
|
2
|
+
return input
|
|
3
|
+
.replace(/&/g, "&")
|
|
4
|
+
.replace(/</g, "<")
|
|
5
|
+
.replace(/>/g, ">")
|
|
6
|
+
.replace(/\"/g, """)
|
|
7
|
+
.replace(/'/g, "'");
|
|
8
|
+
}
|
|
9
|
+
export function renderFileExplorerTemplate(params) {
|
|
10
|
+
const title = escapeHtml(params.title);
|
|
11
|
+
const payload = Buffer.from(JSON.stringify({
|
|
12
|
+
title: params.title,
|
|
13
|
+
mode: params.mode,
|
|
14
|
+
presentation: params.presentation,
|
|
15
|
+
manifest: params.manifest,
|
|
16
|
+
autoEnterSingleDirectory: params.autoEnterSingleDirectory !== false,
|
|
17
|
+
rootDirectories: Array.isArray(params.rootDirectories) ? params.rootDirectories : [],
|
|
18
|
+
}), "utf8").toString("base64");
|
|
19
|
+
return `<!doctype html>
|
|
20
|
+
<html lang="zh-CN">
|
|
21
|
+
<head>
|
|
22
|
+
<meta charset="UTF-8" />
|
|
23
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
24
|
+
<title>${title}</title>
|
|
25
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
26
|
+
<script src="https://unpkg.com/lucide@0.469.0/dist/umd/lucide.min.js"></script>
|
|
27
|
+
</head>
|
|
28
|
+
<body>
|
|
29
|
+
<div class="flex h-screen w-full bg-gray-50 text-slate-800 font-sans overflow-hidden select-none">
|
|
30
|
+
<main class="flex-1 flex flex-col h-full overflow-hidden relative">
|
|
31
|
+
<header class="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-4 md:px-6 shrink-0">
|
|
32
|
+
<div id="breadcrumb" class="hidden md:flex items-center gap-1 overflow-hidden flex-1 mr-4"></div>
|
|
33
|
+
|
|
34
|
+
<div class="flex items-center gap-3">
|
|
35
|
+
<div class="relative">
|
|
36
|
+
<i data-lucide="search" class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" style="width: 16px; height: 16px"></i>
|
|
37
|
+
<input
|
|
38
|
+
id="search-input"
|
|
39
|
+
type="text"
|
|
40
|
+
placeholder="搜索文件..."
|
|
41
|
+
class="pl-9 pr-4 py-2 bg-gray-100 border-none rounded-full text-sm w-48 focus:w-64 focus:ring-2 focus:ring-blue-100 focus:bg-white transition-all outline-none placeholder-gray-400"
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="h-6 w-px bg-gray-200 mx-1"></div>
|
|
46
|
+
|
|
47
|
+
<div class="flex bg-gray-100 p-1 rounded-lg">
|
|
48
|
+
<button
|
|
49
|
+
id="view-grid"
|
|
50
|
+
class="p-1.5 rounded-md transition-all bg-white shadow-sm text-blue-600"
|
|
51
|
+
>
|
|
52
|
+
<i data-lucide="grid-3x3" style="width: 18px; height: 18px"></i>
|
|
53
|
+
</button>
|
|
54
|
+
<button
|
|
55
|
+
id="view-list"
|
|
56
|
+
class="p-1.5 rounded-md transition-all text-gray-500 hover:text-gray-700"
|
|
57
|
+
>
|
|
58
|
+
<i data-lucide="list" style="width: 18px; height: 18px"></i>
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</header>
|
|
63
|
+
|
|
64
|
+
<div id="mobile-breadcrumb" class="md:hidden bg-white border-b border-gray-200 px-3 py-2 overflow-x-auto whitespace-nowrap"></div>
|
|
65
|
+
|
|
66
|
+
<div id="file-area" class="flex-1 overflow-y-auto p-6 scroll-smooth"></div>
|
|
67
|
+
|
|
68
|
+
<aside
|
|
69
|
+
id="details-panel"
|
|
70
|
+
class="absolute right-0 top-0 bottom-0 w-80 bg-white border-l border-gray-200 shadow-2xl z-30 flex flex-col transform transition-transform duration-300 ease-in-out translate-x-full"
|
|
71
|
+
></aside>
|
|
72
|
+
</main>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<script>
|
|
76
|
+
(function () {
|
|
77
|
+
const rawPayload = "${payload}";
|
|
78
|
+
const bytes = Uint8Array.from(atob(rawPayload), function (char) { return char.charCodeAt(0); });
|
|
79
|
+
const payload = JSON.parse(new TextDecoder("utf-8").decode(bytes));
|
|
80
|
+
|
|
81
|
+
const imageExtensions = new Set([
|
|
82
|
+
"png",
|
|
83
|
+
"jpg",
|
|
84
|
+
"jpeg",
|
|
85
|
+
"gif",
|
|
86
|
+
"webp",
|
|
87
|
+
"ico",
|
|
88
|
+
"bmp",
|
|
89
|
+
"tiff",
|
|
90
|
+
"tif",
|
|
91
|
+
"heic",
|
|
92
|
+
"raw",
|
|
93
|
+
"svg",
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
const specialNamesConfig = {
|
|
97
|
+
dockerfile: { icon: "package", color: "text-blue-500" },
|
|
98
|
+
makefile: { icon: "terminal", color: "text-slate-600" },
|
|
99
|
+
license: { icon: "shield", color: "text-yellow-600" },
|
|
100
|
+
jenkinsfile: { icon: "settings", color: "text-red-500" },
|
|
101
|
+
gemfile: { icon: "code-2", color: "text-red-600" },
|
|
102
|
+
"package.json": { icon: "file-json", color: "text-red-500" },
|
|
103
|
+
"package-lock.json": { icon: "file-cog", color: "text-slate-500" },
|
|
104
|
+
"yarn.lock": { icon: "file-cog", color: "text-blue-400" },
|
|
105
|
+
"tsconfig.json": { icon: "file-code", color: "text-blue-600" },
|
|
106
|
+
".gitignore": { icon: "file-cog", color: "text-slate-500" },
|
|
107
|
+
".env": { icon: "settings", color: "text-slate-600" },
|
|
108
|
+
".env.local": { icon: "settings", color: "text-slate-600" },
|
|
109
|
+
".editorconfig": { icon: "settings", color: "text-slate-600" },
|
|
110
|
+
"readme.md": { icon: "file-text", color: "text-blue-500" },
|
|
111
|
+
"changelog.md": { icon: "clock", color: "text-orange-500" },
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const extensionGroups = [
|
|
115
|
+
{
|
|
116
|
+
icon: "image",
|
|
117
|
+
color: "text-purple-500",
|
|
118
|
+
exts: ["png", "jpg", "jpeg", "gif", "webp", "ico", "bmp", "tiff", "tif", "heic", "raw"],
|
|
119
|
+
},
|
|
120
|
+
{ icon: "image", color: "text-orange-500", exts: ["svg"] },
|
|
121
|
+
{ icon: "palette", color: "text-pink-500", exts: ["fig", "sketch", "xd", "ai", "psd", "eps", "indd"] },
|
|
122
|
+
{ icon: "file-text", color: "text-red-500", exts: ["pdf"] },
|
|
123
|
+
{ icon: "file-text", color: "text-blue-600", exts: ["doc", "docx", "pages", "odt", "rtf"] },
|
|
124
|
+
{ icon: "file-text", color: "text-slate-500", exts: ["txt", "log", "md", "markdown"] },
|
|
125
|
+
{ icon: "file-spreadsheet", color: "text-emerald-600", exts: ["xlsx", "xls", "numbers", "ods"] },
|
|
126
|
+
{ icon: "table", color: "text-emerald-500", exts: ["csv", "tsv", "dat"] },
|
|
127
|
+
{ icon: "layout", color: "text-orange-500", exts: ["ppt", "pptx", "key", "odp"] },
|
|
128
|
+
{ icon: "music", color: "text-violet-500", exts: ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma", "aiff"] },
|
|
129
|
+
{ icon: "video", color: "text-rose-500", exts: ["mp4", "mov", "avi", "mkv", "webm", "wmv", "flv", "3gp", "m4v"] },
|
|
130
|
+
{ icon: "file-code", color: "text-yellow-500", exts: ["js", "jsx", "mjs", "cjs"] },
|
|
131
|
+
{ icon: "file-code", color: "text-blue-500", exts: ["ts", "tsx"] },
|
|
132
|
+
{ icon: "globe", color: "text-orange-600", exts: ["html", "htm"] },
|
|
133
|
+
{ icon: "file-code", color: "text-sky-500", exts: ["css", "scss", "less", "sass", "styl"] },
|
|
134
|
+
{ icon: "file-json", color: "text-yellow-600", exts: ["json", "json5"] },
|
|
135
|
+
{ icon: "file-code", color: "text-emerald-500", exts: ["vue", "svelte"] },
|
|
136
|
+
{ icon: "file-code", color: "text-blue-600", exts: ["py", "pyc", "whl"] },
|
|
137
|
+
{ icon: "file-code", color: "text-red-600", exts: ["java", "jar", "class", "war"] },
|
|
138
|
+
{ icon: "file-code", color: "text-slate-600", exts: ["c", "h", "m"] },
|
|
139
|
+
{ icon: "file-code", color: "text-blue-700", exts: ["cpp", "hpp", "cc", "cxx"] },
|
|
140
|
+
{ icon: "file-code", color: "text-cyan-600", exts: ["go"] },
|
|
141
|
+
{ icon: "code-2", color: "text-orange-600", exts: ["rs"] },
|
|
142
|
+
{ icon: "file-code", color: "text-indigo-500", exts: ["php"] },
|
|
143
|
+
{ icon: "code-2", color: "text-red-500", exts: ["rb", "erb"] },
|
|
144
|
+
{ icon: "file-code", color: "text-orange-500", exts: ["swift"] },
|
|
145
|
+
{ icon: "file-code", color: "text-purple-600", exts: ["kt", "kts"] },
|
|
146
|
+
{ icon: "file-code", color: "text-blue-400", exts: ["dart"] },
|
|
147
|
+
{ icon: "file-code", color: "text-blue-800", exts: ["lua"] },
|
|
148
|
+
{ icon: "file-code", color: "text-gray-600", exts: ["pl", "pm"] },
|
|
149
|
+
{ icon: "file-code", color: "text-blue-300", exts: ["r"] },
|
|
150
|
+
{ icon: "database", color: "text-pink-600", exts: ["sql", "db", "sqlite", "db3", "pgsql"] },
|
|
151
|
+
{ icon: "terminal", color: "text-green-600", exts: ["sh", "bat", "cmd", "ps1", "bash", "zsh", "fish"] },
|
|
152
|
+
{
|
|
153
|
+
icon: "settings",
|
|
154
|
+
color: "text-slate-600",
|
|
155
|
+
exts: ["yaml", "yml", "toml", "ini", "conf", "config", "env", "properties"],
|
|
156
|
+
},
|
|
157
|
+
{ icon: "archive", color: "text-amber-600", exts: ["zip", "rar", "7z", "tar", "gz", "bz2", "xz", "iso", "tgz"] },
|
|
158
|
+
{ icon: "package", color: "text-slate-700", exts: ["exe", "dmg", "msi", "bin", "app", "deb", "rpm", "apk", "ipa"] },
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
const extensionConfig = Object.fromEntries(
|
|
162
|
+
extensionGroups.flatMap(function (group) {
|
|
163
|
+
return group.exts.map(function (ext) {
|
|
164
|
+
return [ext, { icon: group.icon, color: group.color }];
|
|
165
|
+
});
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
function escapeHtml(value) {
|
|
170
|
+
return String(value)
|
|
171
|
+
.replace(/&/g, "&")
|
|
172
|
+
.replace(/</g, "<")
|
|
173
|
+
.replace(/>/g, ">")
|
|
174
|
+
.replace(/\"/g, """)
|
|
175
|
+
.replace(/'/g, "'");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function formatBytes(input) {
|
|
179
|
+
const bytes = Number(input);
|
|
180
|
+
if (!Number.isFinite(bytes) || bytes < 0) {
|
|
181
|
+
return "-";
|
|
182
|
+
}
|
|
183
|
+
if (bytes < 1024) {
|
|
184
|
+
return String(bytes) + " B";
|
|
185
|
+
}
|
|
186
|
+
const units = ["KB", "MB", "GB", "TB", "PB"];
|
|
187
|
+
let value = bytes / 1024;
|
|
188
|
+
let unitIndex = 0;
|
|
189
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
190
|
+
value /= 1024;
|
|
191
|
+
unitIndex += 1;
|
|
192
|
+
}
|
|
193
|
+
return value.toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2) + " " + units[unitIndex];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function formatDate(input) {
|
|
197
|
+
if (!input) {
|
|
198
|
+
return "-";
|
|
199
|
+
}
|
|
200
|
+
const ms = Date.parse(String(input));
|
|
201
|
+
if (!Number.isFinite(ms)) {
|
|
202
|
+
return "-";
|
|
203
|
+
}
|
|
204
|
+
return new Date(ms).toLocaleDateString("zh-CN");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function getExtension(name) {
|
|
208
|
+
const lower = String(name || "").toLowerCase();
|
|
209
|
+
const index = lower.lastIndexOf(".");
|
|
210
|
+
if (index < 0 || index === lower.length - 1) {
|
|
211
|
+
return "file";
|
|
212
|
+
}
|
|
213
|
+
return lower.slice(index + 1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function normalizePath(value) {
|
|
217
|
+
return String(value || "")
|
|
218
|
+
.replace(/\\\\/g, "/")
|
|
219
|
+
.replace(/^\\/+/, "")
|
|
220
|
+
.replace(/\\/+$/, "");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function getIconMeta(fileName, type) {
|
|
224
|
+
if (type === "folder") {
|
|
225
|
+
return { icon: "folder", color: "text-blue-500 fill-blue-500/20" };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const safeName = String(fileName || "").toLowerCase();
|
|
229
|
+
const safeType = String(type || "").toLowerCase();
|
|
230
|
+
|
|
231
|
+
if (specialNamesConfig[safeName]) {
|
|
232
|
+
return specialNamesConfig[safeName];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let extension = safeType;
|
|
236
|
+
if (!extension || extension === "file") {
|
|
237
|
+
const parts = safeName.split(".");
|
|
238
|
+
if (parts.length > 1) {
|
|
239
|
+
extension = parts.pop();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (extension && extensionConfig[extension]) {
|
|
244
|
+
return extensionConfig[extension];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return { icon: "file", color: "text-gray-300" };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function renderIcon(fileName, type, size) {
|
|
251
|
+
const meta = getIconMeta(fileName, type);
|
|
252
|
+
return '<i data-lucide="' + meta.icon + '" class="' + meta.color + '" style="width: ' + String(size) + 'px; height: ' + String(size) + 'px"></i>';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function buildData(manifest, rootDirectories) {
|
|
256
|
+
const items = [];
|
|
257
|
+
const byId = new Map();
|
|
258
|
+
const folderByPath = new Map();
|
|
259
|
+
const folderStats = new Map();
|
|
260
|
+
|
|
261
|
+
const rootItem = {
|
|
262
|
+
id: "root",
|
|
263
|
+
parentId: "",
|
|
264
|
+
pathKey: "",
|
|
265
|
+
name: payload.title || "cfshare",
|
|
266
|
+
type: "folder",
|
|
267
|
+
size: "-",
|
|
268
|
+
sizeBytes: 0,
|
|
269
|
+
date: "-",
|
|
270
|
+
dateMs: 0,
|
|
271
|
+
relativeUrl: "",
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
items.push(rootItem);
|
|
275
|
+
byId.set(rootItem.id, rootItem);
|
|
276
|
+
folderByPath.set("", rootItem);
|
|
277
|
+
folderStats.set("", { sizeBytes: 0, dateMs: 0 });
|
|
278
|
+
|
|
279
|
+
let seq = 0;
|
|
280
|
+
const getFolder = function (pathKey, name, parentKey) {
|
|
281
|
+
if (folderByPath.has(pathKey)) {
|
|
282
|
+
return folderByPath.get(pathKey);
|
|
283
|
+
}
|
|
284
|
+
const parentFolder = folderByPath.get(parentKey) || rootItem;
|
|
285
|
+
const folder = {
|
|
286
|
+
id: "d_" + String(++seq),
|
|
287
|
+
parentId: parentFolder.id,
|
|
288
|
+
pathKey: pathKey,
|
|
289
|
+
name: name,
|
|
290
|
+
type: "folder",
|
|
291
|
+
size: "-",
|
|
292
|
+
sizeBytes: 0,
|
|
293
|
+
date: "-",
|
|
294
|
+
dateMs: 0,
|
|
295
|
+
relativeUrl: "",
|
|
296
|
+
};
|
|
297
|
+
items.push(folder);
|
|
298
|
+
byId.set(folder.id, folder);
|
|
299
|
+
folderByPath.set(pathKey, folder);
|
|
300
|
+
folderStats.set(pathKey, { sizeBytes: 0, dateMs: 0 });
|
|
301
|
+
return folder;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
for (const rootNameRaw of Array.isArray(rootDirectories) ? rootDirectories : []) {
|
|
305
|
+
const rootName = normalizePath(rootNameRaw);
|
|
306
|
+
if (!rootName || rootName.includes("/")) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
getFolder(rootName, rootName, "");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
for (const entry of manifest) {
|
|
313
|
+
const normalized = normalizePath(entry && entry.name ? entry.name : "");
|
|
314
|
+
if (!normalized) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
318
|
+
if (parts.length === 0) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
let parentKey = "";
|
|
323
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
324
|
+
const segment = parts[i];
|
|
325
|
+
const currentKey = parentKey ? parentKey + "/" + segment : segment;
|
|
326
|
+
getFolder(currentKey, segment, parentKey);
|
|
327
|
+
parentKey = currentKey;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const fileName = parts[parts.length - 1];
|
|
331
|
+
const parentFolder = folderByPath.get(parentKey) || rootItem;
|
|
332
|
+
const sizeBytes = Number(entry && entry.size ? entry.size : 0);
|
|
333
|
+
const modifiedAt = entry && entry.modified_at ? String(entry.modified_at) : "";
|
|
334
|
+
const modifiedMs = Date.parse(modifiedAt);
|
|
335
|
+
const extension = getExtension(fileName);
|
|
336
|
+
const fileItem = {
|
|
337
|
+
id: "f_" + String(++seq),
|
|
338
|
+
parentId: parentFolder.id,
|
|
339
|
+
pathKey: normalized,
|
|
340
|
+
name: fileName,
|
|
341
|
+
type: extension,
|
|
342
|
+
size: formatBytes(sizeBytes),
|
|
343
|
+
sizeBytes: Number.isFinite(sizeBytes) ? sizeBytes : 0,
|
|
344
|
+
date: formatDate(modifiedAt),
|
|
345
|
+
dateMs: Number.isFinite(modifiedMs) ? modifiedMs : 0,
|
|
346
|
+
relativeUrl: entry && entry.relative_url ? String(entry.relative_url) : "/" + normalized,
|
|
347
|
+
isBinary: entry && typeof entry.is_binary === "boolean" ? Boolean(entry.is_binary) : undefined,
|
|
348
|
+
previewSupported:
|
|
349
|
+
entry && typeof entry.preview_supported === "boolean"
|
|
350
|
+
? Boolean(entry.preview_supported)
|
|
351
|
+
: undefined,
|
|
352
|
+
};
|
|
353
|
+
items.push(fileItem);
|
|
354
|
+
byId.set(fileItem.id, fileItem);
|
|
355
|
+
|
|
356
|
+
let walker = parentKey;
|
|
357
|
+
for (;;) {
|
|
358
|
+
const stats = folderStats.get(walker);
|
|
359
|
+
if (stats) {
|
|
360
|
+
stats.sizeBytes += fileItem.sizeBytes;
|
|
361
|
+
if (fileItem.dateMs > stats.dateMs) {
|
|
362
|
+
stats.dateMs = fileItem.dateMs;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (!walker) {
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
const lastSlash = walker.lastIndexOf("/");
|
|
369
|
+
walker = lastSlash < 0 ? "" : walker.slice(0, lastSlash);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
for (const [pathKey, stats] of folderStats.entries()) {
|
|
374
|
+
const folder = folderByPath.get(pathKey);
|
|
375
|
+
if (!folder) {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
folder.sizeBytes = stats.sizeBytes;
|
|
379
|
+
folder.size = formatBytes(stats.sizeBytes);
|
|
380
|
+
folder.dateMs = stats.dateMs;
|
|
381
|
+
folder.date = stats.dateMs > 0 ? formatDate(new Date(stats.dateMs).toISOString()) : "-";
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return { items: items, byId: byId };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const data = buildData(
|
|
388
|
+
Array.isArray(payload.manifest) ? payload.manifest : [],
|
|
389
|
+
Array.isArray(payload.rootDirectories) ? payload.rootDirectories : [],
|
|
390
|
+
);
|
|
391
|
+
const rootChildren = data.items.filter(function (item) {
|
|
392
|
+
return item.parentId === "root";
|
|
393
|
+
});
|
|
394
|
+
const rootFolders = rootChildren.filter(function (item) {
|
|
395
|
+
return item.type === "folder";
|
|
396
|
+
});
|
|
397
|
+
const rootFiles = rootChildren.filter(function (item) {
|
|
398
|
+
return item.type !== "folder";
|
|
399
|
+
});
|
|
400
|
+
const shouldAutoEnterSingleDirectory = payload.autoEnterSingleDirectory !== false;
|
|
401
|
+
const autoEnterFolder =
|
|
402
|
+
shouldAutoEnterSingleDirectory && rootFolders.length === 1 && rootFiles.length === 0
|
|
403
|
+
? rootFolders[0]
|
|
404
|
+
: null;
|
|
405
|
+
|
|
406
|
+
const state = {
|
|
407
|
+
currentPath: autoEnterFolder
|
|
408
|
+
? [
|
|
409
|
+
{ id: "root", name: payload.title || "cfshare" },
|
|
410
|
+
{ id: autoEnterFolder.id, name: autoEnterFolder.name },
|
|
411
|
+
]
|
|
412
|
+
: [{ id: "root", name: payload.title || "cfshare" }],
|
|
413
|
+
selectedFileId: null,
|
|
414
|
+
viewMode: "grid",
|
|
415
|
+
searchQuery: "",
|
|
416
|
+
panelFile: null,
|
|
417
|
+
isPanelOpen: false,
|
|
418
|
+
presentation: payload.presentation || "download",
|
|
419
|
+
suppressClicksUntil: 0,
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const breadcrumbEl = document.getElementById("breadcrumb");
|
|
423
|
+
const mobileBreadcrumbEl = document.getElementById("mobile-breadcrumb");
|
|
424
|
+
const fileAreaEl = document.getElementById("file-area");
|
|
425
|
+
const panelEl = document.getElementById("details-panel");
|
|
426
|
+
const searchInputEl = document.getElementById("search-input");
|
|
427
|
+
const viewGridEl = document.getElementById("view-grid");
|
|
428
|
+
const viewListEl = document.getElementById("view-list");
|
|
429
|
+
|
|
430
|
+
let panelTimer = null;
|
|
431
|
+
let panelCloseTimer = null;
|
|
432
|
+
const PANEL_ANIMATION_MS = 320;
|
|
433
|
+
|
|
434
|
+
function closestFromEventTarget(target, selector) {
|
|
435
|
+
if (!target) {
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
if (target instanceof Element) {
|
|
439
|
+
return target.closest(selector);
|
|
440
|
+
}
|
|
441
|
+
if (target instanceof Node && target.parentElement) {
|
|
442
|
+
return target.parentElement.closest(selector);
|
|
443
|
+
}
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function getCurrentFolderId() {
|
|
448
|
+
return state.currentPath[state.currentPath.length - 1].id;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function shouldSuppressClick() {
|
|
452
|
+
return Date.now() < state.suppressClicksUntil;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function suppressInteraction(durationMs) {
|
|
456
|
+
const until = Date.now() + Math.max(0, Number(durationMs) || 0);
|
|
457
|
+
if (until > state.suppressClicksUntil) {
|
|
458
|
+
state.suppressClicksUntil = until;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function applyMobilePanelOpenState(open) {
|
|
463
|
+
const backdrop = document.getElementById("panel-backdrop");
|
|
464
|
+
const modalCard = document.getElementById("panel-modal-card");
|
|
465
|
+
if (!backdrop || !modalCard) {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
backdrop.classList.remove("opacity-0", "opacity-100");
|
|
469
|
+
backdrop.classList.add(open ? "opacity-100" : "opacity-0");
|
|
470
|
+
|
|
471
|
+
modalCard.classList.remove(
|
|
472
|
+
"-translate-y-[46%]",
|
|
473
|
+
"-translate-y-[50%]",
|
|
474
|
+
"opacity-0",
|
|
475
|
+
"opacity-100",
|
|
476
|
+
"scale-95",
|
|
477
|
+
"scale-100",
|
|
478
|
+
);
|
|
479
|
+
if (open) {
|
|
480
|
+
modalCard.classList.add("-translate-y-[50%]", "opacity-100", "scale-100");
|
|
481
|
+
} else {
|
|
482
|
+
modalCard.classList.add("-translate-y-[46%]", "opacity-0", "scale-95");
|
|
483
|
+
}
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function getCurrentFiles() {
|
|
488
|
+
if (state.searchQuery.trim() !== "") {
|
|
489
|
+
const q = state.searchQuery.trim().toLowerCase();
|
|
490
|
+
return data.items.filter(function (item) {
|
|
491
|
+
return item.id !== "root" && String(item.name).toLowerCase().includes(q);
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
const folderId = getCurrentFolderId();
|
|
495
|
+
return data.items.filter(function (item) {
|
|
496
|
+
return item.parentId === folderId;
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function isImageItem(item) {
|
|
501
|
+
if (!item || item.type === "folder") {
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
return imageExtensions.has(String(item.type).toLowerCase());
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const textLikeExtensions = new Set([
|
|
508
|
+
"txt",
|
|
509
|
+
"log",
|
|
510
|
+
"md",
|
|
511
|
+
"markdown",
|
|
512
|
+
"rmd",
|
|
513
|
+
"qmd",
|
|
514
|
+
"json",
|
|
515
|
+
"json5",
|
|
516
|
+
"yaml",
|
|
517
|
+
"yml",
|
|
518
|
+
"toml",
|
|
519
|
+
"ini",
|
|
520
|
+
"conf",
|
|
521
|
+
"config",
|
|
522
|
+
"env",
|
|
523
|
+
"properties",
|
|
524
|
+
"xml",
|
|
525
|
+
"csv",
|
|
526
|
+
"tsv",
|
|
527
|
+
"dat",
|
|
528
|
+
"sql",
|
|
529
|
+
"graphql",
|
|
530
|
+
"js",
|
|
531
|
+
"jsx",
|
|
532
|
+
"mjs",
|
|
533
|
+
"cjs",
|
|
534
|
+
"ts",
|
|
535
|
+
"tsx",
|
|
536
|
+
"html",
|
|
537
|
+
"htm",
|
|
538
|
+
"css",
|
|
539
|
+
"scss",
|
|
540
|
+
"less",
|
|
541
|
+
"sass",
|
|
542
|
+
"styl",
|
|
543
|
+
"vue",
|
|
544
|
+
"svelte",
|
|
545
|
+
"py",
|
|
546
|
+
"java",
|
|
547
|
+
"c",
|
|
548
|
+
"h",
|
|
549
|
+
"cpp",
|
|
550
|
+
"hpp",
|
|
551
|
+
"cc",
|
|
552
|
+
"cxx",
|
|
553
|
+
"go",
|
|
554
|
+
"rs",
|
|
555
|
+
"php",
|
|
556
|
+
"rb",
|
|
557
|
+
"erb",
|
|
558
|
+
"swift",
|
|
559
|
+
"kt",
|
|
560
|
+
"kts",
|
|
561
|
+
"dart",
|
|
562
|
+
"lua",
|
|
563
|
+
"pl",
|
|
564
|
+
"pm",
|
|
565
|
+
"r",
|
|
566
|
+
"sh",
|
|
567
|
+
"bat",
|
|
568
|
+
"cmd",
|
|
569
|
+
"ps1",
|
|
570
|
+
"bash",
|
|
571
|
+
"zsh",
|
|
572
|
+
"fish",
|
|
573
|
+
"dockerfile",
|
|
574
|
+
"makefile",
|
|
575
|
+
"gitignore",
|
|
576
|
+
"editorconfig",
|
|
577
|
+
]);
|
|
578
|
+
|
|
579
|
+
const previewSupportedExtensions = new Set([
|
|
580
|
+
"md",
|
|
581
|
+
"markdown",
|
|
582
|
+
"rmd",
|
|
583
|
+
"qmd",
|
|
584
|
+
"html",
|
|
585
|
+
"htm",
|
|
586
|
+
"svg",
|
|
587
|
+
]);
|
|
588
|
+
|
|
589
|
+
const knownBinaryExtensions = new Set([
|
|
590
|
+
"pdf",
|
|
591
|
+
"doc",
|
|
592
|
+
"docx",
|
|
593
|
+
"pages",
|
|
594
|
+
"odt",
|
|
595
|
+
"rtf",
|
|
596
|
+
"xlsx",
|
|
597
|
+
"xls",
|
|
598
|
+
"numbers",
|
|
599
|
+
"ods",
|
|
600
|
+
"ppt",
|
|
601
|
+
"pptx",
|
|
602
|
+
"key",
|
|
603
|
+
"odp",
|
|
604
|
+
"mp3",
|
|
605
|
+
"wav",
|
|
606
|
+
"flac",
|
|
607
|
+
"ogg",
|
|
608
|
+
"m4a",
|
|
609
|
+
"aac",
|
|
610
|
+
"wma",
|
|
611
|
+
"aiff",
|
|
612
|
+
"mp4",
|
|
613
|
+
"mov",
|
|
614
|
+
"avi",
|
|
615
|
+
"mkv",
|
|
616
|
+
"webm",
|
|
617
|
+
"wmv",
|
|
618
|
+
"flv",
|
|
619
|
+
"3gp",
|
|
620
|
+
"m4v",
|
|
621
|
+
"zip",
|
|
622
|
+
"rar",
|
|
623
|
+
"7z",
|
|
624
|
+
"tar",
|
|
625
|
+
"gz",
|
|
626
|
+
"bz2",
|
|
627
|
+
"xz",
|
|
628
|
+
"iso",
|
|
629
|
+
"tgz",
|
|
630
|
+
"exe",
|
|
631
|
+
"dmg",
|
|
632
|
+
"msi",
|
|
633
|
+
"bin",
|
|
634
|
+
"app",
|
|
635
|
+
"deb",
|
|
636
|
+
"rpm",
|
|
637
|
+
"apk",
|
|
638
|
+
"ipa",
|
|
639
|
+
"db",
|
|
640
|
+
"sqlite",
|
|
641
|
+
"db3",
|
|
642
|
+
"whl",
|
|
643
|
+
"jar",
|
|
644
|
+
"class",
|
|
645
|
+
"war",
|
|
646
|
+
]);
|
|
647
|
+
|
|
648
|
+
function getItemExtension(item) {
|
|
649
|
+
if (!item) {
|
|
650
|
+
return "";
|
|
651
|
+
}
|
|
652
|
+
return String(item.type || getExtension(item.name)).toLowerCase();
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function isBinaryItem(item) {
|
|
656
|
+
if (!item || item.type === "folder") {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
if (typeof item.isBinary === "boolean") {
|
|
660
|
+
return item.isBinary;
|
|
661
|
+
}
|
|
662
|
+
const extension = getItemExtension(item);
|
|
663
|
+
if (textLikeExtensions.has(extension)) {
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
if (imageExtensions.has(extension)) {
|
|
667
|
+
return true;
|
|
668
|
+
}
|
|
669
|
+
if (knownBinaryExtensions.has(extension)) {
|
|
670
|
+
return true;
|
|
671
|
+
}
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function supportsPreview(item) {
|
|
676
|
+
if (!item || item.type === "folder") {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
if (typeof item.previewSupported === "boolean") {
|
|
680
|
+
return item.previewSupported;
|
|
681
|
+
}
|
|
682
|
+
if (isBinaryItem(item)) {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
return previewSupportedExtensions.has(getItemExtension(item));
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function buildActionUrl(item, action) {
|
|
689
|
+
if (!item) {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
let relativeUrl = "";
|
|
694
|
+
if (action === "folder-zip") {
|
|
695
|
+
const folderPath = normalizePath(item.pathKey || "");
|
|
696
|
+
if (!folderPath) {
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
relativeUrl = "/__cfshare__/download-folder.zip?path=" + encodeURIComponent(folderPath);
|
|
700
|
+
} else {
|
|
701
|
+
if (!item.relativeUrl) {
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
relativeUrl = item.relativeUrl;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
try {
|
|
708
|
+
const target = new URL(relativeUrl, window.location.origin);
|
|
709
|
+
const current = new URL(window.location.href);
|
|
710
|
+
const currentToken = current.searchParams.get("token");
|
|
711
|
+
if (currentToken && !target.searchParams.get("token")) {
|
|
712
|
+
target.searchParams.set("token", currentToken);
|
|
713
|
+
}
|
|
714
|
+
if (action === "preview" || action === "raw" || action === "download") {
|
|
715
|
+
target.searchParams.set("delivery", action);
|
|
716
|
+
}
|
|
717
|
+
return target.toString();
|
|
718
|
+
} catch {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function openItemAction(item, action) {
|
|
724
|
+
const openUrl = buildActionUrl(item, action);
|
|
725
|
+
if (!openUrl) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (action === "preview" || action === "raw") {
|
|
729
|
+
window.open(openUrl, "_blank", "noopener");
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
window.location.href = openUrl;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function openPreviewOrRaw(item) {
|
|
736
|
+
if (!item || item.type === "folder") {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
openItemAction(item, supportsPreview(item) ? "preview" : "raw");
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function getInlinePreviewUrl(item) {
|
|
743
|
+
if (!item || item.type === "folder") {
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
if (supportsPreview(item)) {
|
|
747
|
+
return buildActionUrl(item, "preview");
|
|
748
|
+
}
|
|
749
|
+
if (!isBinaryItem(item)) {
|
|
750
|
+
return buildActionUrl(item, "raw");
|
|
751
|
+
}
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function isMobileViewport() {
|
|
756
|
+
return window.matchMedia("(max-width: 768px)").matches;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function setSelectedFile(itemId) {
|
|
760
|
+
state.selectedFileId = itemId;
|
|
761
|
+
const selected = itemId ? data.byId.get(itemId) || null : null;
|
|
762
|
+
if (selected) {
|
|
763
|
+
if (panelCloseTimer) {
|
|
764
|
+
clearTimeout(panelCloseTimer);
|
|
765
|
+
panelCloseTimer = null;
|
|
766
|
+
}
|
|
767
|
+
state.panelFile = selected;
|
|
768
|
+
}
|
|
769
|
+
if (panelTimer) {
|
|
770
|
+
clearTimeout(panelTimer);
|
|
771
|
+
panelTimer = null;
|
|
772
|
+
}
|
|
773
|
+
if (itemId) {
|
|
774
|
+
state.isPanelOpen = false;
|
|
775
|
+
renderPanel();
|
|
776
|
+
panelTimer = setTimeout(function () {
|
|
777
|
+
state.isPanelOpen = true;
|
|
778
|
+
if (isMobileViewport() && applyMobilePanelOpenState(true)) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
renderPanel();
|
|
782
|
+
}, 200);
|
|
783
|
+
} else {
|
|
784
|
+
state.isPanelOpen = false;
|
|
785
|
+
const mobileClosingAnimated = isMobileViewport() && applyMobilePanelOpenState(false);
|
|
786
|
+
if (isMobileViewport()) {
|
|
787
|
+
suppressInteraction(PANEL_ANIMATION_MS + 520);
|
|
788
|
+
}
|
|
789
|
+
if (panelCloseTimer) {
|
|
790
|
+
clearTimeout(panelCloseTimer);
|
|
791
|
+
}
|
|
792
|
+
panelCloseTimer = setTimeout(function () {
|
|
793
|
+
panelCloseTimer = null;
|
|
794
|
+
if (state.selectedFileId || state.isPanelOpen) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
state.panelFile = null;
|
|
798
|
+
renderPanel();
|
|
799
|
+
}, PANEL_ANIMATION_MS);
|
|
800
|
+
if (!mobileClosingAnimated) {
|
|
801
|
+
renderPanel();
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
renderFileArea();
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function navigateToFolder(folderId, folderName) {
|
|
808
|
+
state.searchQuery = "";
|
|
809
|
+
if (searchInputEl) {
|
|
810
|
+
searchInputEl.value = "";
|
|
811
|
+
}
|
|
812
|
+
state.currentPath = state.currentPath.concat([{ id: folderId, name: folderName }]);
|
|
813
|
+
setSelectedFile(null);
|
|
814
|
+
renderBreadcrumb();
|
|
815
|
+
renderFileArea();
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function onBreadcrumbClick(index) {
|
|
819
|
+
state.searchQuery = "";
|
|
820
|
+
if (searchInputEl) {
|
|
821
|
+
searchInputEl.value = "";
|
|
822
|
+
}
|
|
823
|
+
state.currentPath = state.currentPath.slice(0, index + 1);
|
|
824
|
+
setSelectedFile(null);
|
|
825
|
+
renderBreadcrumb();
|
|
826
|
+
renderFileArea();
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function onItemDoubleClick(item) {
|
|
830
|
+
if (!item) {
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (item.type === "folder") {
|
|
834
|
+
navigateToFolder(item.id, item.name);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (supportsPreview(item)) {
|
|
838
|
+
openItemAction(item, "preview");
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
if (isBinaryItem(item)) {
|
|
842
|
+
openItemAction(item, "download");
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
openPreviewOrRaw(item);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function renderBreadcrumb() {
|
|
849
|
+
if (!breadcrumbEl) {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
breadcrumbEl.innerHTML = state.currentPath
|
|
853
|
+
.map(function (item, index) {
|
|
854
|
+
const isCurrent = index === state.currentPath.length - 1;
|
|
855
|
+
return '<div class="flex items-center whitespace-nowrap">'
|
|
856
|
+
+ (index > 0
|
|
857
|
+
? '<i data-lucide="chevron-right" class="text-gray-400 mx-1" style="width: 16px; height: 16px"></i>'
|
|
858
|
+
: "")
|
|
859
|
+
+ '<button data-breadcrumb-index="'
|
|
860
|
+
+ String(index)
|
|
861
|
+
+ '" class="px-2 py-1 rounded-md text-sm transition-colors '
|
|
862
|
+
+ (isCurrent
|
|
863
|
+
? "font-semibold text-slate-900 bg-gray-100"
|
|
864
|
+
: "text-gray-500 hover:bg-gray-50 hover:text-slate-700")
|
|
865
|
+
+ '">'
|
|
866
|
+
+ escapeHtml(item.name)
|
|
867
|
+
+ "</button></div>";
|
|
868
|
+
})
|
|
869
|
+
.join("");
|
|
870
|
+
if (mobileBreadcrumbEl) {
|
|
871
|
+
mobileBreadcrumbEl.innerHTML = state.currentPath
|
|
872
|
+
.map(function (item, index) {
|
|
873
|
+
const isCurrent = index === state.currentPath.length - 1;
|
|
874
|
+
return '<button data-breadcrumb-index="'
|
|
875
|
+
+ String(index)
|
|
876
|
+
+ '" class="inline-flex items-center text-sm '
|
|
877
|
+
+ (isCurrent ? "font-semibold text-slate-900" : "text-slate-500")
|
|
878
|
+
+ '">'
|
|
879
|
+
+ (index > 0 ? '<span class="mx-1.5 text-slate-300">/</span>' : "")
|
|
880
|
+
+ '<span>'
|
|
881
|
+
+ escapeHtml(item.name)
|
|
882
|
+
+ "</span></button>";
|
|
883
|
+
})
|
|
884
|
+
.join("");
|
|
885
|
+
}
|
|
886
|
+
if (window.lucide && window.lucide.createIcons) {
|
|
887
|
+
window.lucide.createIcons();
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function renderEmptyFolder() {
|
|
892
|
+
return '<div class="h-full flex flex-col items-center justify-center text-gray-400">'
|
|
893
|
+
+ '<i data-lucide="folder" class="mb-4 opacity-20" style="width: 64px; height: 64px"></i>'
|
|
894
|
+
+ "<p>此文件夹为空</p>"
|
|
895
|
+
+ "</div>";
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function renderFileCard(file, selected) {
|
|
899
|
+
return '<div data-item-id="'
|
|
900
|
+
+ escapeHtml(file.id)
|
|
901
|
+
+ '" class="group relative p-4 rounded-xl border flex flex-col items-center gap-3 cursor-pointer transition-all duration-200 '
|
|
902
|
+
+ (selected
|
|
903
|
+
? "bg-blue-50 border-blue-400 shadow-[0_0_0_1px_rgba(96,165,250,1)]"
|
|
904
|
+
: "bg-white border-gray-100 hover:border-gray-300 hover:shadow-md")
|
|
905
|
+
+ '">'
|
|
906
|
+
+ '<div class="w-12 h-12 flex items-center justify-center transition-transform group-hover:scale-110 duration-300">'
|
|
907
|
+
+ renderIcon(file.name, file.type, 32)
|
|
908
|
+
+ "</div>"
|
|
909
|
+
+ '<div class="text-center w-full">'
|
|
910
|
+
+ '<p class="text-sm font-medium text-slate-700 truncate w-full px-2" title="'
|
|
911
|
+
+ escapeHtml(file.name)
|
|
912
|
+
+ '">'
|
|
913
|
+
+ escapeHtml(file.name)
|
|
914
|
+
+ "</p>"
|
|
915
|
+
+ '<p class="text-xs text-gray-400 mt-1">'
|
|
916
|
+
+ escapeHtml(file.date)
|
|
917
|
+
+ "</p>"
|
|
918
|
+
+ "</div></div>";
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function renderFileRow(file, selected) {
|
|
922
|
+
return '<tr data-item-id="'
|
|
923
|
+
+ escapeHtml(file.id)
|
|
924
|
+
+ '" class="cursor-pointer transition-colors group '
|
|
925
|
+
+ (selected ? "bg-blue-50" : "hover:bg-gray-50")
|
|
926
|
+
+ '">'
|
|
927
|
+
+ '<td class="px-6 py-4 whitespace-nowrap">'
|
|
928
|
+
+ '<div class="flex items-center">'
|
|
929
|
+
+ '<div class="shrink-0 h-8 w-8 flex items-center justify-center">'
|
|
930
|
+
+ renderIcon(file.name, file.type, 20)
|
|
931
|
+
+ "</div>"
|
|
932
|
+
+ '<div class="ml-4"><div class="text-sm font-medium text-gray-900">'
|
|
933
|
+
+ escapeHtml(file.name)
|
|
934
|
+
+ "</div></div></div></td>"
|
|
935
|
+
+ '<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">'
|
|
936
|
+
+ escapeHtml(file.size)
|
|
937
|
+
+ "</td>"
|
|
938
|
+
+ '<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">'
|
|
939
|
+
+ escapeHtml(file.date)
|
|
940
|
+
+ "</td>"
|
|
941
|
+
+ '<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">'
|
|
942
|
+
+ '<button data-download-id="'
|
|
943
|
+
+ escapeHtml(file.id)
|
|
944
|
+
+ '" class="text-gray-400 hover:text-blue-600 transition-colors" title="'
|
|
945
|
+
+ (file.type === "folder" ? "打包下载" : "下载文件")
|
|
946
|
+
+ '">'
|
|
947
|
+
+ '<i data-lucide="download" style="width: 18px; height: 18px"></i>'
|
|
948
|
+
+ "</button></td></tr>";
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function renderPanelActions(panelFile, mobileLayout) {
|
|
952
|
+
if (panelFile.type === "folder") {
|
|
953
|
+
return '<button id="panel-folder-download" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2.5 rounded-xl font-medium transition-colors shadow-sm flex items-center justify-center gap-2">'
|
|
954
|
+
+ '<i data-lucide="download" style="width: 16px; height: 16px"></i>'
|
|
955
|
+
+ "打包下载"
|
|
956
|
+
+ "</button>";
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
if (isImageItem(panelFile)) {
|
|
960
|
+
return '<button id="panel-download" class="w-full '
|
|
961
|
+
+ (mobileLayout ? "bg-blue-600 hover:bg-blue-700 text-white" : "bg-white hover:bg-gray-100 border border-gray-200 text-slate-700")
|
|
962
|
+
+ ' py-2.5 rounded-xl font-medium transition-colors shadow-sm flex items-center justify-center gap-2">'
|
|
963
|
+
+ '<i data-lucide="download" style="width: 16px; height: 16px"></i>'
|
|
964
|
+
+ "下载"
|
|
965
|
+
+ "</button>";
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if (supportsPreview(panelFile) || !isBinaryItem(panelFile)) {
|
|
969
|
+
return '<div class="grid grid-cols-2 gap-2.5">'
|
|
970
|
+
+ '<button id="panel-preview" class="bg-blue-600 hover:bg-blue-700 text-white py-2.5 rounded-xl font-medium transition-colors shadow-sm flex items-center justify-center gap-2">'
|
|
971
|
+
+ '<i data-lucide="eye" style="width: 16px; height: 16px"></i>'
|
|
972
|
+
+ "预览"
|
|
973
|
+
+ "</button>"
|
|
974
|
+
+ '<button id="panel-download" class="bg-white hover:bg-gray-100 border border-gray-200 text-slate-700 py-2.5 rounded-xl font-medium transition-colors shadow-sm flex items-center justify-center gap-2">'
|
|
975
|
+
+ '<i data-lucide="download" style="width: 16px; height: 16px"></i>'
|
|
976
|
+
+ "下载"
|
|
977
|
+
+ "</button>"
|
|
978
|
+
+ "</div>";
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
return '<button id="panel-download" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2.5 rounded-xl font-medium transition-colors shadow-sm flex items-center justify-center gap-2">'
|
|
982
|
+
+ '<i data-lucide="download" style="width: 16px; height: 16px"></i>'
|
|
983
|
+
+ "下载文件"
|
|
984
|
+
+ "</button>";
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function renderFileArea() {
|
|
988
|
+
if (!fileAreaEl) {
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
const currentFiles = getCurrentFiles();
|
|
993
|
+
if (currentFiles.length === 0) {
|
|
994
|
+
fileAreaEl.innerHTML = renderEmptyFolder();
|
|
995
|
+
if (window.lucide && window.lucide.createIcons) {
|
|
996
|
+
window.lucide.createIcons();
|
|
997
|
+
}
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if (state.viewMode === "grid") {
|
|
1002
|
+
const cards = currentFiles
|
|
1003
|
+
.map(function (file) {
|
|
1004
|
+
return renderFileCard(file, state.selectedFileId === file.id);
|
|
1005
|
+
})
|
|
1006
|
+
.join("");
|
|
1007
|
+
fileAreaEl.innerHTML = '<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4">'
|
|
1008
|
+
+ cards
|
|
1009
|
+
+ "</div>";
|
|
1010
|
+
} else {
|
|
1011
|
+
const rows = currentFiles
|
|
1012
|
+
.map(function (file) {
|
|
1013
|
+
return renderFileRow(file, state.selectedFileId === file.id);
|
|
1014
|
+
})
|
|
1015
|
+
.join("");
|
|
1016
|
+
fileAreaEl.innerHTML = '<div class="min-w-full inline-block align-middle">'
|
|
1017
|
+
+ '<div class="border border-gray-200 rounded-lg overflow-hidden">'
|
|
1018
|
+
+ '<table class="min-w-full divide-y divide-gray-200">'
|
|
1019
|
+
+ '<thead class="bg-gray-50"><tr>'
|
|
1020
|
+
+ '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">名称</th>'
|
|
1021
|
+
+ '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">大小</th>'
|
|
1022
|
+
+ '<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">修改日期</th>'
|
|
1023
|
+
+ '<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>'
|
|
1024
|
+
+ '</tr></thead><tbody class="bg-white divide-y divide-gray-200">'
|
|
1025
|
+
+ rows
|
|
1026
|
+
+ "</tbody></table></div></div>";
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
if (window.lucide && window.lucide.createIcons) {
|
|
1030
|
+
window.lucide.createIcons();
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
function renderPanel() {
|
|
1035
|
+
if (!panelEl) {
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
const panelFile = state.panelFile;
|
|
1040
|
+
const mobileLayout = isMobileViewport();
|
|
1041
|
+
if (!panelFile) {
|
|
1042
|
+
panelEl.className = mobileLayout
|
|
1043
|
+
? "fixed inset-0 z-40 pointer-events-none"
|
|
1044
|
+
: "absolute right-0 top-0 bottom-0 w-80 bg-white border-l border-gray-200 shadow-2xl z-30 flex flex-col transform transition-transform duration-300 ease-in-out translate-x-full";
|
|
1045
|
+
panelEl.innerHTML = "";
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (mobileLayout) {
|
|
1050
|
+
const previewUrl = getInlinePreviewUrl(panelFile);
|
|
1051
|
+
const modalHeightClass = isImageItem(panelFile) ? "max-h-[86vh]" : "max-h-[68vh]";
|
|
1052
|
+
panelEl.className = "fixed inset-0 z-40 pointer-events-auto";
|
|
1053
|
+
panelEl.innerHTML = '<div id="panel-backdrop" class="absolute inset-0 bg-slate-900/45 transition-opacity duration-200 '
|
|
1054
|
+
+ (state.isPanelOpen ? "opacity-100" : "opacity-0")
|
|
1055
|
+
+ '"></div>'
|
|
1056
|
+
+ '<div id="panel-modal-card" class="absolute inset-x-3 top-1/2 rounded-2xl bg-white shadow-2xl border border-slate-200 flex flex-col overflow-hidden transform transition-all duration-300 '
|
|
1057
|
+
+ modalHeightClass
|
|
1058
|
+
+ " "
|
|
1059
|
+
+ "-translate-y-[46%] opacity-0 scale-95"
|
|
1060
|
+
+ '">'
|
|
1061
|
+
+ '<div class="p-4 border-b border-gray-100 flex items-center justify-between bg-white/95 backdrop-blur">'
|
|
1062
|
+
+ '<span class="font-semibold text-slate-800">详细信息</span>'
|
|
1063
|
+
+ '<button id="panel-close" class="text-gray-400 hover:text-gray-600 rounded-full p-1.5 hover:bg-gray-100">'
|
|
1064
|
+
+ '<i data-lucide="x" style="width: 18px; height: 18px"></i>'
|
|
1065
|
+
+ "</button></div>"
|
|
1066
|
+
+ (isImageItem(panelFile)
|
|
1067
|
+
? '<div class="w-full aspect-[16/9] bg-slate-200 overflow-hidden">'
|
|
1068
|
+
+ (previewUrl
|
|
1069
|
+
? '<img src="'
|
|
1070
|
+
+ escapeHtml(previewUrl)
|
|
1071
|
+
+ '" alt="'
|
|
1072
|
+
+ escapeHtml(panelFile.name)
|
|
1073
|
+
+ '" class="w-full h-full object-cover" loading="lazy" />'
|
|
1074
|
+
: '<div class="w-full h-full flex items-center justify-center text-gray-400"><i data-lucide="image" style="width: 28px; height: 28px"></i></div>')
|
|
1075
|
+
+ "</div>"
|
|
1076
|
+
: "")
|
|
1077
|
+
+ '<div class="p-5 space-y-4 flex-1 min-h-0 overflow-y-auto">'
|
|
1078
|
+
+ (!isImageItem(panelFile)
|
|
1079
|
+
? '<div class="flex items-center gap-3"><div class="w-14 h-14 bg-gray-50 rounded-xl flex items-center justify-center shadow-inner">'
|
|
1080
|
+
+ renderIcon(panelFile.name, panelFile.type, 30)
|
|
1081
|
+
+ '</div><div class="min-w-0"><h3 class="text-base font-bold text-slate-800 break-all">'
|
|
1082
|
+
+ escapeHtml(panelFile.name)
|
|
1083
|
+
+ '</h3><p class="text-xs text-gray-500 mt-1 uppercase">'
|
|
1084
|
+
+ escapeHtml(panelFile.type === "file" ? "FILE" : String(panelFile.type).toUpperCase())
|
|
1085
|
+
+ "</p></div></div>"
|
|
1086
|
+
: '<div><h3 class="text-base font-bold text-slate-800 break-all">'
|
|
1087
|
+
+ escapeHtml(panelFile.name)
|
|
1088
|
+
+ '</h3><p class="text-xs text-gray-500 mt-1 uppercase">'
|
|
1089
|
+
+ escapeHtml(panelFile.type === "file" ? "FILE" : String(panelFile.type).toUpperCase())
|
|
1090
|
+
+ "</p></div>")
|
|
1091
|
+
+ '<div class="space-y-3 pt-1">'
|
|
1092
|
+
+ '<div class="flex items-start gap-3"><div class="mt-0.5 text-gray-400"><i data-lucide="hard-drive" style="width: 16px; height: 16px"></i></div><div><p class="text-xs font-medium text-gray-500">大小</p><p class="text-sm text-slate-700 font-medium">'
|
|
1093
|
+
+ escapeHtml(panelFile.size)
|
|
1094
|
+
+ "</p></div></div>"
|
|
1095
|
+
+ '<div class="flex items-start gap-3"><div class="mt-0.5 text-gray-400"><i data-lucide="clock" style="width: 16px; height: 16px"></i></div><div><p class="text-xs font-medium text-gray-500">修改时间</p><p class="text-sm text-slate-700 font-medium">'
|
|
1096
|
+
+ escapeHtml(panelFile.date)
|
|
1097
|
+
+ "</p></div></div>"
|
|
1098
|
+
+ '<div class="flex items-start gap-3"><div class="mt-0.5 text-gray-400"><i data-lucide="folder" style="width: 16px; height: 16px"></i></div><div><p class="text-xs font-medium text-gray-500">位置</p><p class="text-sm text-slate-700 font-medium">'
|
|
1099
|
+
+ escapeHtml(state.currentPath[state.currentPath.length - 1].name)
|
|
1100
|
+
+ "</p></div></div>"
|
|
1101
|
+
+ "</div></div>"
|
|
1102
|
+
+ '<div class="p-4 border-t border-gray-100 bg-white/95 backdrop-blur">'
|
|
1103
|
+
+ renderPanelActions(panelFile, true)
|
|
1104
|
+
+ "</div></div>";
|
|
1105
|
+
} else {
|
|
1106
|
+
panelEl.className = "absolute right-0 top-0 bottom-0 w-80 bg-white border-l border-gray-200 shadow-2xl z-30 flex flex-col transform transition-transform duration-300 ease-in-out " + (state.isPanelOpen ? "translate-x-0" : "translate-x-full");
|
|
1107
|
+
|
|
1108
|
+
panelEl.innerHTML = '<div class="p-4 border-b border-gray-100 flex items-center justify-between">'
|
|
1109
|
+
+ '<span class="font-semibold text-slate-700">详细信息</span>'
|
|
1110
|
+
+ '<button id="panel-close" class="text-gray-400 hover:text-gray-600 rounded-full p-1 hover:bg-gray-100">'
|
|
1111
|
+
+ '<i data-lucide="x" style="width: 18px; height: 18px"></i>'
|
|
1112
|
+
+ "</button></div>"
|
|
1113
|
+
+ '<div class="p-6 flex flex-col items-center border-b border-gray-100">'
|
|
1114
|
+
+ '<div class="w-24 h-24 bg-gray-50 rounded-2xl flex items-center justify-center mb-4 shadow-inner">'
|
|
1115
|
+
+ renderIcon(panelFile.name, panelFile.type, 48)
|
|
1116
|
+
+ "</div>"
|
|
1117
|
+
+ '<h3 class="text-lg font-bold text-slate-800 text-center break-all">'
|
|
1118
|
+
+ escapeHtml(panelFile.name)
|
|
1119
|
+
+ "</h3>"
|
|
1120
|
+
+ '<p class="text-sm text-gray-500 mt-1 uppercase">'
|
|
1121
|
+
+ escapeHtml(panelFile.type === "file" ? "FILE" : String(panelFile.type).toUpperCase())
|
|
1122
|
+
+ "</p></div>"
|
|
1123
|
+
+ '<div class="p-6 space-y-6 flex-1 min-h-0 overflow-y-auto">'
|
|
1124
|
+
+ '<div class="space-y-4">'
|
|
1125
|
+
+ '<div class="flex items-start gap-3"><div class="mt-0.5 text-gray-400"><i data-lucide="hard-drive" style="width: 16px; height: 16px"></i></div><div><p class="text-xs font-medium text-gray-500">大小</p><p class="text-sm text-slate-700 font-medium">'
|
|
1126
|
+
+ escapeHtml(panelFile.size)
|
|
1127
|
+
+ "</p></div></div>"
|
|
1128
|
+
+ '<div class="flex items-start gap-3"><div class="mt-0.5 text-gray-400"><i data-lucide="clock" style="width: 16px; height: 16px"></i></div><div><p class="text-xs font-medium text-gray-500">修改时间</p><p class="text-sm text-slate-700 font-medium">'
|
|
1129
|
+
+ escapeHtml(panelFile.date)
|
|
1130
|
+
+ "</p></div></div>"
|
|
1131
|
+
+ '<div class="flex items-start gap-3"><div class="mt-0.5 text-gray-400"><i data-lucide="folder" style="width: 16px; height: 16px"></i></div><div><p class="text-xs font-medium text-gray-500">位置</p><p class="text-sm text-slate-700 font-medium">'
|
|
1132
|
+
+ escapeHtml(state.currentPath[state.currentPath.length - 1].name)
|
|
1133
|
+
+ "</p></div></div>"
|
|
1134
|
+
+ "</div>"
|
|
1135
|
+
+ (isImageItem(panelFile)
|
|
1136
|
+
? '<div class="bg-gray-50 rounded-lg p-3"><p class="text-xs font-semibold text-gray-400 mb-2">预览</p><div class="w-full max-h-72 overflow-auto bg-gray-200 rounded flex items-center justify-center">'
|
|
1137
|
+
+ (getInlinePreviewUrl(panelFile)
|
|
1138
|
+
? '<img src="'
|
|
1139
|
+
+ escapeHtml(getInlinePreviewUrl(panelFile) || "")
|
|
1140
|
+
+ '" alt="'
|
|
1141
|
+
+ escapeHtml(panelFile.name)
|
|
1142
|
+
+ '" class="max-w-full max-h-72 object-contain" loading="lazy" />'
|
|
1143
|
+
: '<i data-lucide="image" style="width: 24px; height: 24px"></i>')
|
|
1144
|
+
+ "</div></div>"
|
|
1145
|
+
: "")
|
|
1146
|
+
+ "</div>"
|
|
1147
|
+
+ '<div class="p-4 border-t border-gray-100 bg-gray-50">'
|
|
1148
|
+
+ renderPanelActions(panelFile, false)
|
|
1149
|
+
+ "</div>";
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
const closeButton = document.getElementById("panel-close");
|
|
1153
|
+
if (closeButton) {
|
|
1154
|
+
closeButton.addEventListener("click", function (event) {
|
|
1155
|
+
event.preventDefault();
|
|
1156
|
+
event.stopPropagation();
|
|
1157
|
+
suppressInteraction(PANEL_ANIMATION_MS + 520);
|
|
1158
|
+
setSelectedFile(null);
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
const backdrop = document.getElementById("panel-backdrop");
|
|
1163
|
+
if (backdrop) {
|
|
1164
|
+
backdrop.addEventListener("click", function (event) {
|
|
1165
|
+
event.preventDefault();
|
|
1166
|
+
event.stopPropagation();
|
|
1167
|
+
suppressInteraction(PANEL_ANIMATION_MS + 520);
|
|
1168
|
+
setSelectedFile(null);
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const modalCard = document.getElementById("panel-modal-card");
|
|
1173
|
+
if (modalCard) {
|
|
1174
|
+
modalCard.addEventListener("click", function (event) {
|
|
1175
|
+
event.stopPropagation();
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
if (mobileLayout && state.isPanelOpen) {
|
|
1180
|
+
requestAnimationFrame(function () {
|
|
1181
|
+
applyMobilePanelOpenState(true);
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
const panelPreviewButton = document.getElementById("panel-preview");
|
|
1186
|
+
if (panelPreviewButton) {
|
|
1187
|
+
panelPreviewButton.addEventListener("click", function () {
|
|
1188
|
+
if (!state.panelFile || state.panelFile.type === "folder") {
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
openPreviewOrRaw(state.panelFile);
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
const panelDownloadButton = document.getElementById("panel-download");
|
|
1196
|
+
if (panelDownloadButton) {
|
|
1197
|
+
panelDownloadButton.addEventListener("click", function () {
|
|
1198
|
+
if (!state.panelFile || state.panelFile.type === "folder") {
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
openItemAction(state.panelFile, "download");
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
const panelFolderDownloadButton = document.getElementById("panel-folder-download");
|
|
1206
|
+
if (panelFolderDownloadButton) {
|
|
1207
|
+
panelFolderDownloadButton.addEventListener("click", function () {
|
|
1208
|
+
if (!state.panelFile || state.panelFile.type !== "folder") {
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
openItemAction(state.panelFile, "folder-zip");
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
if (window.lucide && window.lucide.createIcons) {
|
|
1216
|
+
window.lucide.createIcons();
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
if (breadcrumbEl) {
|
|
1221
|
+
breadcrumbEl.addEventListener("click", function (event) {
|
|
1222
|
+
const target = closestFromEventTarget(event.target, "[data-breadcrumb-index]");
|
|
1223
|
+
if (!target) {
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
const index = Number.parseInt(target.getAttribute("data-breadcrumb-index") || "", 10);
|
|
1227
|
+
if (!Number.isFinite(index) || index < 0) {
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
onBreadcrumbClick(index);
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
if (mobileBreadcrumbEl) {
|
|
1235
|
+
mobileBreadcrumbEl.addEventListener("click", function (event) {
|
|
1236
|
+
const target = closestFromEventTarget(event.target, "[data-breadcrumb-index]");
|
|
1237
|
+
if (!target) {
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
const index = Number.parseInt(target.getAttribute("data-breadcrumb-index") || "", 10);
|
|
1241
|
+
if (!Number.isFinite(index) || index < 0) {
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
onBreadcrumbClick(index);
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
if (searchInputEl) {
|
|
1249
|
+
searchInputEl.addEventListener("input", function () {
|
|
1250
|
+
state.searchQuery = searchInputEl.value || "";
|
|
1251
|
+
setSelectedFile(null);
|
|
1252
|
+
renderFileArea();
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
if (viewGridEl) {
|
|
1257
|
+
viewGridEl.addEventListener("click", function () {
|
|
1258
|
+
state.viewMode = "grid";
|
|
1259
|
+
viewGridEl.className = "p-1.5 rounded-md transition-all bg-white shadow-sm text-blue-600";
|
|
1260
|
+
if (viewListEl) {
|
|
1261
|
+
viewListEl.className = "p-1.5 rounded-md transition-all text-gray-500 hover:text-gray-700";
|
|
1262
|
+
}
|
|
1263
|
+
renderFileArea();
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
if (viewListEl) {
|
|
1268
|
+
viewListEl.addEventListener("click", function () {
|
|
1269
|
+
state.viewMode = "list";
|
|
1270
|
+
viewListEl.className = "p-1.5 rounded-md transition-all bg-white shadow-sm text-blue-600";
|
|
1271
|
+
if (viewGridEl) {
|
|
1272
|
+
viewGridEl.className = "p-1.5 rounded-md transition-all text-gray-500 hover:text-gray-700";
|
|
1273
|
+
}
|
|
1274
|
+
renderFileArea();
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
if (fileAreaEl) {
|
|
1279
|
+
fileAreaEl.addEventListener("click", function (event) {
|
|
1280
|
+
if (shouldSuppressClick()) {
|
|
1281
|
+
event.preventDefault();
|
|
1282
|
+
event.stopPropagation();
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
const downloadButton = closestFromEventTarget(event.target, "[data-download-id]");
|
|
1286
|
+
if (downloadButton) {
|
|
1287
|
+
event.stopPropagation();
|
|
1288
|
+
const fileId = String(downloadButton.getAttribute("data-download-id") || "");
|
|
1289
|
+
const file = data.byId.get(fileId);
|
|
1290
|
+
if (file) {
|
|
1291
|
+
openItemAction(file, file.type === "folder" ? "folder-zip" : "download");
|
|
1292
|
+
}
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
const itemEl = closestFromEventTarget(event.target, "[data-item-id]");
|
|
1297
|
+
if (itemEl) {
|
|
1298
|
+
event.stopPropagation();
|
|
1299
|
+
const fileId = String(itemEl.getAttribute("data-item-id") || "");
|
|
1300
|
+
const file = data.byId.get(fileId);
|
|
1301
|
+
if (file) {
|
|
1302
|
+
if (event.detail >= 2) {
|
|
1303
|
+
onItemDoubleClick(file);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
setSelectedFile(fileId);
|
|
1307
|
+
}
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
setSelectedFile(null);
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
const suppressGlobalEvent = function (event) {
|
|
1316
|
+
if (!shouldSuppressClick()) {
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
event.preventDefault();
|
|
1320
|
+
event.stopPropagation();
|
|
1321
|
+
};
|
|
1322
|
+
document.addEventListener("pointerdown", suppressGlobalEvent, true);
|
|
1323
|
+
document.addEventListener("pointerup", suppressGlobalEvent, true);
|
|
1324
|
+
document.addEventListener("touchend", suppressGlobalEvent, true);
|
|
1325
|
+
document.addEventListener("click", suppressGlobalEvent, true);
|
|
1326
|
+
|
|
1327
|
+
window.addEventListener("resize", function () {
|
|
1328
|
+
if (state.panelFile) {
|
|
1329
|
+
renderPanel();
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
renderBreadcrumb();
|
|
1334
|
+
renderFileArea();
|
|
1335
|
+
renderPanel();
|
|
1336
|
+
})();
|
|
1337
|
+
</script>
|
|
1338
|
+
</body>
|
|
1339
|
+
</html>`;
|
|
1340
|
+
}
|