65 lines
2.2 KiB
TypeScript
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();
|
|
};
|
|
}
|