mirror of https://github.com/VERT-sh/VERT.git
164 lines
4.2 KiB
TypeScript
164 lines
4.2 KiB
TypeScript
import { VertFile, type WorkerMessage } from "$lib/types";
|
|
import { Converter, FormatInfo } from "./converter.svelte";
|
|
import { browser } from "$app/environment";
|
|
import PandocWorker from "$lib/workers/pandoc?worker&url";
|
|
import { error, log } from "$lib/util/logger";
|
|
import { ToastManager } from "$lib/util/toast.svelte";
|
|
import { m } from "$lib/paraglide/messages";
|
|
|
|
export class PandocConverter extends Converter {
|
|
public name = "pandoc";
|
|
public ready = $state(false);
|
|
public wasm: ArrayBuffer = null!;
|
|
|
|
private activeConversions = new Map<string, Worker>();
|
|
|
|
constructor() {
|
|
super();
|
|
if (!browser) return;
|
|
(async () => {
|
|
try {
|
|
this.status = "downloading";
|
|
// currently fetching from unsafe origin due to CORS, needs to be changed before merge
|
|
this.wasm = await fetch("https://newjeans.cafe/pandoc.wasm").then((r) =>
|
|
r.arrayBuffer(),
|
|
);
|
|
|
|
this.status = "ready";
|
|
} catch (err) {
|
|
this.status = "error";
|
|
error(
|
|
["converters", this.name],
|
|
`Failed to load Pandoc worker: ${err}`,
|
|
);
|
|
ToastManager.add({
|
|
type: "error",
|
|
message: m["workers.errors.pandoc"](),
|
|
});
|
|
}
|
|
})();
|
|
}
|
|
|
|
public async convert(file: VertFile, to: string): Promise<VertFile> {
|
|
const worker = new Worker(PandocWorker, {
|
|
type: "module",
|
|
});
|
|
|
|
this.activeConversions.set(file.id, worker);
|
|
|
|
const loadMsg: WorkerMessage = {
|
|
type: "load",
|
|
wasm: this.wasm,
|
|
id: file.id,
|
|
};
|
|
worker.postMessage(loadMsg);
|
|
await waitForMessage(worker, "loaded");
|
|
const convertMsg: WorkerMessage = {
|
|
type: "convert",
|
|
to,
|
|
input: {
|
|
file: file.file,
|
|
name: file.name,
|
|
from: file.from,
|
|
to,
|
|
},
|
|
compression: null,
|
|
id: file.id,
|
|
};
|
|
worker.postMessage(convertMsg);
|
|
const result = await waitForMessage(worker);
|
|
if (result.type === "error") {
|
|
worker.terminate();
|
|
// throw new Error(result.error);
|
|
const error = result.error.toString();
|
|
switch (result.errorKind) {
|
|
case "PandocUnknownReaderError": {
|
|
throw new Error(
|
|
`${file.from} is not a supported input format for documents.`,
|
|
);
|
|
}
|
|
|
|
case "PandocUnknownWriterError": {
|
|
throw new Error(
|
|
`${to} is not a supported output format for documents.`,
|
|
);
|
|
}
|
|
|
|
case "PandocParseError": {
|
|
if (error.includes("JSON missing pandoc-api-version")) {
|
|
throw new Error(
|
|
`This JSON file is not a pandoc-converted JSON file. It must be converted with pandoc / VERT to be converted again.`,
|
|
);
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line no-fallthrough
|
|
default:
|
|
if (result.errorKind)
|
|
throw new Error(
|
|
`[${result.errorKind}] ${result.error}`,
|
|
);
|
|
else throw new Error(result.error);
|
|
}
|
|
}
|
|
|
|
if (!to.startsWith(".")) to = `.${to}`;
|
|
this.activeConversions.delete(file.id);
|
|
worker.terminate();
|
|
return new VertFile(
|
|
new File([result.output], file.name),
|
|
result.isZip ? ".zip" : to,
|
|
);
|
|
}
|
|
|
|
public async cancel(input: VertFile): Promise<void> {
|
|
const worker = this.activeConversions.get(input.id);
|
|
if (!worker) {
|
|
error(
|
|
["converters", this.name],
|
|
`no active conversion found for file ${input.name}`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
log(
|
|
["converters", this.name],
|
|
`cancelling conversion for file ${input.name}`,
|
|
);
|
|
|
|
worker.terminate();
|
|
this.activeConversions.delete(input.id);
|
|
}
|
|
|
|
public supportedFormats = [
|
|
new FormatInfo("docx", true, true),
|
|
new FormatInfo("doc", true, true),
|
|
new FormatInfo("md", true, true),
|
|
new FormatInfo("html", true, true),
|
|
new FormatInfo("rtf", true, true),
|
|
new FormatInfo("csv", true, true),
|
|
new FormatInfo("tsv", true, true),
|
|
new FormatInfo("json", true, true), // must be a pandoc-converted json
|
|
new FormatInfo("rst", true, true),
|
|
new FormatInfo("epub", true, true),
|
|
new FormatInfo("odt", true, true),
|
|
new FormatInfo("docbook", true, true),
|
|
];
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
function waitForMessage(worker: Worker, type?: string): Promise<any> {
|
|
return new Promise((resolve) => {
|
|
const onMessage = (e: MessageEvent) => {
|
|
if (type && e.data.type === type) {
|
|
worker.removeEventListener("message", onMessage);
|
|
resolve(e.data);
|
|
} else {
|
|
worker.removeEventListener("message", onMessage);
|
|
resolve(e.data);
|
|
}
|
|
};
|
|
worker.addEventListener("message", onMessage);
|
|
});
|
|
}
|