Files
WRNexusJS/packages/viewer/src/text.ts
T

65 lines
2.2 KiB
TypeScript

import { setButtonLabel } from "./renderers/dom.ts";
import { convertUrl, converterFormats } from "./convert/client.ts";
import { extensionOf } from "./kinds.ts";
import { readBounded } from "./source.ts";
import type { ViewerSource } from "./types.ts";
/** Bounded plain text for search/indexing consumers; the server rechecks source access. */
export async function extractText(
source: ViewerSource,
options: { baseURI?: string; signal?: AbortSignal; fetch?: typeof fetch } = {},
): Promise<string> {
const url = convertUrl(source, "text", options.baseURI);
if (!url) throw new Error("Text extraction needs a URL");
const response = await (options.fetch ?? fetch)(url, { signal: options.signal });
if (!response.headers.get("content-type")?.startsWith("text/plain")) {
await response.body?.cancel();
throw new Error("Text extraction unavailable");
}
return new TextDecoder().decode(await readBounded(response, 2 * 1024 * 1024));
}
export function mountExtractedText(
parent: HTMLElement,
source: ViewerSource,
signal: AbortSignal,
): () => void {
const doc = parent.ownerDocument;
const button = doc.createElement("button");
button.type = "button";
button.className = "wrn-viewer-action";
setButtonLabel(button, "Extract text");
const details = doc.createElement("section");
const summary = doc.createElement("h3");
summary.textContent = "Extracted text";
const text = doc.createElement("pre");
details.append(summary, text);
let disposed = false;
void converterFormats({ baseURI: doc.baseURI })
.then((formats) => {
if (!disposed && source.src && formats[extensionOf(source.name)]) parent.append(button);
})
.catch(() => {});
button.onclick = async () => {
button.disabled = true;
try {
const result = await extractText(source, { baseURI: doc.baseURI, signal });
if (disposed) return;
text.textContent = result || "No text was found.";
parent.append(details);
} catch {
if (!disposed) {
text.textContent = "Text extraction is unavailable.";
parent.append(details);
}
} finally {
if (!disposed) button.disabled = false;
}
};
return () => {
disposed = true;
button.remove();
details.remove();
};
}