vitepress-plugin-filelist 0.3.1 → 0.4.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 +20 -3
- package/dist/client/browser/index.d.ts +44 -0
- package/dist/client/browser/index.js +700 -27
- package/dist/client/ssr/index.d.ts +44 -0
- package/dist/client/ssr/index.js +513 -7
- package/dist/client/style.css +253 -0
- package/package.json +1 -1
|
@@ -32,6 +32,7 @@ interface FileListLabels {
|
|
|
32
32
|
demoPreview: string;
|
|
33
33
|
demoBatch: string;
|
|
34
34
|
downloadFailed: string;
|
|
35
|
+
/** @deprecated Preview errors are shown in the dialog (`previewUnavailable`). */
|
|
35
36
|
previewFailed: string;
|
|
36
37
|
batchFailed: string;
|
|
37
38
|
defaultZip: string;
|
|
@@ -52,6 +53,23 @@ interface FileListLabels {
|
|
|
52
53
|
expand: string;
|
|
53
54
|
/** Accessible name of the collapse button, `{name}`. */
|
|
54
55
|
collapse: string;
|
|
56
|
+
/** Preview dialog: close button. */
|
|
57
|
+
close: string;
|
|
58
|
+
/** Preview dialog: previous / next previewable file. */
|
|
59
|
+
previous: string;
|
|
60
|
+
next: string;
|
|
61
|
+
/** Preview dialog: rotate image by 90°. */
|
|
62
|
+
rotate: string;
|
|
63
|
+
/** Preview dialog: toggle the image between fit-to-window and actual size. */
|
|
64
|
+
actualSize: string;
|
|
65
|
+
/** Preview dialog: file type that is not previewed (Office and others). */
|
|
66
|
+
previewUnsupported: string;
|
|
67
|
+
/** Preview dialog: preview failed (network, CORS, unsupported codec). */
|
|
68
|
+
previewUnavailable: string;
|
|
69
|
+
/** Preview dialog: PDF larger than the in-page limit, `{size}`. */
|
|
70
|
+
previewTooLarge: string;
|
|
71
|
+
/** Preview dialog: only the first `{size}` of a text file is shown. */
|
|
72
|
+
textTruncated: string;
|
|
55
73
|
}
|
|
56
74
|
interface FileListSource {
|
|
57
75
|
/** OpenList path, directory or single file. */
|
|
@@ -213,6 +231,32 @@ export declare function totalSize(items: Array<{
|
|
|
213
231
|
size?: number;
|
|
214
232
|
}>): number;
|
|
215
233
|
export declare function compareNodes(a: SortableNode, b: SortableNode, key: SortKey, ascending?: boolean): number;
|
|
234
|
+
export type PreviewKind = 'image' | 'video' | 'audio' | 'text' | 'pdf' | 'none';
|
|
235
|
+
/**
|
|
236
|
+
* How a file is shown in the preview dialog. Office documents and anything
|
|
237
|
+
* else the browser cannot render are `none` (info + download).
|
|
238
|
+
*/
|
|
239
|
+
export declare function previewKind(name: string): PreviewKind;
|
|
240
|
+
export interface IframePreview {
|
|
241
|
+
label: string;
|
|
242
|
+
scheme: string;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Viewers from OpenList's public `iframe_previews` setting that match a file,
|
|
246
|
+
* in the order OpenList lists them. Keys are comma-separated extensions or a
|
|
247
|
+
* `/regex/flags` tested against the extension, as in OpenList's frontend.
|
|
248
|
+
*/
|
|
249
|
+
export declare function iframePreviewsFor(name: string, setting: string | undefined): IframePreview[];
|
|
250
|
+
/**
|
|
251
|
+
* Fill an OpenList preview scheme: `$name`, `$url` (raw URL) and `$durl`
|
|
252
|
+
* (signed download URL), each optionally prefixed with `e` (URI-encode) or
|
|
253
|
+
* `b` (base64), e.g. `$e_url`, `$b_url`, `$e_durl`.
|
|
254
|
+
*/
|
|
255
|
+
export declare function fillPreviewUrl(scheme: string, args: {
|
|
256
|
+
name: string;
|
|
257
|
+
rawUrl: string;
|
|
258
|
+
downloadUrl: string;
|
|
259
|
+
}): string;
|
|
216
260
|
/**
|
|
217
261
|
* Register `<FileList>` and `<VPFileList>` globally.
|
|
218
262
|
*
|
package/dist/client/ssr/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { computed, defineComponent, mergeProps, onBeforeUnmount, onMounted, ref, unref, useSSRContext } from "vue";
|
|
2
|
-
import { ssrIncludeBooleanAttr, ssrInterpolate, ssrLooseContain, ssrLooseEqual, ssrRenderAttr, ssrRenderAttrs, ssrRenderClass, ssrRenderList, ssrRenderStyle } from "vue/server-renderer";
|
|
1
|
+
import { computed, defineComponent, mergeProps, onBeforeUnmount, onMounted, ref, unref, useSSRContext, watch } from "vue";
|
|
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 = {
|
|
5
5
|
"zh-CN": {
|
|
@@ -43,7 +43,16 @@ const fileListLocales = {
|
|
|
43
43
|
selectAll: "全选",
|
|
44
44
|
selectItem: "选择 {name}",
|
|
45
45
|
expand: "展开 {name}",
|
|
46
|
-
collapse: "收起 {name}"
|
|
46
|
+
collapse: "收起 {name}",
|
|
47
|
+
close: "关闭",
|
|
48
|
+
previous: "上一个",
|
|
49
|
+
next: "下一个",
|
|
50
|
+
rotate: "旋转",
|
|
51
|
+
actualSize: "原始大小",
|
|
52
|
+
previewUnsupported: "该类型暂不支持在线预览,可下载后查看",
|
|
53
|
+
previewUnavailable: "该文件无法在页面内预览,可下载后查看",
|
|
54
|
+
previewTooLarge: "文件较大({size}),请下载后查看",
|
|
55
|
+
textTruncated: "仅显示前 {size},完整内容请下载"
|
|
47
56
|
},
|
|
48
57
|
"en-US": {
|
|
49
58
|
allFiles: "All files",
|
|
@@ -86,7 +95,16 @@ const fileListLocales = {
|
|
|
86
95
|
selectAll: "Select all",
|
|
87
96
|
selectItem: "Select {name}",
|
|
88
97
|
expand: "Expand {name}",
|
|
89
|
-
collapse: "Collapse {name}"
|
|
98
|
+
collapse: "Collapse {name}",
|
|
99
|
+
close: "Close",
|
|
100
|
+
previous: "Previous",
|
|
101
|
+
next: "Next",
|
|
102
|
+
rotate: "Rotate",
|
|
103
|
+
actualSize: "Actual size",
|
|
104
|
+
previewUnsupported: "This file type cannot be previewed here. Download it to view.",
|
|
105
|
+
previewUnavailable: "This file cannot be previewed in the page. Download it to view.",
|
|
106
|
+
previewTooLarge: "This file is large ({size}). Download it to view.",
|
|
107
|
+
textTruncated: "Showing the first {size} only. Download for the full file."
|
|
90
108
|
}
|
|
91
109
|
};
|
|
92
110
|
function formatLabel(template, values = {}) {
|
|
@@ -268,6 +286,376 @@ function compareNodes(a, b, key, ascending = true) {
|
|
|
268
286
|
else result = a.name.localeCompare(b.name, "zh-Hans-CN", { numeric: true });
|
|
269
287
|
return ascending ? result : -result;
|
|
270
288
|
}
|
|
289
|
+
const PREVIEW_EXTS = {
|
|
290
|
+
image: [
|
|
291
|
+
"png",
|
|
292
|
+
"jpg",
|
|
293
|
+
"jpeg",
|
|
294
|
+
"gif",
|
|
295
|
+
"webp",
|
|
296
|
+
"svg",
|
|
297
|
+
"bmp",
|
|
298
|
+
"ico",
|
|
299
|
+
"avif"
|
|
300
|
+
],
|
|
301
|
+
video: [
|
|
302
|
+
"mp4",
|
|
303
|
+
"webm",
|
|
304
|
+
"m4v",
|
|
305
|
+
"mov",
|
|
306
|
+
"ogv"
|
|
307
|
+
],
|
|
308
|
+
audio: [
|
|
309
|
+
"mp3",
|
|
310
|
+
"wav",
|
|
311
|
+
"ogg",
|
|
312
|
+
"oga",
|
|
313
|
+
"m4a",
|
|
314
|
+
"aac",
|
|
315
|
+
"flac",
|
|
316
|
+
"opus"
|
|
317
|
+
],
|
|
318
|
+
text: [
|
|
319
|
+
"txt",
|
|
320
|
+
"md",
|
|
321
|
+
"markdown",
|
|
322
|
+
"log",
|
|
323
|
+
"json",
|
|
324
|
+
"csv",
|
|
325
|
+
"tsv",
|
|
326
|
+
"xml",
|
|
327
|
+
"yml",
|
|
328
|
+
"yaml",
|
|
329
|
+
"toml",
|
|
330
|
+
"ini",
|
|
331
|
+
"conf",
|
|
332
|
+
"properties",
|
|
333
|
+
"js",
|
|
334
|
+
"mjs",
|
|
335
|
+
"cjs",
|
|
336
|
+
"ts",
|
|
337
|
+
"tsx",
|
|
338
|
+
"jsx",
|
|
339
|
+
"vue",
|
|
340
|
+
"css",
|
|
341
|
+
"scss",
|
|
342
|
+
"less",
|
|
343
|
+
"html",
|
|
344
|
+
"htm",
|
|
345
|
+
"py",
|
|
346
|
+
"java",
|
|
347
|
+
"kt",
|
|
348
|
+
"c",
|
|
349
|
+
"h",
|
|
350
|
+
"cpp",
|
|
351
|
+
"hpp",
|
|
352
|
+
"cs",
|
|
353
|
+
"go",
|
|
354
|
+
"rs",
|
|
355
|
+
"rb",
|
|
356
|
+
"php",
|
|
357
|
+
"sh",
|
|
358
|
+
"bat",
|
|
359
|
+
"ps1",
|
|
360
|
+
"sql",
|
|
361
|
+
"srt",
|
|
362
|
+
"vtt",
|
|
363
|
+
"ass",
|
|
364
|
+
"lrc",
|
|
365
|
+
"gitignore",
|
|
366
|
+
"env"
|
|
367
|
+
],
|
|
368
|
+
pdf: ["pdf"]
|
|
369
|
+
};
|
|
370
|
+
const PREVIEW_KINDS = {};
|
|
371
|
+
for (const [kind, exts] of Object.entries(PREVIEW_EXTS)) for (const ext of exts) PREVIEW_KINDS[ext] = kind;
|
|
372
|
+
function lowerExt(name) {
|
|
373
|
+
const dot = name.lastIndexOf(".");
|
|
374
|
+
return dot >= 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* How a file is shown in the preview dialog. Office documents and anything
|
|
378
|
+
* else the browser cannot render are `none` (info + download).
|
|
379
|
+
*/
|
|
380
|
+
function previewKind(name) {
|
|
381
|
+
return PREVIEW_KINDS[lowerExt(name)] ?? "none";
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Viewers from OpenList's public `iframe_previews` setting that match a file,
|
|
385
|
+
* in the order OpenList lists them. Keys are comma-separated extensions or a
|
|
386
|
+
* `/regex/flags` tested against the extension, as in OpenList's frontend.
|
|
387
|
+
*/
|
|
388
|
+
function iframePreviewsFor(name, setting) {
|
|
389
|
+
if (!setting) return [];
|
|
390
|
+
let parsed;
|
|
391
|
+
try {
|
|
392
|
+
parsed = JSON.parse(setting);
|
|
393
|
+
} catch {
|
|
394
|
+
return [];
|
|
395
|
+
}
|
|
396
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
397
|
+
const ext = lowerExt(name);
|
|
398
|
+
const out = [];
|
|
399
|
+
for (const [key, viewers] of Object.entries(parsed)) {
|
|
400
|
+
let hit = false;
|
|
401
|
+
if (key.startsWith("/")) {
|
|
402
|
+
const end = key.lastIndexOf("/");
|
|
403
|
+
try {
|
|
404
|
+
hit = end > 0 && new RegExp(key.slice(1, end), key.slice(end + 1)).test(ext);
|
|
405
|
+
} catch {
|
|
406
|
+
hit = false;
|
|
407
|
+
}
|
|
408
|
+
} else hit = key.split(",").map((item) => item.trim().toLowerCase()).includes(ext);
|
|
409
|
+
if (!hit || !viewers || typeof viewers !== "object") continue;
|
|
410
|
+
for (const [label, scheme] of Object.entries(viewers)) if (typeof scheme === "string" && /^https?:\/\//i.test(scheme)) out.push({
|
|
411
|
+
label,
|
|
412
|
+
scheme
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
return out;
|
|
416
|
+
}
|
|
417
|
+
function applyUrlOps(placeholder, url) {
|
|
418
|
+
const ops = placeholder.slice(1, placeholder.lastIndexOf("url")).replace(/d$/, "").match(/[eb]/g) ?? [];
|
|
419
|
+
let value = url;
|
|
420
|
+
for (const op of ops.reverse()) value = op === "e" ? encodeURIComponent(value) : btoa(String.fromCharCode(...new TextEncoder().encode(value)));
|
|
421
|
+
return value;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Fill an OpenList preview scheme: `$name`, `$url` (raw URL) and `$durl`
|
|
425
|
+
* (signed download URL), each optionally prefixed with `e` (URI-encode) or
|
|
426
|
+
* `b` (base64), e.g. `$e_url`, `$b_url`, `$e_durl`.
|
|
427
|
+
*/
|
|
428
|
+
function fillPreviewUrl(scheme, args) {
|
|
429
|
+
return scheme.replace(/\$name/g, args.name).replace(/\$[eb_]*durl/g, (placeholder) => applyUrlOps(placeholder, args.downloadUrl)).replace(/\$[eb_]*url/g, (placeholder) => applyUrlOps(placeholder, args.rawUrl));
|
|
430
|
+
}
|
|
431
|
+
// src/client/PreviewDialog.vue
|
|
432
|
+
const _sfc_main$1 = /*@__PURE__*/ defineComponent({
|
|
433
|
+
__name: "PreviewDialog",
|
|
434
|
+
__ssrInlineRender: true,
|
|
435
|
+
props: {
|
|
436
|
+
open: { type: Boolean },
|
|
437
|
+
file: {},
|
|
438
|
+
labels: {},
|
|
439
|
+
src: { default: "" },
|
|
440
|
+
viewer: { default: "" },
|
|
441
|
+
resolving: {
|
|
442
|
+
type: Boolean,
|
|
443
|
+
default: false
|
|
444
|
+
},
|
|
445
|
+
error: { default: "" },
|
|
446
|
+
demo: {
|
|
447
|
+
type: Boolean,
|
|
448
|
+
default: false
|
|
449
|
+
},
|
|
450
|
+
position: { default: 0 },
|
|
451
|
+
total: { default: 0 },
|
|
452
|
+
maxTextBytes: { default: 1048576 },
|
|
453
|
+
maxPdfBytes: { default: 52428800 }
|
|
454
|
+
},
|
|
455
|
+
emits: [
|
|
456
|
+
"close",
|
|
457
|
+
"prev",
|
|
458
|
+
"next",
|
|
459
|
+
"download"
|
|
460
|
+
],
|
|
461
|
+
setup(__props, { emit: __emit }) {
|
|
462
|
+
/**
|
|
463
|
+
* PreviewDialog.vue —— 文件预览弹窗(原生 <dialog>,不跳转页面)
|
|
464
|
+
*
|
|
465
|
+
* image / video / audio:原生标签;text:Range 读取前 maxTextBytes;
|
|
466
|
+
* pdf:OpenList `iframe_previews` 中的查看器,未配置时用浏览器自带阅读器(Blob);
|
|
467
|
+
* 其它类型(含 Office)、失败、过大、演示模式:文件信息 + 下载。
|
|
468
|
+
*/
|
|
469
|
+
const props = __props;
|
|
470
|
+
function t(key, values) {
|
|
471
|
+
return formatLabel(props.labels[key], values);
|
|
472
|
+
}
|
|
473
|
+
const dialog = ref();
|
|
474
|
+
const kind = computed(() => props.file ? previewKind(props.file.name) : "none");
|
|
475
|
+
const loadingBody = ref(false);
|
|
476
|
+
const bodyFailed = ref(false);
|
|
477
|
+
const textContent = ref("");
|
|
478
|
+
const truncated = ref(false);
|
|
479
|
+
const blobUrl = ref("");
|
|
480
|
+
const rotation = ref(0);
|
|
481
|
+
const actualSize = ref(false);
|
|
482
|
+
let controller;
|
|
483
|
+
let loadSeq = 0;
|
|
484
|
+
const tooLarge = computed(() => kind.value === "pdf" && !props.viewer && (props.file?.size ?? 0) > props.maxPdfBytes);
|
|
485
|
+
const mode = computed(() => {
|
|
486
|
+
if (props.demo) return "demo";
|
|
487
|
+
if (props.error || bodyFailed.value) return "failed";
|
|
488
|
+
if (kind.value === "none") return "unsupported";
|
|
489
|
+
if (tooLarge.value) return "tooLarge";
|
|
490
|
+
return kind.value;
|
|
491
|
+
});
|
|
492
|
+
const busy = computed(() => !props.demo && kind.value !== "none" && !props.error && !bodyFailed.value && (props.resolving || loadingBody.value || !props.src && !tooLarge.value));
|
|
493
|
+
const infoMessage = computed(() => {
|
|
494
|
+
const file = props.file;
|
|
495
|
+
if (!file) return "";
|
|
496
|
+
if (mode.value === "demo") return t("demoPreview", { name: file.name });
|
|
497
|
+
if (mode.value === "tooLarge") return t("previewTooLarge", { size: formatSize(file.size) });
|
|
498
|
+
if (mode.value === "failed") return t("previewUnavailable");
|
|
499
|
+
return t("previewUnsupported");
|
|
500
|
+
});
|
|
501
|
+
function revokeBlob() {
|
|
502
|
+
if (blobUrl.value) URL.revokeObjectURL(blobUrl.value);
|
|
503
|
+
blobUrl.value = "";
|
|
504
|
+
}
|
|
505
|
+
function resetBody() {
|
|
506
|
+
controller?.abort();
|
|
507
|
+
controller = void 0;
|
|
508
|
+
loadSeq += 1;
|
|
509
|
+
revokeBlob();
|
|
510
|
+
loadingBody.value = false;
|
|
511
|
+
bodyFailed.value = false;
|
|
512
|
+
textContent.value = "";
|
|
513
|
+
truncated.value = false;
|
|
514
|
+
rotation.value = 0;
|
|
515
|
+
actualSize.value = false;
|
|
516
|
+
}
|
|
517
|
+
async function readPrefix(response, limit) {
|
|
518
|
+
const reader = response.body?.getReader();
|
|
519
|
+
if (!reader) {
|
|
520
|
+
const all = new Uint8Array(await response.arrayBuffer());
|
|
521
|
+
return {
|
|
522
|
+
bytes: all.slice(0, limit),
|
|
523
|
+
more: all.length > limit
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
const chunks = [];
|
|
527
|
+
let length = 0;
|
|
528
|
+
let more = false;
|
|
529
|
+
while (true) {
|
|
530
|
+
const { done, value } = await reader.read();
|
|
531
|
+
if (done) break;
|
|
532
|
+
chunks.push(value);
|
|
533
|
+
length += value.length;
|
|
534
|
+
if (length >= limit) {
|
|
535
|
+
more = length > limit;
|
|
536
|
+
await reader.cancel().catch(() => {});
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
const bytes = new Uint8Array(Math.min(length, limit));
|
|
541
|
+
let offset = 0;
|
|
542
|
+
for (const chunk of chunks) {
|
|
543
|
+
const part = chunk.subarray(0, bytes.length - offset);
|
|
544
|
+
bytes.set(part, offset);
|
|
545
|
+
offset += part.length;
|
|
546
|
+
if (offset >= bytes.length) break;
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
bytes,
|
|
550
|
+
more
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
async function loadBody() {
|
|
554
|
+
resetBody();
|
|
555
|
+
const file = props.file;
|
|
556
|
+
if (!props.open || !file || props.demo || !props.src || props.error) return;
|
|
557
|
+
if (kind.value !== "text" && !(kind.value === "pdf" && !props.viewer && !tooLarge.value)) return;
|
|
558
|
+
const seq = loadSeq;
|
|
559
|
+
controller = new AbortController();
|
|
560
|
+
loadingBody.value = true;
|
|
561
|
+
try {
|
|
562
|
+
if (kind.value === "text") {
|
|
563
|
+
const limit = props.maxTextBytes;
|
|
564
|
+
const response = await fetch(props.src, {
|
|
565
|
+
headers: { Range: `bytes=0-${limit - 1}` },
|
|
566
|
+
signal: controller.signal
|
|
567
|
+
});
|
|
568
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
569
|
+
const { bytes, more } = await readPrefix(response, limit);
|
|
570
|
+
if (seq !== loadSeq) return;
|
|
571
|
+
textContent.value = new TextDecoder("utf-8").decode(bytes);
|
|
572
|
+
truncated.value = more || file.size > limit;
|
|
573
|
+
} else {
|
|
574
|
+
const response = await fetch(props.src, { signal: controller.signal });
|
|
575
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
576
|
+
const buffer = await response.arrayBuffer();
|
|
577
|
+
if (seq !== loadSeq) return;
|
|
578
|
+
blobUrl.value = URL.createObjectURL(new Blob([buffer], { type: "application/pdf" }));
|
|
579
|
+
}
|
|
580
|
+
} catch {
|
|
581
|
+
if (seq === loadSeq && !controller?.signal.aborted) bodyFailed.value = true;
|
|
582
|
+
} finally {
|
|
583
|
+
if (seq === loadSeq) loadingBody.value = false;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
watch(() => [
|
|
587
|
+
props.open,
|
|
588
|
+
props.file?.name,
|
|
589
|
+
props.src,
|
|
590
|
+
props.viewer,
|
|
591
|
+
props.error,
|
|
592
|
+
props.demo
|
|
593
|
+
], () => void loadBody(), { immediate: true });
|
|
594
|
+
watch(() => props.open, (open) => {
|
|
595
|
+
const el = dialog.value;
|
|
596
|
+
if (!el) return;
|
|
597
|
+
if (open && !el.open) {
|
|
598
|
+
if (typeof el.showModal === "function") el.showModal();
|
|
599
|
+
else el.setAttribute("open", "");
|
|
600
|
+
} else if (!open && el.open) {
|
|
601
|
+
if (typeof el.close === "function") el.close();
|
|
602
|
+
else el.removeAttribute("open");
|
|
603
|
+
}
|
|
604
|
+
}, { flush: "post" });
|
|
605
|
+
onBeforeUnmount(resetBody);
|
|
606
|
+
return (_ctx, _push, _parent, _attrs) => {
|
|
607
|
+
_push(`<dialog${ssrRenderAttrs(mergeProps({
|
|
608
|
+
ref_key: "dialog",
|
|
609
|
+
ref: dialog,
|
|
610
|
+
class: "fl-preview",
|
|
611
|
+
"aria-label": __props.file?.name
|
|
612
|
+
}, _attrs))}>`);
|
|
613
|
+
if (__props.open && __props.file) {
|
|
614
|
+
_push(`<div class="fl-preview-panel"><header class="fl-preview-head"><span class="fl-type"${ssrRenderAttr("data-kind", unref(fileKind)(__props.file.name))} aria-hidden="true">${ssrInterpolate(unref(extOf)(__props.file.name) || "FILE")}</span><div class="fl-preview-title"><span class="fl-preview-name">${ssrInterpolate(__props.file.name)}</span><span class="fl-preview-meta">${ssrInterpolate(unref(formatSize)(__props.file.size))} · ${ssrInterpolate(unref(formatDate)(__props.file.modified))}</span></div>`);
|
|
615
|
+
if (__props.total > 1 || mode.value === "image" && !busy.value) {
|
|
616
|
+
_push(`<div class="fl-preview-tools fl-preview-nav">`);
|
|
617
|
+
if (__props.total > 1) _push(`<!--[--><button class="fl-preview-tool" type="button"${ssrRenderAttr("aria-label", t("previous"))}${ssrRenderAttr("title", t("previous"))}><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="m15 6-6 6 6 6"></path></svg></button><span class="fl-preview-count">${ssrInterpolate(__props.position)} / ${ssrInterpolate(__props.total)}</span><button class="fl-preview-tool" type="button"${ssrRenderAttr("aria-label", t("next"))}${ssrRenderAttr("title", t("next"))}><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><!--]-->`);
|
|
618
|
+
else _push(`<!---->`);
|
|
619
|
+
if (mode.value === "image" && !busy.value) {
|
|
620
|
+
_push(`<!--[--><button class="fl-preview-tool fl-preview-rotate" type="button"${ssrRenderAttr("aria-label", t("rotate"))}${ssrRenderAttr("title", t("rotate"))}><svg viewBox="0 0 24 24" width="17" height="17" 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><button class="fl-preview-tool fl-preview-zoom" type="button"${ssrRenderAttr("aria-pressed", actualSize.value)}${ssrRenderAttr("aria-label", t("actualSize"))}${ssrRenderAttr("title", t("actualSize"))}><svg viewBox="0 0 24 24" width="17" height="17" 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>`);
|
|
621
|
+
if (!actualSize.value) _push(`<path d="M11 8v6M8 11h6"></path>`);
|
|
622
|
+
else _push(`<path d="M8 11h6"></path>`);
|
|
623
|
+
_push(`</svg></button><!--]-->`);
|
|
624
|
+
} else _push(`<!---->`);
|
|
625
|
+
_push(`</div>`);
|
|
626
|
+
} else _push(`<!---->`);
|
|
627
|
+
_push(`<div class="fl-preview-tools fl-preview-actions">`);
|
|
628
|
+
if (!__props.demo) _push(`<button class="fl-preview-tool fl-preview-download" type="button"${ssrRenderAttr("aria-label", t("download"))}${ssrRenderAttr("title", t("download"))}><svg viewBox="0 0 24 24" width="17" height="17" 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>`);
|
|
629
|
+
else _push(`<!---->`);
|
|
630
|
+
_push(`<button class="fl-preview-tool fl-preview-close" type="button" autofocus${ssrRenderAttr("aria-label", t("close"))}${ssrRenderAttr("title", t("close"))}><svg 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></button></div></header><div class="fl-preview-body"${ssrRenderAttr("data-mode", busy.value ? "loading" : mode.value)}>`);
|
|
631
|
+
if (busy.value) _push(`<div class="fl-preview-status" role="status">${ssrInterpolate(t("loading"))}</div>`);
|
|
632
|
+
else if (mode.value === "image") _push(`<img class="${ssrRenderClass([{ "fl-preview-actual": actualSize.value }, "fl-preview-image"])}"${ssrRenderAttr("src", __props.src)}${ssrRenderAttr("alt", __props.file.name)} style="${ssrRenderStyle({ transform: rotation.value ? `rotate(${rotation.value}deg)` : void 0 })}">`);
|
|
633
|
+
else if (mode.value === "video") _push(`<video class="fl-preview-video"${ssrRenderAttr("src", __props.src)} controls playsinline preload="metadata"></video>`);
|
|
634
|
+
else if (mode.value === "audio") _push(`<audio class="fl-preview-audio"${ssrRenderAttr("src", __props.src)} controls preload="metadata"></audio>`);
|
|
635
|
+
else if (mode.value === "text") {
|
|
636
|
+
_push(`<div class="fl-preview-text">`);
|
|
637
|
+
if (truncated.value) _push(`<p class="fl-preview-note">${ssrInterpolate(t("textTruncated", { size: unref(formatSize)(__props.maxTextBytes) }))}</p>`);
|
|
638
|
+
else _push(`<!---->`);
|
|
639
|
+
_push(`<pre>${ssrInterpolate(textContent.value)}</pre></div>`);
|
|
640
|
+
} else if (mode.value === "pdf") _push(`<iframe class="fl-preview-frame"${ssrRenderAttr("src", __props.viewer || blobUrl.value)}${ssrRenderAttr("title", __props.file.name)}></iframe>`);
|
|
641
|
+
else {
|
|
642
|
+
_push(`<div class="fl-preview-info"><span class="fl-type fl-preview-badge"${ssrRenderAttr("data-kind", unref(fileKind)(__props.file.name))} aria-hidden="true">${ssrInterpolate(unref(extOf)(__props.file.name) || "FILE")}</span><p class="fl-preview-message">${ssrInterpolate(infoMessage.value)}</p>`);
|
|
643
|
+
if (!__props.demo) _push(`<button class="fl-btn fl-btn-primary" type="button"><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(t("download"))}</button>`);
|
|
644
|
+
else _push(`<!---->`);
|
|
645
|
+
_push(`</div>`);
|
|
646
|
+
}
|
|
647
|
+
_push(`</div></div>`);
|
|
648
|
+
} else _push(`<!---->`);
|
|
649
|
+
_push(`</dialog>`);
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
const _sfc_setup$1 = _sfc_main$1.setup;
|
|
654
|
+
_sfc_main$1.setup = (props, ctx) => {
|
|
655
|
+
const ssrContext = useSSRContext();
|
|
656
|
+
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/client/PreviewDialog.vue");
|
|
657
|
+
return _sfc_setup$1 ? _sfc_setup$1(props, ctx) : void 0;
|
|
658
|
+
};
|
|
271
659
|
// src/client/FileList.vue
|
|
272
660
|
const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
273
661
|
__name: "FileList",
|
|
@@ -313,6 +701,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
313
701
|
const sortKey = ref("name");
|
|
314
702
|
const sortAsc = ref(true);
|
|
315
703
|
const siteTitle = ref("");
|
|
704
|
+
/** OpenList's public `iframe_previews` setting (JSON text). */
|
|
705
|
+
const iframePreviews = ref("");
|
|
316
706
|
const batchBusy = ref(false);
|
|
317
707
|
const batchProgress = ref(0);
|
|
318
708
|
const downloadCache = /* @__PURE__ */ new Map();
|
|
@@ -498,13 +888,47 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
498
888
|
refresh: false
|
|
499
889
|
}))?.content ?? [];
|
|
500
890
|
}
|
|
891
|
+
function absoluteUrl(raw) {
|
|
892
|
+
if (!raw) return "";
|
|
893
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) return raw;
|
|
894
|
+
const root = baseUrl.value || (typeof window !== "undefined" ? window.location.origin : "");
|
|
895
|
+
try {
|
|
896
|
+
return new URL(raw, `${root}/`).href;
|
|
897
|
+
} catch {
|
|
898
|
+
return raw;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
async function resolveUrl(node) {
|
|
902
|
+
if (demo.value) return "";
|
|
903
|
+
if (node.raw_url) return absoluteUrl(node.raw_url);
|
|
904
|
+
const cached = downloadCache.get(node.path);
|
|
905
|
+
if (cached) return cached;
|
|
906
|
+
let url = "";
|
|
907
|
+
try {
|
|
908
|
+
url = absoluteUrl((await request("/api/fs/get", {
|
|
909
|
+
path: node.path,
|
|
910
|
+
password: node.password || activePassword.value
|
|
911
|
+
}))?.raw_url ?? "");
|
|
912
|
+
} catch {
|
|
913
|
+
url = "";
|
|
914
|
+
}
|
|
915
|
+
if (!url) url = directUrl(node);
|
|
916
|
+
downloadCache.set(node.path, url);
|
|
917
|
+
return url;
|
|
918
|
+
}
|
|
919
|
+
/** OpenList's signed `/d/` download link for a file. */
|
|
920
|
+
function directUrl(node) {
|
|
921
|
+
return `${baseUrl.value}/d${encodePath(node.path)}${node.sign ? `?sign=${encodeURIComponent(node.sign)}` : ""}`;
|
|
922
|
+
}
|
|
501
923
|
async function loadSiteTitle() {
|
|
502
924
|
if (demo.value) return;
|
|
503
925
|
try {
|
|
504
926
|
const data = await request("/api/public/settings");
|
|
505
927
|
siteTitle.value = data?.site_title || data?.title || "";
|
|
928
|
+
iframePreviews.value = data?.iframe_previews || "";
|
|
506
929
|
} catch {
|
|
507
930
|
siteTitle.value = "";
|
|
931
|
+
iframePreviews.value = "";
|
|
508
932
|
}
|
|
509
933
|
}
|
|
510
934
|
function sortTree(nodes) {
|
|
@@ -675,9 +1099,75 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
675
1099
|
resortAll();
|
|
676
1100
|
}
|
|
677
1101
|
});
|
|
678
|
-
|
|
1102
|
+
function triggerUrl(url) {
|
|
1103
|
+
const anchor = document.createElement("a");
|
|
1104
|
+
anchor.href = url;
|
|
1105
|
+
anchor.target = "_blank";
|
|
1106
|
+
anchor.rel = "noopener";
|
|
1107
|
+
document.body.appendChild(anchor);
|
|
1108
|
+
anchor.click();
|
|
1109
|
+
anchor.remove();
|
|
1110
|
+
}
|
|
1111
|
+
async function download(node) {
|
|
1112
|
+
if (node.is_dir) return;
|
|
1113
|
+
if (demo.value) {
|
|
1114
|
+
showToast(t("demoDownload", { name: node.name }));
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
try {
|
|
1118
|
+
triggerUrl(await resolveUrl(node));
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
showToast(t("downloadFailed", { message: errorText(error) }));
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
/** Only types the dialog can actually render get a preview button. */
|
|
679
1124
|
function canPreview(node) {
|
|
680
|
-
return !node.is_dir &&
|
|
1125
|
+
return !node.is_dir && previewKind(node.name) !== "none";
|
|
1126
|
+
}
|
|
1127
|
+
const previewNode = ref(null);
|
|
1128
|
+
const previewSrc = ref("");
|
|
1129
|
+
const previewViewer = ref("");
|
|
1130
|
+
const previewResolving = ref(false);
|
|
1131
|
+
const previewError = ref("");
|
|
1132
|
+
let previewSeq = 0;
|
|
1133
|
+
/** Previewable files in the order they are listed, for ← / →. */
|
|
1134
|
+
const previewList = computed(() => visible.value.filter(canPreview));
|
|
1135
|
+
const previewPosition = computed(() => previewNode.value ? previewList.value.indexOf(previewNode.value) + 1 : 0);
|
|
1136
|
+
async function preview(node) {
|
|
1137
|
+
const seq = ++previewSeq;
|
|
1138
|
+
previewNode.value = node;
|
|
1139
|
+
previewSrc.value = "";
|
|
1140
|
+
previewViewer.value = "";
|
|
1141
|
+
previewError.value = "";
|
|
1142
|
+
previewResolving.value = false;
|
|
1143
|
+
if (demo.value || !canPreview(node)) return;
|
|
1144
|
+
previewResolving.value = true;
|
|
1145
|
+
try {
|
|
1146
|
+
const url = await resolveUrl(node);
|
|
1147
|
+
if (seq !== previewSeq) return;
|
|
1148
|
+
previewSrc.value = url;
|
|
1149
|
+
if (previewKind(node.name) === "pdf") {
|
|
1150
|
+
const viewer = iframePreviewsFor(node.name, iframePreviews.value)[0];
|
|
1151
|
+
if (viewer) previewViewer.value = fillPreviewUrl(viewer.scheme, {
|
|
1152
|
+
name: node.name,
|
|
1153
|
+
rawUrl: url,
|
|
1154
|
+
downloadUrl: directUrl(node)
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
if (seq === previewSeq) previewError.value = errorText(error);
|
|
1159
|
+
} finally {
|
|
1160
|
+
if (seq === previewSeq) previewResolving.value = false;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function stepPreview(delta) {
|
|
1164
|
+
const list = previewList.value;
|
|
1165
|
+
if (!list.length) return;
|
|
1166
|
+
preview(list[((previewNode.value ? list.indexOf(previewNode.value) : -1) + delta + list.length) % list.length]);
|
|
1167
|
+
}
|
|
1168
|
+
function closePreview() {
|
|
1169
|
+
previewSeq += 1;
|
|
1170
|
+
previewNode.value = null;
|
|
681
1171
|
}
|
|
682
1172
|
const displayTitle = computed(() => props.title || siteTitle.value || (props.path === "/" ? t("allFiles") : props.path));
|
|
683
1173
|
const stickyTop = ref(0);
|
|
@@ -772,6 +1262,22 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
772
1262
|
_push(`<!--]--><div class="fl-foot"><span>${ssrInterpolate(t("loaded", { count: allFiles.value.length }))} · ${ssrInterpolate(t("totalSize", { size: unref(formatSize)(loadedBytes.value) }))}</span></div>`);
|
|
773
1263
|
if (toast.value) _push(`<div class="fl-toast" role="status">${ssrInterpolate(toast.value)}</div>`);
|
|
774
1264
|
else _push(`<!---->`);
|
|
1265
|
+
_push(ssrRenderComponent(_sfc_main$1, {
|
|
1266
|
+
open: !!previewNode.value,
|
|
1267
|
+
file: previewNode.value,
|
|
1268
|
+
labels: text.value,
|
|
1269
|
+
src: previewSrc.value,
|
|
1270
|
+
viewer: previewViewer.value,
|
|
1271
|
+
resolving: previewResolving.value,
|
|
1272
|
+
error: previewError.value,
|
|
1273
|
+
demo: demo.value,
|
|
1274
|
+
position: previewPosition.value,
|
|
1275
|
+
total: previewPosition.value ? previewList.value.length : 0,
|
|
1276
|
+
onClose: closePreview,
|
|
1277
|
+
onPrev: ($event) => stepPreview(-1),
|
|
1278
|
+
onNext: ($event) => stepPreview(1),
|
|
1279
|
+
onDownload: ($event) => previewNode.value && download(previewNode.value)
|
|
1280
|
+
}, null, _parent));
|
|
775
1281
|
_push(`</div>`);
|
|
776
1282
|
};
|
|
777
1283
|
}
|
|
@@ -803,4 +1309,4 @@ function enhanceAppWithFileList({ app }) {
|
|
|
803
1309
|
if (!app._context.components.FileList) app.component("FileList", _sfc_main);
|
|
804
1310
|
if (!app._context.components.VPFileList) app.component("VPFileList", _sfc_main);
|
|
805
1311
|
}
|
|
806
|
-
export { _sfc_main as FileList, _sfc_main as VPFileList, compareNodes, encodePath, enhanceAppWithFileList, extOf, fileKind, fileListLocales, formatDate, formatLabel, formatSize, joinPath, resolveLabels, totalSize, uniqueEntryName, zipEntryName, zipSegment };
|
|
1312
|
+
export { _sfc_main as FileList, _sfc_main as VPFileList, compareNodes, encodePath, enhanceAppWithFileList, extOf, fileKind, fileListLocales, fillPreviewUrl, formatDate, formatLabel, formatSize, iframePreviewsFor, joinPath, previewKind, resolveLabels, totalSize, uniqueEntryName, zipEntryName, zipSegment };
|