Ship file viewer, editor improvements, and private package releases
This commit is contained in:
1 parent
645914955c
commit
a4a31032b9
158 files changed
+10523
-137
No files matched your search
@@ -0,0 +1,95 @@
|
||||
import { extensionOf, kindOfExtension, kindOfMime } from "./kinds.ts";
|
||||
import { defaultRegistry, downloadRenderer } from "./registry.ts";
|
||||
import { sniff } from "./sniff.ts";
|
||||
import { readBounded, safeSourceUrl } from "./source.ts";
|
||||
import type { ResolveOptions, ViewerKind, ViewerPlan, ViewerSource } from "./types.ts";
|
||||
|
||||
export async function resolve(
|
||||
source: ViewerSource,
|
||||
options: ResolveOptions = {},
|
||||
): Promise<ViewerPlan> {
|
||||
const ext = extensionOf(source.name);
|
||||
let kind = kindOfMime(source.mime ?? source.blob?.type ?? "") ?? kindOfExtension(ext);
|
||||
const fallback = (reason: string): ViewerPlan => ({
|
||||
kind,
|
||||
renderer: downloadRenderer,
|
||||
fallback: [],
|
||||
needsConvert: false,
|
||||
sandboxed: false,
|
||||
reason,
|
||||
});
|
||||
if ([".exe", ".dll"].includes(ext)) return fallback("Executable files cannot be previewed");
|
||||
const registry = options.registry ?? defaultRegistry;
|
||||
const candidates = (k: ViewerKind, extension = ext) => registry.candidates(k, extension);
|
||||
// No renderer for the kind is not an oversized file: leave the cap open so the
|
||||
// ladder reports why nothing can display it rather than "exceeds preview limit".
|
||||
const capFor = (k: ViewerKind) => {
|
||||
const caps = candidates(k).map((r) => r.maxInlineBytes ?? 2 * 1024 * 1024);
|
||||
return caps.length ? Math.max(...caps) : Infinity;
|
||||
};
|
||||
try {
|
||||
let size = source.blob?.size ?? source.size;
|
||||
let bytes: Uint8Array;
|
||||
if (source.blob) {
|
||||
bytes = new Uint8Array(await source.blob.slice(0, 4096).arrayBuffer());
|
||||
} else {
|
||||
const url = source.src && safeSourceUrl(source.src);
|
||||
if (!url) return fallback("No readable file source");
|
||||
const request = options.fetch ?? fetch;
|
||||
const head = await request(url, { method: "HEAD", signal: options.signal });
|
||||
if (head.ok) {
|
||||
const length = head.headers.get("content-length");
|
||||
if (length !== null && /^\d+$/.test(length)) size = Math.max(size ?? 0, Number(length));
|
||||
kind = kindOfMime(head.headers.get("content-type") ?? "") ?? kind;
|
||||
}
|
||||
if (size !== undefined && size > capFor(kind)) return fallback("File exceeds preview limit");
|
||||
const response = await request(url, {
|
||||
headers: { Range: "bytes=0-4095" },
|
||||
signal: options.signal,
|
||||
});
|
||||
bytes = await readBounded(response, 4096, true);
|
||||
}
|
||||
const detected = sniff(bytes);
|
||||
if (detected.executable) return fallback("Executable files cannot be previewed");
|
||||
const previous = kind;
|
||||
// Plain UTF-8 retains code classification; active markup and signatures win.
|
||||
// RTF is the one office format that is genuinely text, so `doc` survives a
|
||||
// text signature too — otherwise a .rtf loses its extension and its reader.
|
||||
// A generic container signature (OLE, ZIP, unrecognised binary) says only
|
||||
// "not text", so a .docx or .psd keeps the kind its extension implies.
|
||||
const generic = detected.kind === "binary" || detected.kind === "archive";
|
||||
const keepsExtensionKind =
|
||||
(detected.kind === "text" && ["text", "code", "markup", "sheet", "doc"].includes(kind)) ||
|
||||
(generic && ["doc", "sheet", "archive", "binary"].includes(kind));
|
||||
kind = keepsExtensionKind ? kind : detected.kind;
|
||||
// An extension override must not bypass conflicting signatures.
|
||||
const selectedExt =
|
||||
kind === previous || (detected.kind === "text" && previous === "binary") ? ext : "";
|
||||
const available = [];
|
||||
let needsConvert = false;
|
||||
for (const renderer of candidates(kind, selectedExt)) {
|
||||
// A tier 3 renderer needs a converter, and `load` is how it proves one is
|
||||
// reachable. Without that check it could never report itself unavailable.
|
||||
if (renderer.tier === 3 && !renderer.load) continue;
|
||||
if (size !== undefined && size > (renderer.maxInlineBytes ?? 2 * 1024 * 1024)) continue;
|
||||
try {
|
||||
await renderer.load?.(options);
|
||||
if (renderer.tier === 3) needsConvert = true;
|
||||
available.push(renderer);
|
||||
} catch {
|
||||
/* Optional dependency unavailable. */
|
||||
}
|
||||
}
|
||||
const renderer = available.shift();
|
||||
if (!renderer) return fallback("Preview unavailable for this file");
|
||||
return {
|
||||
kind,
|
||||
renderer,
|
||||
fallback: [...available, downloadRenderer],
|
||||
needsConvert: needsConvert && renderer.tier === 3,
|
||||
sandboxed: kind === "markup" || kind === "vector",
|
||||
};
|
||||
} catch {
|
||||
return fallback("File could not be loaded");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user