/** * PubScript exporters — the shared export engine. * * A single entry point, `exportProject`, flattens a `Project` into the shared * flat model and dispatches to one of six exporters (Markdown, HTML, EPUB, * DOCX, ODT, PDF) with a selectable theme. Every exporter consumes the same * `FlatProject`, so formats stay consistent and golden-file tests are stable. * * The engine is format-agnostic and environment-agnostic: it runs in the web * app (download button), the CLI, and tests. */ import { exportDocx } from "./docx.js"; import { exportEpub } from "./epub.js"; import { exportHtml } from "./html.js"; import { exportMarkdown } from "./md.js"; import { exportOdt } from "./odt.js"; import { exportPdf } from "./pdf.js"; import { flattenProject, type ExportProject, type ExportScope, type FlatProject } from "./model.js"; import { getTheme, type Theme } from "./themes.js"; export type ExportFormat = "markdown" | "html" | "epub" | "docx" | "odt" | "pdf"; /** All supported export formats, in display order. */ export const EXPORT_FORMATS: ExportFormat[] = [ "markdown", "html", "epub", "docx", "odt", "pdf", ]; /** The result of an export: bytes or text plus its metadata. */ export interface ExportResult { format: ExportFormat; mimeType: string; extension: string; data: Uint8Array | string; } /** Options controlling an export. */ export interface ExportOptions { /** Which scope to export (default: whole project). */ scope?: ExportScope; /** The book/chapter id to export when scope is "book"/"chapter". */ scopeId?: string; /** Theme id (e.g. "ink") or a Theme object (default: "ink"). */ theme?: string | Theme; /** Include YAML frontmatter in Markdown output. */ frontmatter?: boolean; /** Fixed ISO timestamp for the export (deterministic output / golden files). */ generatedAt?: string; } interface FormatSpec { mimeType: string; extension: string; build: (fp: FlatProject, theme: Theme, options: ExportOptions) => Promise | string; } const registry: Record = { markdown: { mimeType: "text/markdown", extension: "md", build: (fp, _theme, options) => exportMarkdown(fp.blocks, options.frontmatter ? { frontmatter: fp.frontmatter } : undefined), }, html: { mimeType: "text/html", extension: "html", build: (fp, theme, options) => exportHtml(fp, { theme, tableOfContents: true, generatedAt: options.generatedAt }), }, epub: { mimeType: "application/epub+zip", extension: "epub", build: (fp, theme) => exportEpub(fp, { theme }), }, docx: { mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", extension: "docx", build: (fp, theme, options) => exportDocx(fp, { themeId: theme.id, generatedAt: options.generatedAt }), }, odt: { mimeType: "application/vnd.oasis.opendocument.text", extension: "odt", build: (fp, theme, options) => exportOdt(fp, { theme, generatedAt: options.generatedAt }), }, pdf: { mimeType: "application/pdf", extension: "pdf", build: (fp, theme) => exportPdf(fp, { theme }), }, }; /** * Export a project (or a book/chapter scope of it) to the requested format. * * @param project the source project * @param options format is taken from the caller via `format` argument */ export async function exportProject( project: ExportProject, format: ExportFormat, options?: ExportOptions ): Promise { const spec = registry[format]; if (!spec) throw new Error(`Unsupported export format: ${format}`); const theme = getTheme(typeof options?.theme === "string" ? options.theme : undefined); const flat = flattenProject(project, options); const data = await spec.build(flat, theme, options ?? {}); return { format, mimeType: spec.mimeType, extension: spec.extension, data }; } /** * Convenience: export a project to a format, returning the bytes/text. * Equivalent to `exportProject(project, format)` and reading `.data`. */ export async function exportProjectData( project: ExportProject, format: ExportFormat, options?: ExportOptions ): Promise { return (await exportProject(project, format, options)).data; } export * from "./model.js"; export * from "./themes.js"; export { exportMarkdown } from "./md.js"; export { exportHtml } from "./html.js"; export { exportEpub } from "./epub.js"; export { exportDocx } from "./docx.js"; export { exportOdt } from "./odt.js"; export { exportPdf } from "./pdf.js";