feat(files): wide file manager layout and basic previews (FM-2)
- Files page uses a wide layout (route handle layout:"wide"): no max width, full-height directory tree docked next to the sidebar, resizable (200-520px, remembered, double-click resets) and collapsible; mobile keeps the tree in a drawer. - GET /files/preview (admin console session only, same path guards as download): text/NFO with encoding detection (BOM/UTF-16/UTF-8/GB18030/Big5), subtitles first 200 cues (SRT/VTT/ASS/SSA, PGS timings), .torrent file list/trackers (passkey masked)/infohash, image dimensions. Reads at most 1 MiB of text; .torrent capped at 4 MiB. - Download also allows text files, .torrent and avif/gif. - Research notes in docs/design/file-preview-research.md. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
3add4bab41
commit
289e8b297a
20 files changed
+1294
-159
No files matched your search
@@ -4,6 +4,7 @@ import type { Principal } from "../../http/auth.ts";
|
||||
import { FilesError, type FmDeps, isAdminSession, recheck, resolveIn } from "./common.ts";
|
||||
import { audit } from "./ledger.ts";
|
||||
import { kindOfName } from "./list.ts";
|
||||
import { previewKindOf } from "./preview-parse.ts";
|
||||
|
||||
// GET /files/download(W5-A2):仅管理员会话;只下载 NFO/字幕/图片小文件(视频不经浏览器,规格 §1 边界);
|
||||
// 路径守卫同其它接口(realpath 在根内、符号链接目标也须在根内,打开前再复核一次);
|
||||
@@ -19,6 +20,7 @@ const MIME: Record<string, string> = {
|
||||
webp: "image/webp",
|
||||
bmp: "image/bmp",
|
||||
avif: "image/avif",
|
||||
torrent: "application/x-bittorrent",
|
||||
nfo: "text/plain; charset=utf-8",
|
||||
xml: "text/plain; charset=utf-8",
|
||||
srt: "text/plain; charset=utf-8",
|
||||
@@ -48,7 +50,10 @@ export async function downloadFile(
|
||||
if (r.rel === "" || r.lst?.isDirectory()) throw new FilesError("bad_request", "只能下载文件,不能下载目录");
|
||||
const name = basename(r.abs);
|
||||
const kind = kindOfName(name);
|
||||
if (!ALLOWED.has(kind)) throw new FilesError("forbidden", `只能下载 NFO、字幕和图片,此文件类型为 ${kind}`);
|
||||
// FM-2:可预览的文本类(txt/log/md/json…)、.torrent、avif/gif 也允许下载
|
||||
const pk = previewKindOf(name);
|
||||
if (!ALLOWED.has(kind) && !pk)
|
||||
throw new FilesError("forbidden", `只能下载 NFO、字幕、图片、文本和 .torrent,此文件类型为 ${kind}`);
|
||||
const real = await recheck(r.abs, r.rootReal);
|
||||
const file = await Deno.open(real, { read: true });
|
||||
let size: number;
|
||||
@@ -67,7 +72,7 @@ export async function downloadFile(
|
||||
}
|
||||
const ext = extname(name).replace(/^\./, "").toLowerCase();
|
||||
// SVG 可带脚本:不内联、不给 image/svg+xml
|
||||
const inline = q.inline && kind === "image" && ext !== "svg";
|
||||
const inline = q.inline && (kind === "image" || pk === "image") && ext !== "svg";
|
||||
await audit(d, principal, "download", { root: q.root, path: r.rel, size, inline });
|
||||
return new Response(file.readable, {
|
||||
status: 200,
|
||||
|
||||
Reference in new issue
Block a user